/*global siteglobals, module, lang, django*/ /* PRETIX WIDGET BEGINS HERE */ /* This is embedded in an isolation wrapper that exposes siteglobals as the global scope. */ var Vue = module.exports; var strings = { 'sold_out': django.pgettext('widget', 'Sold out'), 'buy': django.pgettext('widget', 'Buy'), 'reserved': django.pgettext('widget', 'Reserved'), 'free': django.pgettext('widget', 'FREE'), 'price_from': django.pgettext('widget', 'from %(currency)s %(price)s'), 'tax_incl': django.pgettext('widget', 'incl. %(rate)s% %(taxname)s'), 'tax_plus': django.pgettext('widget', 'plus %(rate)s% %(taxname)s'), 'quota_left': django.pgettext('widget', 'currently available: %s'), 'voucher_required': django.pgettext('widget', 'Only available with a voucher'), 'order_min': django.pgettext('widget', 'minimum amount to order: %s'), 'exit': django.pgettext('widget', 'Close ticket shop'), 'loading_error': django.pgettext('widget', 'The ticket shop could not be loaded.'), 'cart_error': django.pgettext('widget', 'The cart could not be created. Please try again later'), 'waiting_list': django.pgettext('widget', 'Waiting list'), 'cart_exists': django.pgettext('widget', 'You currently have an active cart for this event. If you select more' + ' products, they will be added to your existing cart.'), 'resume_checkout': django.pgettext('widget', 'Resume checkout'), 'poweredby': django.pgettext('widget', 'event' + ' ticketing powered by pretix'), 'redeem_voucher': django.pgettext('widget', 'Redeem a voucher'), 'redeem': django.pgettext('widget', 'Redeem'), 'voucher_code': django.pgettext('widget', 'Voucher code'), 'close': django.pgettext('widget', 'Close'), 'continue': django.pgettext('widget', 'Continue'), 'variations': django.pgettext('widget', 'See variations'), }; var setCookie = function (cname, cvalue, exdays) { var d = new Date(); d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000)); var expires = "expires=" + d.toUTCString(); document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/"; }; var getCookie = function (name) { var value = "; " + document.cookie; var parts = value.split("; " + name + "="); if (parts.length == 2) return parts.pop().split(";").shift() || null; else return null; }; /* HTTP API Call helpers */ var api = { '_getXHR': function () { try { return new window.XMLHttpRequest(); } catch (e) { // explicitly bubble up the exception if not found return new window.ActiveXObject('Microsoft.XMLHTTP'); } }, '_getJSON': function (endpoint, callback, err_callback) { var xhr = api._getXHR(); xhr.open("GET", endpoint, true); xhr.onload = function (e) { if (xhr.readyState === 4) { if (xhr.status === 200) { callback(JSON.parse(xhr.responseText)); } else { console.error(xhr.statusText); } } }; xhr.onerror = function (e) { console.error(xhr.statusText); err_callback(xhr, e); }; xhr.send(null); }, '_postFormJSON': function (endpoint, form, callback, err_callback) { var params = [].filter.call(form.elements, function (el) { return (el.type !== 'checkbox' && el.type !== 'radio') || el.checked; }) .filter(function (el) { return !!el.name && !!el.value; }) .filter(function (el) { return !el.disabled; }) .map(function (el) { return encodeURIComponent(el.name) + '=' + encodeURIComponent(el.value); }).join('&'); var xhr = api._getXHR(); xhr.open("POST", endpoint, true); xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); xhr.onload = function (e) { if (xhr.readyState === 4) { if (xhr.status === 200) { callback(JSON.parse(xhr.responseText)); } else { console.error(xhr.statusText); } } }; xhr.onerror = function (e) { console.error(xhr.statusText); err_callback(xhr, e); }; xhr.send(params); } }; var makeid = function (length) { var text = ""; var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; for (var i = 0; i < length; i++) { text += possible.charAt(Math.floor(Math.random() * possible.length)); } return text; }; var site_is_secure = function () { return /https.*/.test(document.location.protocol) }; var widget_id = makeid(16); /* Vue Components */ Vue.component('availbox', { template: ('
' + '
' + '' + strings.voucher_required + '' + '
' + '
' + strings.reserved + '
' + '
' + strings.sold_out + '
' + '' + '
' + '' + '' + '
' + '
'), props: { item: Object, variation: Object }, computed: { input_name: function () { if (this.item.has_variations) { return 'variation_' + this.item.id + '_' + this.variation.id; } else { return 'item_' + this.item.id; } }, order_max: function () { return this.item.has_variations ? this.variation.order_max : this.item.order_max; }, avail: function () { return this.item.has_variations ? this.variation.avail : this.item.avail; }, waiting_list_show: function () { return this.avail[0] < 100 && this.$root.waiting_list_enabled; }, waiting_list_url: function () { if (this.item.has_variations) { return this.$root.event_url + 'w/' + widget_id + '/waitinglist/?item=' + this.item.id + '&var=' + this.variation.id; } else { return this.$root.event_url + 'w/' + widget_id + '/waitinglist/?item=' + this.item.id; } } } }); Vue.component('pricebox', { template: ('
' + '{{ priceline }}' + '' + '{{ original_line }} ' + '{{ priceline }}' + '
' + '{{ $root.currency }} ' + '' + '
' + '' + '{{ taxline }}' + '' + '
'), props: { price: Object, free_price: Boolean, field_name: String, original_price: String }, computed: { display_price: function () { if (this.$root.display_net_prices) { return roundTo(parseFloat(this.price.net), 2).toFixed(2); } else { return roundTo(parseFloat(this.price.gross), 2).toFixed(2); } }, original_line: function () { return this.$root.currency + " " + roundTo(parseFloat(this.original_price), 2).toFixed(2); }, priceline: function () { if (this.price.gross === "0.00") { return strings.free; } else { return this.$root.currency + " " + this.display_price; } }, taxline: function () { if (this.$root.display_net_prices) { return django.interpolate(strings.tax_plus, { 'rate': floatformat(this.price.rate, 2), 'taxname': this.price.name }, true); } else { return django.interpolate(strings.tax_incl, { 'rate': floatformat(this.price.rate, 2), 'taxname': this.price.name }, true); } } } }); Vue.component('variation', { template: ('
' + '
' + '
' + '
' + '{{ variation.value }}' + '
' + '

