From 303b9912ff66e4d2f2d96dae8d60522861723d8a Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Sat, 3 Mar 2018 11:20:41 +0100 Subject: [PATCH] =?UTF-8?q?Add=20=E2=80=9Ebutton=E2=80=9C=20operation=20mo?= =?UTF-8?q?de=20of=20the=20widget=20(#778)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/user/events/widget.rst | 39 ++ src/pretix/presale/views/widget.py | 2 +- .../static/pretixpresale/js/widget/widget.js | 469 +++++++++++------- 3 files changed, 325 insertions(+), 185 deletions(-) diff --git a/doc/user/events/widget.rst b/doc/user/events/widget.rst index 6f497b1c84..30d7e6132c 100644 --- a/doc/user/events/widget.rst +++ b/doc/user/events/widget.rst @@ -101,4 +101,43 @@ voucher's settings. +pretix Button +------------- + +Instead of a product list, you can also display just a single button. When pressed, the button will add a number of +products associated with the button to the cart and will immediately proceed to checkout if the operation succeeded. +You can try out this behavior here: + +.. raw:: html + + Buy ticket! + +

+ +You can embed the pretix Button just like the pretix Widget. Just like above, first embed the CSS and JavaScript +resources. Then, instead of the ``pretix-widget`` tag, use the ``pretix-button`` tag:: + + + Buy ticket! + + +As you can see, the ``pretix-button`` element takes an additional ``items`` attribute that specifies the items that +should be added to the cart. The syntax of this attribute is ``item_ITEMID=1,item_ITEMID=2,variation_ITEMID_VARID=4`` +where ``ITEMID`` are the internal IDs of items to be added and ``VARID`` are the internal IDs of variations of those +items, if the items have variations. + +Just as the widget, the button supports the optional attributes ``voucher`` and ``skip-ssl-check``. + +You can style the button using the ``pretix-button`` CSS class. + +.. versionchanged:: 1.13 + + The pretix Button has been added in version 1.13. + .. _Let's Encrypt: https://letsencrypt.org/ diff --git a/src/pretix/presale/views/widget.py b/src/pretix/presale/views/widget.py index ea0e5b469d..cd8e0076ae 100644 --- a/src/pretix/presale/views/widget.py +++ b/src/pretix/presale/views/widget.py @@ -101,7 +101,7 @@ def generate_widget_js(lang): @condition(etag_func=widget_js_etag) -@cache_page(60) +@cache_page(1 if settings.DEBUG else 60) def widget_js(request, lang, **kwargs): if lang not in [lc for lc, ll in settings.LANGUAGES]: raise Http404() diff --git a/src/pretix/static/pretixpresale/js/widget/widget.js b/src/pretix/static/pretixpresale/js/widget/widget.js index d5dc9a6b4f..39a93ca8f8 100644 --- a/src/pretix/static/pretixpresale/js/widget/widget.js +++ b/src/pretix/static/pretixpresale/js/widget/widget.js @@ -373,12 +373,168 @@ Vue.component('category', { category: Object } }); + +var shared_methods = { + buy: function (event) { + if (this.$root.useIframe) { + event.preventDefault(); + } else { + return; + } + 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 + '?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 () { + 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 + '/checkout/start?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; + }, + 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: ('
' + template: ('
' + '
' - + '
' - + '' - + '
' + + shared_loading_fragment + '
' + '' + '' @@ -410,154 +566,79 @@ Vue.component('pretix-widget', { + strings.poweredby + '
' + '
' - + '
' - + '
' - + '' - + '
' - + '
' - + '' - + '' - + '
' - + '
' - + '
' - + '' - + '
' - + '

{{ $root.error_message }}

' - + '

' - + '

' - + '
' - + '
' - + '' - + '
' + + shared_iframe_fragment + + shared_alert_fragment + '
' + '' ), - data: function () { - return { - async_task_id: null, - async_task_check_url: null, - async_task_timeout: null, - async_task_interval: 100, - voucher: null, - } - }, - methods: { - buy: function (event) { - if (this.$root.useIframe) { - event.preventDefault(); - } else { - return; - } - 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 + '?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 () { - 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 + '/checkout/start?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; - }, - iframeLoaded: function () { - if (this.$root.frame_loading) { - this.$root.frame_loading = false; - this.$root.frame_shown = true; - } - } - }, - 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, - }; - }, - } + 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; + } + } +}; + +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 + "/checkout/start"; + var form_target = this.event_url + 'w/' + widget_id + '/cart/add?iframe=1&next=' + checkout_url; + if (getCookie(this.cookieName)) { + form_target += "&take_cart_id=" + getCookie(this.cookieName); + } + return form_target; + }, + useIframe: function () { + return window.innerWidth >= 800 && (this.skip_ssl || site_is_secure()); + } +}; + var create_widget = function (element) { var event_url = element.attributes.event.value; if (!event_url.match(/\/$/)) { @@ -578,6 +659,7 @@ var create_widget = function (element) { voucher_code: voucher, display_net_prices: false, show_variations_expanded: false, + skip_ssl: skip_ssl, error: null, display_add_to_cart: false, loading: 1, @@ -623,64 +705,83 @@ var create_widget = function (element) { app.loading--; }); }, - 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 + "/checkout/start"; - var form_target = this.event_url + 'w/' + widget_id + '/cart/add?iframe=1&next=' + checkout_url; - if (getCookie(this.cookieName)) { - form_target += "&take_cart_id=" + getCookie(this.cookieName); - } - return form_target; - }, - useIframe: function () { - return window.innerWidth >= 800 && (skip_ssl || site_is_secure()); + 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; + + var itemsplit = raw_items.split(","); + var items = []; + for (var i = 0; i < itemsplit.length; i++) { + if (itemsplit[i].indexOf("=")) { + 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, + 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 } }, - 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; - } - } - } + 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"); 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"); + 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 + 'widgets': widgetlist, + 'buttons': buttonlist };