/*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 = $("