' + '{{ quota_left_str }}' + '

' + '
' + '
' + '
' + '' + '' + ' ' + '
' + '
' + '' + '
' + '
' + '
' + '
'), props: { variation: Object, item: Object, }, computed: { quota_left_str: function () { return django.interpolate(strings["quota_left"], [this.variation.avail[1]]); }, } }); Vue.component('item', { template: ('
' + '
' + '
' + '' + '
' + '' + '{{ item.name }}' + '' + '{{ item.name }}' + '
' + '

' + '{{ min_order_str }}' + '

' + '

' + '{{ quota_left_str }}' + '

' + '
' + '
' + '
' + '' + '' + '
{{ pricerange }}
' + ' ' + '
' + '' + '
' + '
' + '
' + '' + '' + '
' + '
'), props: { item: Object, }, data: function () { return { expanded: this.$root.show_variations_expanded }; }, methods: { expand: function () { this.expanded = !this.expanded; } }, computed: { classObject: function () { return { 'pretix-widget-item': true, 'pretix-widget-item-with-picture': !!this.item.picture, 'pretix-widget-item-with-variations': this.item.has_variations } }, varClasses: function () { return { 'pretix-widget-item-variations': true, 'pretix-widget-item-variations-expanded': this.expanded, } }, min_order_str: function () { return django.interpolate(strings["order_min"], [this.item.order_min]); }, quota_left_str: function () { return django.interpolate(strings["quota_left"], [this.item.avail[1]]); }, show_toggle: function () { return this.item.has_variations && !this.$root.show_variations_expanded; }, pricerange: function () { if (this.item.min_price !== this.item.max_price || this.item.free_price) { return django.interpolate(strings.price_from, { 'currency': this.$root.currency, 'price': floatformat(this.item.min_price, 2) }, true); } else if (this.item.min_price === "0.00" && this.item.max_price === "0.00") { return strings.free; } else { return this.$root.currency + " " + floatformat(this.item.min_price, 2); } }, } }); Vue.component('category', { template: ('
' + '

