/*global $, gettext, ngettext, interpolate */ function formatPrice(price, currency, locale) { if (!window.Intl || !Intl.NumberFormat) return price; var priceToFormat = price if (currency === undefined) { currency = $("[data-currency]").data("currency") } if (locale === undefined) { locale = $("[data-locale]").data("locale") || $("[data-pretixlocale]").data("pretixlocale"); } var opt = currency ? {style: "currency", currency: currency} : null; var nf = new Intl.NumberFormat(locale, opt) if (isNaN(priceToFormat) && priceToFormat.replaceAll) { // price is not a number, try to reformat based on locale/currency-format var replacements = { group: "", decimal: "." } // format a dummy number to get parts of formatting and // replace group and decimal according to replacements // to hopefully get a parsable number nf.formatToParts(1234.567).forEach(function(part) { if (replacements.hasOwnProperty(part.type)) { priceToFormat = priceToFormat.replaceAll(part.value, replacements[part.type]) } }); if (isNaN(priceToFormat)) return price } try { return nf.format(priceToFormat) } catch (error) { return price } } var apiGET = function (url, callback) { $.getJSON(url, function (data) { callback(data); }); }; var i18nToString = function (i18nstring) { var locale = $("body").attr("data-pretixlocale"); if (i18nstring[locale]) { return i18nstring[locale]; } else if (i18nstring["en"]) { return i18nstring["en"]; } for (key in i18nstring) { if (i18nstring[key]) { return i18nstring[key]; } } }; $(document).ajaxError(function (event, jqXHR, settings, thrownError) { waitingDialog.hide(); var c = $(jqXHR.responseText).filter('.container'); if (jqXHR.responseText && jqXHR.responseText.indexOf("") !== -1) { location.href = '/control/login?next=' + encodeURIComponent(location.pathname + location.search + location.hash) } else if (c.length > 0) { ajaxErrDialog.show(c.first().html()); } else if (thrownError !== "abort" && thrownError !== "") { console.error(event, jqXHR, settings, thrownError); alert(gettext('Unknown error.')); } }); var form_handlers = function (el) { el.find("[data-formset]").formset( { animateForms: true, reorderMode: 'animate' } ); el.find("[data-formset]").on("formAdded", "div", function (event) { form_handlers($(event.target)); }); el.find("[data-formset] [data-formset-sort]").on("click", function (event) { // Sort forms alphabetically by their first field var $formset = $(this).closest("[data-formset]"); var $forms = $formset.find("[data-formset-form]").not("[data-formset-form-deleted]") var compareForms = function(form_a, form_b) { var a = $(form_a).find('input:not([name*=-ORDER]):not([name*=-DELETE]):not([name*=-id])').val(); var b = $(form_b).find('input:not([name*=-ORDER]):not([name*=-DELETE]):not([name*=-id])').val(); return a.localeCompare(b); } $forms = $forms.sort(compareForms); $forms.each(function(i, form) { var $order = $(form).find('[name*=-ORDER]'); $order.val(i + 1); }); // Trigger visual reorder $formset.find("[name*=-ORDER]").first().trigger("change"); }); // Vouchers el.find("#voucher-bulk-codes-generate").click(function () { if (!$("#voucher-bulk-codes-num").get(0).reportValidity()) return; var num = $("#voucher-bulk-codes-num").val(); var prefix = $('#voucher-bulk-codes-prefix').val(); if (num != "") { var url = $(this).attr("data-rng-url"); $("#id_codes").html("Generating..."); $(".form-group:has(#voucher-bulk-codes-num)").removeClass("has-error"); $.getJSON(url + '?num=' + num + '&prefix=' + escape(prefix), function (data) { $("#id_codes").val(data.codes.join("\n")); }); } else { $(".form-group:has(#voucher-bulk-codes-num)").addClass("has-error"); $("#voucher-bulk-codes-num").focus(); setTimeout(function () { $(".form-group:has(#voucher-bulk-codes-num)").removeClass("has-error"); }, 3000); } }); el.find(".datetimepicker").each(function () { $(this).datetimepicker({ format: $("body").attr("data-datetimeformat"), locale: $("body").attr("data-datetimelocale"), useCurrent: false, showClear: !$(this).prop("required"), icons: { time: 'fa fa-clock-o', date: 'fa fa-calendar', up: 'fa fa-chevron-up', down: 'fa fa-chevron-down', previous: $("html").hasClass("rtl") ? 'fa fa-chevron-right' : 'fa fa-chevron-left', next: $("html").hasClass("rtl") ? 'fa fa-chevron-left' : 'fa fa-chevron-right', today: 'fa fa-screenshot', clear: 'fa fa-trash', close: 'fa fa-remove' } }); if (!$(this).val()) { $(this).data("DateTimePicker").viewDate(moment().hour(0).minute(0).second(0)); } }); el.find(".datepickerfield").each(function () { var opts = { format: $("body").attr("data-dateformat"), locale: $("body").attr("data-datetimelocale"), useCurrent: false, showClear: !$(this).prop("required"), icons: { time: 'fa fa-clock-o', date: 'fa fa-calendar', up: 'fa fa-chevron-up', down: 'fa fa-chevron-down', previous: $("html").hasClass("rtl") ? 'fa fa-chevron-right' : 'fa fa-chevron-left', next: $("html").hasClass("rtl") ? 'fa fa-chevron-left' : 'fa fa-chevron-right', today: 'fa fa-screenshot', clear: 'fa fa-trash', close: 'fa fa-remove' }, }; if ($(this).is('[data-min]')) { opts["minDate"] = $(this).attr("data-min"); opts["viewDate"] = $(this).attr("data-min"); } if ($(this).is('[data-max]')) { opts["maxDate"] = $(this).attr("data-max"); opts["viewDate"] = (opts.minDate && // if minDate and maxDate are set, use the one closer to now as viewDate Math.abs(+new Date(opts.minDate) - new Date()) < Math.abs(+new Date(opts.maxDate) - new Date()) ) ? opts.minDate : opts.maxDate; } if ($(this).is('[data-is-payment-date]')) opts["daysOfWeekDisabled"] = JSON.parse($("body").attr("data-payment-weekdays-disabled")); $(this).datetimepicker(opts).on("dp.hide", function() { // when min/max is used in datetimepicker, closing and re-opening the picker opens at the wrong date // therefore keep the current viewDate and re-set it after datetimepicker is done hiding var $dtp = $(this).data("DateTimePicker"); var currentViewDate = $dtp.viewDate(); window.setTimeout(function () { $dtp.viewDate(currentViewDate); }, 50); }); if ($(this).parent().is('.splitdatetimerow')) { $(this).on("dp.change", function (ev) { var $timepicker = $(this).closest(".splitdatetimerow").find(".timepickerfield"); var date = $(this).data('DateTimePicker').date(); if (date === null) { return; } if ($timepicker.val() === "") { if (/_(until|end|to)(_|$)/.test($(this).attr("name"))) { date.set({'hour': 23, 'minute': 59, 'second': 59}); } else { date.set({'hour': 0, 'minute': 0, 'second': 0}); } $timepicker.data('DateTimePicker').date(date); } }); } }); el.find(".timepickerfield").each(function () { var opts = { format: $("body").attr("data-timeformat"), locale: $("body").attr("data-datetimelocale"), useCurrent: false, showClear: !$(this).prop("required"), icons: { time: 'fa fa-clock-o', date: 'fa fa-calendar', up: 'fa fa-chevron-up', down: 'fa fa-chevron-down', previous: $("html").hasClass("rtl") ? 'fa fa-chevron-right' : 'fa fa-chevron-left', next: $("html").hasClass("rtl") ? 'fa fa-chevron-left' : 'fa fa-chevron-right', today: 'fa fa-screenshot', clear: 'fa fa-trash', close: 'fa fa-remove' } }; if ($(this).is('[data-is-payment-date]')) opts["daysOfWeekDisabled"] = JSON.parse($("body").attr("data-payment-weekdays-disabled")); $(this).datetimepicker(opts); }); el.find(".datetimepicker[data-date-after], .datepickerfield[data-date-after]").each(function () { var later_field = $(this), earlier_field = $($(this).attr("data-date-after")), update = function () { var earlier = earlier_field.data('DateTimePicker').date(), later = later_field.data('DateTimePicker').date(); if (earlier === null) { earlier = false; } else if (later !== null && later.isBefore(earlier) && !later.isSame(earlier)) { later_field.data('DateTimePicker').date(earlier.add(1, 'h')); } later_field.data('DateTimePicker').minDate(earlier); }; update(); earlier_field.on("dp.change", update); }); el.find(".datetimepicker[data-date-default], .datepickerfield[data-date-default]").each(function () { var fill_field = $(this), default_field = $($(this).attr("data-date-default")), show = function () { var fill_date = fill_field.data('DateTimePicker').date(), default_date = default_field.data('DateTimePicker').date(); if (fill_date === null) { fill_field.data("DateTimePicker").defaultDate(default_date); } }; fill_field.on("dp.show", show); }); function luminance(r, g, b) { // Algorithm defined as https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef var a = [r, g, b].map(function (v) { v /= 255; return v <= 0.03928 ? v / 12.92 : Math.pow( (v + 0.055) / 1.055, 2.4 ); }); return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722; } function contrast(rgb1, rgb2) { // Algorithm defined at https://www.w3.org/TR/WCAG20-TECHS/G17.html#G17-tests var l1 = luminance(rgb1[0], rgb1[1], rgb1[2]) + 0.05, l2 = luminance(rgb2[0], rgb2[1], rgb2[2]) + 0.05, ratio = l1/l2 if (l2 > l1) {ratio = 1/ratio} return ratio.toFixed(1) } el.find(".colorpickerfield").colorpicker({ format: 'hex', align: 'left', customClass: 'colorpicker-2x', sliders: { saturation: { maxLeft: 200, maxTop: 200 }, hue: { maxTop: 200 }, alpha: { maxTop: 200 } } }).not(".no-contrast").on('changeColor create', function (e) { if (e.type == 'changeColor' && !e.value) { return; } var rgb = $(this).colorpicker('color').toRGB(); var c = contrast([255,255,255], [rgb.r, rgb.g, rgb.b]); var mark = 'times'; var $icon = $(this).parent().find(".contrast-icon"); if ($icon.length === 0 && $(this).parent().find(".contrast-state").length === 0) { $(this).parent().append("
"); } var $note = $(this).parent().find(".contrast-state"); if ($(this).val() === "") { $note.remove(); } var icon, text, cls; if (c > 7) { icon = "fa-check-circle"; text = gettext('Your color has great contrast and will provide excellent accessibility.'); cls = "text-success"; } else if (c > 4.5) { icon = "fa-info-circle"; text = gettext('Your color has decent contrast and is sufficient for minimum accessibility requirements.'); cls = ""; } else { icon = "fa-warning"; text = gettext('Your color has insufficient contrast to white. Accessibility of your site will be impacted.'); cls = "text-danger"; } if ($icon.length === 0) { $note.html("") .append(text); $note.removeClass("text-success").removeClass("text-danger").addClass(cls); } else { $icon.html("") $icon.attr("title", text); $icon.tooltip('destroy'); window.setTimeout(function() { $icon.tooltip({"title": text}); }, 250); } }); function findDependency(searchString, sourceElement) { if (searchString.substr(0, 1) === '<') { return $(sourceElement).closest("form, .form-horizontal").find(searchString.substr(1)); } else { return $(searchString); } } el.find("input[data-checkbox-dependency]").each(function () { var dependent = $(this), dependency = findDependency($(this).attr("data-checkbox-dependency"), this), update = function () { var enabled = dependency.prop('checked'); dependent.prop('disabled', !enabled).closest('.form-group, .form-field-boundary').toggleClass('disabled', !enabled); if (!enabled && !dependent.is('[data-checkbox-dependency-visual]')) { dependent.prop('checked', false); dependent.trigger('change') } }; update(); dependency.on("change", update); }); el.find("select[data-inverse-dependency], input[data-inverse-dependency]").each(function () { var dependent = $(this), dependency = findDependency($(this).attr("data-inverse-dependency"), this), update = function () { var enabled = !dependency.prop('checked'); dependent.prop('disabled', !enabled).closest('.form-group, .form-field-boundary').toggleClass('disabled', !enabled); }; update(); dependency.on("change", update); }); el.find("div[data-display-dependency], textarea[data-display-dependency], input[data-display-dependency], select[data-display-dependency], button[data-display-dependency]").each(function () { var dependent = $(this), dependency = findDependency($(this).attr("data-display-dependency"), this), update = function (ev) { var enabled = dependency.toArray().some(function(d) { if (d.disabled) return false; if (d.type === 'checkbox' || d.type === 'radio') { return d.checked; } else if (d.type === 'select-one') { var checkValue; if ((checkValue = /^\/(.*)\/$/.exec(dependent.attr("data-display-dependency-regex")))) { return new RegExp(checkValue[1]).test(d.value); } else if ((checkValue = dependent.attr("data-display-dependency-value"))) { return d.value === checkValue; } else { return !!d.value } } else { return (!!d.value && !d.value.match(/^0\.?0*$/g)); } }); if (dependent.is("[data-inverse]")) { enabled = !enabled; } var $toggling = dependent; if (dependent.is("[data-disable-dependent]")) { $toggling.attr('disabled', !enabled).trigger("change"); } const tagName = dependent.get(0).tagName.toLowerCase() if (tagName !== "div" && tagName !== "button") { $toggling = dependent.closest('.form-group'); } if (ev) { if (enabled) { $toggling.stop().slideDown(); } else { $toggling.stop().slideUp(); } } else { $toggling.stop().toggle(enabled); } }; update(); dependency.each(function() { $(this).closest('.form-group').find('[name=' + $(this).attr("name") + ']').on("change dp.change", update); }) }); el.find("input[data-required-if], select[data-required-if], textarea[data-required-if]").each(function () { var dependent = $(this), dependency = $($(this).attr("data-required-if")), update = function (ev) { var enabled = (dependency.attr("type") === 'checkbox' || dependency.attr("type") === 'radio') ? dependency.prop('checked') : !!dependency.val(); dependent.prop('required', enabled).closest('.form-group').toggleClass('required', enabled).find('.optional').stop().animate({ 'opacity': enabled ? 0 : 1 }, ev ? 500 : 1); }; update(); dependency.closest('.form-group').find('input[name=' + dependency.attr("name") + ']').on("change", update); dependency.closest('.form-group').find('input[name=' + dependency.attr("name") + ']').on("dp.change", update); }); el.find("div.scrolling-choice:not(.no-search)").each(function () { if ($(this).find("input[type=text]").length > 0) { return; } var $menu = $("
").addClass("choice-options-menu"); var $inp_search = $("").addClass("form-control").attr("type", "text").attr("placeholder", gettext("Search query")); $menu.append($inp_search); $(this).prepend($menu); $inp_search.on("keyup change", function (e) { var term = $inp_search.val().toLowerCase(); $(this).closest(".scrolling-choice").find("div.radio").each(function () { $(this).toggleClass("hidden", !$(this).text().toLowerCase().includes(term)); }) }) }); el.find("div.scrolling-multiple-choice").each(function () { if ($(this).find(".choice-options-all").length > 0) { return; } var $menu = $("
").addClass("choice-options-menu"); var $a_all = $("").addClass("choice-options-all").attr("href", "#").text(gettext("All")); var $a_none = $("").addClass("choice-options-none").attr("href", "#").text(gettext("None")); var $inp_search = $("").addClass("form-control").attr("type", "text").attr("placeholder", gettext("Search query")); var $lbl_tgl = $("
").addClass("checkbox menu-checkbox"); var $cb_tgl = $("").attr("type", "checkbox").addClass("menu-checkbox"); $lbl_tgl.append($("
").text(res.text).html()) ); if (res.event) { $ret.append( $("").addClass("secondary").append( $("").addClass("fa fa-calendar fa-fw") ).append(" ").append($("
").text(res.event).html()) ); } return $ret; }, }).on("select2:select", function () { // Allow continuing to select if ($s[0].hasAttribute("multiple")) { window.setTimeout(function () { $s.parent().find('.select2-search__field').focus(); }, 50); } }); if ($s[0].hasAttribute("data-close-on-clear")) { $s.on("select2:clear", function () { window.setTimeout(function () { $s.select2('close'); }, 50); }); } }); el.find('[data-model-select2=event]').each(function () { var $s = $(this); $s.select2({ closeOnSelect: !this.hasAttribute('multiple'), theme: "bootstrap", delay: 100, allowClear: !$s.prop("required"), width: '100%', language: $("body").attr("data-select2-locale"), ajax: { url: $(this).attr('data-select2-url'), data: function (params) { return { query: params.term, page: params.page || 1 } } }, placeholder: $(this).attr("data-placeholder") || "", templateResult: function (res) { if (!res.id) { return res.text; } var $ret = $("").append( $("").addClass("event-name-full").append($("
").text(res.name).html()) ); if (res.organizer) { $ret.append( $("").addClass("event-organizer").append( $("").addClass("fa fa-users fa-fw") ).append(" ").append($("
").text(res.organizer).html()) ); } $ret.append( $("").addClass("event-daterange").append( $("").addClass("fa fa-calendar fa-fw") ).append(" ").append(res.date_range) ); return $ret; }, }).on("select2:select", function () { // Allow continuing to select window.setTimeout(function () { $s.parent().find('.select2-search__field').focus(); }, 50); }); }); el.find(".simple-subevent-choice").change(function () { $(this).closest("form").submit(); }); el.find("input[name=basics-slug]").bind("keyup keydown change", function () { $(this).closest(".form-group").find(".slug-length").toggle($(this).val().length > 16); }); el.find("script[data-replace-with-qr]").each(function () { var $div = $("
"); $div.insertBefore($(this)); $div.qrcode( { text: $(this).html(), correctLevel: 0, // M width: $(this).attr("data-size") ? parseInt($(this).attr("data-size")) : 256, height: $(this).attr("data-size") ? parseInt($(this).attr("data-size")) : 256, } ); }); el.find(".bulk-edit-field-group").each(function () { var $checkbox = $(this).find("input[type=checkbox][name=_bulk]"); var $content = $(this).find(".field-content"); var $fields = $content.find("input, select, textarea, button"); var $dialog = $(this).attr("data-confirm-dialog") ? $($(this).attr("data-confirm-dialog")) : null; var warningShown = false; if ($dialog) { $dialog.on("close", function () { if ($dialog.get(0).returnValue === "yes") { $checkbox.prop("checked", true); } else { $checkbox.prop("checked", false); warningShown = false; } update(); }); } var update = function () { var isChecked = $checkbox.prop("checked"); $content.toggleClass("enabled", isChecked); $fields.attr("tabIndex", isChecked ? 0 : -1); } $content.on("focusin change click", function () { if ($checkbox.prop("checked")) return; if ($dialog && !warningShown) { warningShown = true; $dialog.get(0).showModal(); } else { $checkbox.prop("checked", true); update(); } }); $checkbox.on('change', function () { var isChecked = $checkbox.prop("checked"); if (isChecked && $dialog && !warningShown) { warningShown = true; $dialog.get(0).showModal(); } else if (!isChecked) { warningShown = false; } update(); }) update(); }); el.find("input[name*=question], select[name*=question]").change(questions_toggle_dependent); questions_toggle_dependent(); questions_init_photos(el); var lastFocusedInput; $(document).on('focusin', 'input, textarea', function(e) { lastFocusedInput = e.target; }).on("click", function(e) { if (e.target.classList.contains('content-placeholder')) { var container = e.target.closest(".form-group"); if (!lastFocusedInput || !container.contains(lastFocusedInput)) { lastFocusedInput = container.querySelector("input, textarea"); //lastFocusedInput.selectionStart = lastFocusedInput.selectionEnd = lastFocusedInput.value.length; } if (lastFocusedInput) { var start = lastFocusedInput.selectionStart; var end = lastFocusedInput.selectionEnd; var v = lastFocusedInput.value; var p = e.target.textContent; var phStart = /\{\w*$/.exec(v.substring(0, start)); var phEnd = /^\w*\}/.exec(v.substring(end)); if (phStart) { start -= phStart[0].length } if (phEnd) { end += phEnd[0].length; } lastFocusedInput.value = v.substring(0, start) + p + v.substring(end); lastFocusedInput.selectionStart = start; lastFocusedInput.selectionEnd = start + p.length lastFocusedInput.focus(); } } }); }; function setup_basics(el) { el.find("#sumtoggle").find("button").click(function () { $(".table-product-overview .sum-gross").toggle($(this).attr("data-target") === ".sum-gross"); $(".table-product-overview .sum-net").toggle($(this).attr("data-target") === ".sum-net"); $(".table-product-overview .count").toggle($(this).attr("data-target") === ".count"); $("#sumtoggle").find("button").not($(this)).removeClass("active"); $(this).addClass("active"); }); el.find('.collapsible').collapse(); el.find("input[data-toggle=radiocollapse]").change(function () { $($(this).attr("data-parent")).find(".collapse.in").collapse('hide'); $($(this).attr("data-target")).collapse('show'); }); el.find("div.collapsed").removeClass("collapsed").addClass("collapse"); el.find(".has-error, .panel-body .alert-danger:not(:has(.has-error))").each(function () { var $this = $(this); var $panel = $this.closest("div.panel-collapse").collapse("show"); var alert = el.find(".alert-danger").get(0); if (alert === this) { return; } var label = ""; var description = ""; var scrollTarget = null; if ($this.hasClass('alert-danger')) { // just a general error messages without a actual errorenous input label = $this.closest('.panel').find('.panel-title').contents().filter(function() { return this.nodeType == Node.TEXT_NODE; }).text() description = $this.text(); scrollTarget = $this.closest('.panel').get(0); if (!scrollTarget.id) { scrollTarget.id = "panel_" + $("input", scrollTarget).attr("id"); } } else { label = $("label", this).first().contents().filter(function () { return this.nodeType != Node.ELEMENT_NODE || !this.classList.contains("optional") }).text(); description = $(".help-block", this).first().text(); scrollTarget = $(":input", this).get(0); } if (!alert || !scrollTarget) { return; } $('
  • ' + $.trim(label) + ' – ' + description + '
  • ') .appendTo(alert.querySelector("ul") || $("