{{ category.name }}

' + '
' + '
' + '
' + '' + '
' + '
'), props: { category: Object } }); var shared_methods = { buy: function (event) { if (this.$root.useIframe) { event.preventDefault(); } else { return; } if (this.$root.is_button && this.$root.items.length === 0) { this.resume(); } else { var url = this.$root.formTarget + "&locale=" + lang + "&ajax=1"; this.$root.frame_loading = true; this.async_task_interval = 100; api._postFormJSON(url, this.$refs.form, this.buy_callback, this.buy_error_callback); } }, buy_error_callback: function (xhr, data) { this.$root.error_message = strings['cart_error']; this.$root.frame_loading = false; }, buy_check_error_callback: function (xhr, data) { if (xhr.status == 200 || (xhr.status >= 400 && xhr.status < 500)) { this.$root.error_message = strings['cart_error']; this.$root.frame_loading = false; } else { this.async_task_timeout = window.setTimeout(this.buy_check, 1000); } }, buy_callback: function (data) { if (data.redirect) { var iframe = this.$refs['frame-container'].children[0]; this.$root.cart_id = data.cart_id; setCookie(this.$root.cookieName, data.cart_id, 30); if (data.redirect.substr(0, 1) === '/') { data.redirect = this.$root.event_url.replace(/^([^\/]+:\/\/[^\/]+)\/.*$/, "$1") + data.redirect; } var url = data.redirect; if (url.indexOf('?')) { url = url + '&iframe=1&locale=' + lang + '&take_cart_id=' + this.$root.cart_id; } else { url = url + '?iframe=1&locale=' + lang + '&take_cart_id=' + this.$root.cart_id; } if (data.success === false) { url = url.replace(/checkout\/start/g, ""); this.$root.error_message = data.message; if (data.has_cart) { this.$root.error_url_after = url; } this.$root.frame_loading = false; } else { iframe.src = url; } } else { this.async_task_id = data.async_id; if (data.check_url) { this.async_task_check_url = this.$root.event_url.replace(/^([^\/]+:\/\/[^\/]+)\/.*$/, "$1") + data.check_url; } this.async_task_timeout = window.setTimeout(this.buy_check, this.async_task_interval); this.async_task_interval = 250; } }, buy_check: function () { api._getJSON(this.async_task_check_url, this.buy_callback, this.buy_check_error_callback); }, errorContinue: function () { var iframe = this.$refs['frame-container'].children[0]; iframe.src = this.$root.error_url_after; this.$root.frame_loading = true; this.$root.error_message = null; this.$root.error_url_after = null; }, errorClose: function () { this.$root.error_message = null; this.$root.error_url_after = null; }, redeem: function (event) { if (this.$root.useIframe) { event.preventDefault(); } else { return; } var redirect_url = this.$root.voucherFormTarget + '&voucher=' + this.voucher + '&subevent=' + this.$root.subevent; var iframe = this.$refs['frame-container'].children[0]; this.$root.frame_loading = true; iframe.src = redirect_url; }, resume: function () { var redirect_url = this.$root.event_url + 'w/' + widget_id + '/?iframe=1&locale=' + lang + '&take_cart_id=' + this.$root.cart_id; if (this.$root.useIframe) { var iframe = this.$refs['frame-container'].children[0]; this.$root.frame_loading = true; iframe.src = redirect_url; } else { window.open(redirect_url); } }, close: function () { this.$root.frame_shown = false; this.$root.reload(); }, iframeLoaded: function () { if (this.$root.frame_loading) { this.$root.frame_loading = false; this.$root.frame_shown = true; } } }; var shared_widget_data = function () { return { async_task_id: null, async_task_check_url: null, async_task_timeout: null, async_task_interval: 100, voucher: null, } }; var shared_widget_computed = { frameClasses: function () { return { 'pretix-widget-frame-holder': true, 'pretix-widget-frame-shown': this.$root.frame_shown || this.$root.frame_loading, }; }, alertClasses: function () { return { 'pretix-widget-alert-holder': true, 'pretix-widget-alert-shown': this.$root.error_message, }; }, }; var shared_loading_fragment = ( '
' + '' + '
' ); var shared_iframe_fragment = ( '
' + '
' + '' + '
' + '
' + '' + '' + '
' + '
' ); var shared_alert_fragment = ( '
' + '' + '
' + '

{{ $root.error_message }}

' + '

' + '

' + '
' + '
' + '' + '
' ); Vue.component('pretix-widget', { template: ('
' + '
' + shared_loading_fragment + '
' + '' + '' + '
{{ $root.error }}
' + '
' + '' + strings['cart_exists'] + '
' + '
' + '' + '
' + '' + '
' + '
' + '
' + '
' + '

'+ strings['redeem_voucher'] +'

' + '
' + '' + '
' + '' + '' + '
' + '' + '
' + '
' + '
' + '
' + '
' + strings.poweredby + '
' + '
' + shared_iframe_fragment + shared_alert_fragment + '
' + '' ), data: shared_widget_data, methods: shared_methods, computed: shared_widget_computed, }); Vue.component('pretix-button', { template: ('
' + '
' + '
' + '' + '' + '' + '' + '
' + '
' + '
' + shared_iframe_fragment + shared_alert_fragment + '
' + '' ), data: shared_widget_data, methods: shared_methods, computed: shared_widget_computed, }); /* Function to create the actual Vue instances */ var shared_root_methods = { open_link_in_frame: function (event) { if (this.$root.useIframe) { event.preventDefault(); var url = event.target.attributes.href.value; this.$children[0].$refs['frame-container'].children[0].src = url; this.frame_loading = true; } else { return; } }, reload: function () { var url; if (this.$root.subevent) { url = this.$root.event_url + this.$root.subevent + '/widget/product_list?lang=' + lang; } else { url = this.$root.event_url + 'widget/product_list?lang=' + lang; } var cart_id = getCookie(this.cookieName); if (this.$root.voucher_code) { url += '&voucher=' + escape(this.$root.voucher_code); } if (cart_id) { url += "&cart_id=" + cart_id; } var root = this.$root; api._getJSON(url, function (data) { root.categories = data.items_by_category; root.currency = data.currency; root.display_net_prices = data.display_net_prices; root.error = data.error; root.display_add_to_cart = data.display_add_to_cart; root.waiting_list_enabled = data.waiting_list_enabled; root.show_variations_expanded = data.show_variations_expanded; root.cart_id = cart_id; root.cart_exists = data.cart_exists; root.vouchers_exist = data.vouchers_exist; root.itemnum = data.itemnum; if (root.loading > 0) { root.loading--; } }, function (error) { root.categories = []; root.currency = ''; root.error = strings['loading_error']; if (root.loading > 0) { root.loading--; } }); } }; var shared_root_computed = { cookieName: function () { return "pretix_widget_" + this.event_url.replace(/[^a-zA-Z0-9]+/g, "_"); }, voucherFormTarget: function () { var form_target = this.event_url + 'w/' + widget_id + '/redeem?iframe=1&locale=' + lang; if (getCookie(this.cookieName)) { form_target += "&take_cart_id=" + getCookie(this.cookieName); } if (this.subevent) { form_target += "&subevent=" + this.subevent; } return form_target; }, formTarget: function () { var checkout_url = "/" + this.event_url.replace(/^[^\/]+:\/\/([^\/]+)\//, "") + "w/" + widget_id + "/"; if (!this.$root.cart_exists) { checkout_url += "checkout/start"; } var form_target = this.event_url + 'w/' + widget_id + '/cart/add?iframe=1&next=' + encodeURIComponent(checkout_url); if (getCookie(this.cookieName)) { form_target += "&take_cart_id=" + getCookie(this.cookieName); } return form_target; }, useIframe: function () { return Math.min(screen.width, window.innerWidth) >= 800 && (this.skip_ssl || site_is_secure()); }, showPrices: function () { var has_priced = false; var cnt_items = 0; for (var i = 0; i < this.categories.length; i++) { for (var j = 0; j < this.categories[i].items.length; j++) { var item = this.categories[i].items[j]; if (item.has_variations) { cnt_items += item.variations.length; has_priced = true; } else { cnt_items++; has_priced = has_priced || item.price.gross != "0.00"; } } } return has_priced || cnt_items > 1; } }; var create_widget = function (element) { var event_url = element.attributes.event.value; if (!event_url.match(/\/$/)) { event_url += "/"; } var voucher = element.attributes.voucher ? element.attributes.voucher.value : null; var subevent = element.attributes.subevent ? element.attributes.subevent.value : null; var skip_ssl = element.attributes["skip-ssl-check"] ? true : false; var disable_vouchers = element.attributes["disable-vouchers"] ? true : false; if (element.tagName !== "pretix-widget") { element.innerHTML = ""; } var app = new Vue({ el: element, data: function () { return { event_url: event_url, subevent: subevent, is_button: false, categories: null, currency: null, voucher_code: voucher, display_net_prices: false, show_variations_expanded: false, skip_ssl: skip_ssl, error: null, display_add_to_cart: false, loading: 1, widget_id: 'pretix-widget-' + widget_id, frame_loading: false, frame_shown: false, error_message: null, error_url_after: null, vouchers_exist: false, disable_vouchers: disable_vouchers, cart_exists: false, itemcount: 0 } }, created: function () { this.reload(); }, computed: shared_root_computed, methods: shared_root_methods }); return app; }; var create_button = function (element) { var event_url = element.attributes.event.value; if (!event_url.match(/\/$/)) { event_url += "/"; } var voucher = element.attributes.voucher ? element.attributes.voucher.value : null; var subevent = element.attributes.subevent ? element.attributes.subevent.value : null; var raw_items = element.attributes.items ? element.attributes.items.value : ""; var skip_ssl = element.attributes["skip-ssl-check"] ? true : false; var button_text = element.innerHTML; if (element.tagName !== "pretix-button") { element.innerHTML = "" + element.innerHTML + ""; } var itemsplit = raw_items.split(","); var items = []; for (var i = 0; i < itemsplit.length; i++) { if (itemsplit[i].indexOf("=") > 0 ) { var splitthis = itemsplit[i].split("="); items.push({'item': splitthis[0], 'count': splitthis[1]}) } } var app = new Vue({ el: element, data: function () { return { event_url: event_url, subevent: subevent, is_button: true, skip_ssl: skip_ssl, voucher_code: voucher, items: items, error: null, widget_id: 'pretix-widget-' + widget_id, frame_loading: false, frame_shown: false, error_message: null, error_url_after: null, button_text: button_text } }, created: function () { }, computed: shared_root_computed, methods: shared_root_methods }); return app; }; /* Find all widgets on the page and render them */ widgetlist = []; buttonlist = []; document.createElement("pretix-widget"); document.createElement("pretix-button"); docReady(function () { var widgets = document.querySelectorAll("pretix-widget, div.pretix-widget-compat"); var wlength = widgets.length; for (var i = 0; i < wlength; i++) { var widget = widgets[i]; widgetlist.push(create_widget(widget)); } var buttons = document.querySelectorAll("pretix-button, div.pretix-button-compat"); var blength = buttons.length; for (var i = 0; i < blength; i++) { var button = buttons[i]; buttonlist.push(create_button(button)); } }); /* Set a global variable for debugging. In DEBUG mode, siteglobals will be window, otherwise it will be something unnamed. */ siteglobals.pretixwidget = { 'Vue': Vue, 'widgets': widgetlist, 'buttons': buttonlist };