Files
pretix_cgo/src/pretix/static/pretixcontrol/js/ui/main.js
luelista 5962536a11 Dialog for cart renewal, async task without page refresh (#5148)
* async_task: deduplicate response handling code

* extend cart without full page reload

* update dialog markup

* fix error response from CartExtend

* refactor asynctask, make sure waitingDialog.show() re-initializes dialog contents

* add cart expiry notification

* add aria references to other dialogs

* improve error handling

* fix error if max_extend=None

* different message for expiring soon and expired carts

* refactor dialog css

* add classes to further dialog elements

* switch extend-cart-dialog and loadingmodal to <dialog>

* Backport simple_block_tag from Django 5.2

* Use simple_block_tag for {% dialog %} tag

* add alertdialog role

* Update src/pretix/static/pretixbase/scss/_dialogs.scss

Co-authored-by: Richard Schreiber <schreiber@rami.io>

* fix mobile dialog styles not being overwritten

* asynctask dialog: prevent close by escape on chrome

* remove dynamic aria-live from #cart-deadline

dynamic aria-live is generally not well supported and as we have the dialog now anyways, we can remove it

* move continue-button to right

* Update src/pretix/static/pretixpresale/js/ui/cart.js

Co-authored-by: Richard Schreiber <schreiber@rami.io>

* Fix CSS for old-style dialog

* fix heading display/level

* align dialogs at the top as they originally were

* fix </div> from merge-conflict

* fix missing grow for dialog-content

* improve cart-extend-button ui

* do not show cart-extend-dialog onload

* improve message if 0 minutes

* do not save messae in session if ajax_dont_redirect

* add ajax_dont_redirect to async_task_check_url

* improve draw_deadline to only update #cart-deadline if necessary

* add renew-confirmation-message

---------

Co-authored-by: Richard Schreiber <schreiber@rami.io>
Co-authored-by: Raphael Michel <michel@rami.io>
2025-05-27 07:17:50 +02:00

1080 lines
43 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*global $,gettext*/
function gettext(msgid) {
if (typeof django !== 'undefined' && typeof django.gettext !== 'undefined') {
return django.gettext(msgid);
}
return msgid;
}
function ngettext(singular, plural, count) {
if (typeof django !== 'undefined' && typeof django.ngettext !== 'undefined') {
return django.ngettext(singular, plural, count);
}
return plural;
}
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.indexOf("<!-- pretix-login-marker -->") !== -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));
});
// 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("<div class='help-block contrast-state'></div>");
}
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("<span class='fa fa-fw " + icon + "'></span>")
.append(text);
$note.removeClass("text-success").removeClass("text-danger").addClass(cls);
} else {
$icon.html("<span class='fa fa-fw " + icon + " " + cls + "'></span>")
$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);
}
};
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]").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.attr("data-disable-dependent")) {
$toggling.attr('disabled', !enabled).trigger("change");
}
if (dependent.get(0).tagName.toLowerCase() !== "div") {
$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 = $("<div>").addClass("choice-options-menu");
var $inp_search = $("<input>").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 = $("<div>").addClass("choice-options-menu");
var $a_all = $("<a>").addClass("choice-options-all").attr("href", "#").text(gettext("All"));
var $a_none = $("<a>").addClass("choice-options-none").attr("href", "#").text(gettext("None"));
var $inp_search = $("<input>").addClass("form-control").attr("type", "text").attr("placeholder", gettext("Search query"));
var $lbl_tgl = $("<div>").addClass("checkbox menu-checkbox");
var $cb_tgl = $("<input>").attr("type", "checkbox").addClass("menu-checkbox");
$lbl_tgl.append($("<label>").append($cb_tgl).append(gettext("Selected only")));
$menu.append($("<span>").append($a_all).append(" / ").append($a_none))
if (!$(this).is(".no-search")) {
$menu.append($inp_search);
$menu.append($lbl_tgl);
}
$(this).prepend($menu);
$(this).find(".choice-options-none").click(function (e) {
$(this).closest(".scrolling-multiple-choice").find("input[type=checkbox]:not(.menu-checkbox)").prop("checked", false);
$cb_tgl.trigger("change");
e.preventDefault();
return false;
});
$(this).find(".choice-options-all").click(function (e) {
$(this).closest(".scrolling-multiple-choice").find("input[type=checkbox]:not(.menu-checkbox)").prop("checked", true);
$cb_tgl.trigger("change");
e.preventDefault();
return false;
});
$cb_tgl.on("change", function (e) {
var tgl = $cb_tgl.prop("checked");
$(this).closest(".scrolling-multiple-choice").find("div.checkbox:not(.menu-checkbox)").each(function () {
$(this).toggleClass("sr-only", tgl && !$(this).find("input[type=checkbox]").prop("checked"));
})
});
$inp_search.on("keyup change", function (e) {
var term = $inp_search.val().toLowerCase();
$(this).closest(".scrolling-multiple-choice").find("div.checkbox:not(.menu-checkbox)").each(function () {
$(this).toggleClass("hidden", !$(this).text().toLowerCase().includes(term));
})
})
});
el.find('.select2-static').select2({
theme: "bootstrap",
language: $("body").attr("data-select2-locale"),
});
el.find('[data-model-select2=json_script]').each(function() {
const selectedValue = this.value;
this.replaceChildren();
$(this).select2({
theme: "bootstrap",
language: $("body").attr("data-select2-locale"),
data: JSON.parse($(this.getAttribute('data-select2-src')).text()),
}).val(selectedValue).trigger('change');
});
el.find('input[data-typeahead-url]').each(function () {
var $inp = $(this);
if ($inp.data("ttTypeahead") || $inp.hasClass("tt-hint")) {
// Already initialized on this element
return;
}
$inp.typeahead(null, {
minLength: 1,
highlight: true,
source: new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
remote: {
url: $inp.attr("data-typeahead-url"),
prepare: function (query, settings) {
var sep = (settings.url.indexOf('?') > 0) ? '&' : '?';
settings.url = settings.url + sep + 'q=' + encodeURIComponent(query);
return settings;
},
transform: function (object) {
var results = object.results;
var suggs = [];
var reslen = results.length;
for (var i = 0; i < reslen; i++) {
suggs.push(results[i]);
}
return suggs;
}
}
}),
display: function (obj) {
return obj.name;
},
});
});
el.find('[data-model-select2=generic]').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"),
placeholder: $(this).attr("data-placeholder"),
ajax: {
url: $(this).attr('data-select2-url'),
data: function (params) {
return {
query: params.term,
page: params.page || 1
}
}
},
templateResult: function (res) {
if (!res.id) {
return res.text;
}
var $ret = $("<span>").append(
$(res.inactive ? "<strike class='text-muted'>" : "<span>").addClass("primary").append($("<div>").text(res.text).html())
);
if (res.event) {
$ret.append(
$("<span>").addClass("secondary").append(
$("<span>").addClass("fa fa-calendar fa-fw")
).append(" ").append($("<div>").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 = $("<span>").append(
$("<span>").addClass("event-name-full").append($("<div>").text(res.name).html())
);
if (res.organizer) {
$ret.append(
$("<span>").addClass("event-organizer").append(
$("<span>").addClass("fa fa-users fa-fw")
).append(" ").append($("<div>").text(res.organizer).html())
);
}
$ret.append(
$("<span>").addClass("event-daterange").append(
$("<span>").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>");
$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 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;
$checkbox.prop("checked", true);
update();
});
$checkbox.on('change', 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;
}
$('<li><a href="#' + scrollTarget.id + '">' + $.trim(label) + '</a> ' + description + '</li>')
.appendTo(alert.querySelector("ul") || $("<ul>").appendTo(alert))
.find("a").on("click", function(e) {
$panel.collapse("show");
var tab = scrollTarget.closest(".tab-pane");
if (tab) {
$(".nav-tabs a[href='#" + tab.id + "']").click();
}
scrollTarget.scrollIntoView();
scrollTarget.focus();
});
});
el.find('[data-toggle="tooltip"]').tooltip();
el.find('[data-toggle="tooltip_html"]').tooltip({
'html': true,
'whiteList': {
// Global attributes allowed on any supplied element below.
'*': ['class', 'dir', 'id', 'lang', 'role'],
b: [],
br: [],
code: [],
div: [], // required for template
h3: ['class', 'role'], // required for template
i: [],
small: [],
span: [],
strong: [],
u: [],
}
});
el.find('a.pagination-selection').click(function (e) {
e.preventDefault();
var max = parseInt($(this).data("max"))
var inp = prompt(gettext("Enter page number between 1 and %(max)s.").replace("%(max)s", max));
if (inp) {
if (!parseInt(inp) || parseInt(inp) < 1 || parseInt(inp) > max) {
alert(gettext("Invalid page number."));
} else {
location.href = $(this).attr("data-href").replace("_PAGE_", inp);
}
}
});
var url = document.location.toString();
if (url.match('#')) {
$('.nav-tabs a[href="#' + url.split('#')[1] + '"]').tab('show');
}
el.find('a[data-toggle="tab"]').on('click', function (e) {
if (!$(this).closest(".tab-content").length) {
// only append hash if not inside a .panel
window.location.hash = this.hash;
}
});
// Event wizard
el.find("#event-slug-random-generate").click(function () {
var url = $(this).attr("data-rng-url");
$("#id_basics-slug").val("Generating...");
$.getJSON(url, function (data) {
$("#id_basics-slug").val(data.slug);
});
});
el.find(".qrcode-canvas").each(function () {
$(this).qrcode(
{
text: $.trim($($(this).attr("data-qrdata")).html())
}
);
});
el.find(".propagated-settings-box").find("input, textarea, select").not("[readonly]")
.attr("data-propagated-locked", "true").prop("readonly", true);
el.find(".propagated-settings-box button[data-action=unlink]").click(function (ev) {
var $box = $(this).closest(".propagated-settings-box");
$box.find("input[name=decouple]").val($(this).val());
$box.find("[data-propagated-locked]").prop("readonly", false);
$box.removeClass("locked").addClass("unlocked");
ev.preventDefault();
return true;
});
// Tables with bulk selection, e.g. subevent list
el.find("input[data-toggle-table]").each(function (ev) {
var $toggle = $(this);
var $actionButtons = $(".batch-select-actions button", this.form);
var countLabels = $("<span></span>").appendTo($actionButtons.filter(function () { return !$(this).closest(".dropdown-menu").length }));
var $table = $toggle.closest("table");
var $selectAll = $table.find(".table-select-all");
var $rows = $table.find("tbody tr");
var $checkboxes = $rows.find("td:first-child input[type=checkbox]");
var firstIndex, lastIndex, selectionChecked, onChangeSelectionHappened = false;
var updateSelection = function(a, b, checked) {
if (a > b) {
//[a, b] = [b, a];// ES6 not ready yet for pretix
var tmp = a;
a = b;
b = tmp;
}
for (var i = a; i <= b; i++) {
var checkbox = $checkboxes.get(i);
if (!checkbox.hasAttribute("data-inital")) checkbox.setAttribute("data-inital", checkbox.checked);
if (checked === undefined || checked === null) checkbox.checked = checkbox.getAttribute("data-inital") === "true";
else checkbox.checked = checked;
}
};
var onChangeSelection = function(ev) {
onChangeSelectionHappened = true;
var row = ev.target.closest("tr");
var currentIndex = 0;
while(row = row.previousSibling) {
if (row.tagName) currentIndex++;
}
var dCurrent = currentIndex - firstIndex;
var dLast = lastIndex - firstIndex;
if (dCurrent*dLast < 0) {
// direction of selection changed => reset all previously selected
updateSelection(lastIndex, firstIndex);
}
else if (Math.abs(dCurrent) < Math.abs(dLast)) {
// selection distance decreased => reset unselected
updateSelection(currentIndex, lastIndex);
}
lastIndex = currentIndex;
updateSelection(firstIndex, currentIndex, selectionChecked);
ev.preventDefault();
};
$table.on("pointerdown", function(ev) {
if (!ev.target.closest("td:first-child")) return;
var row = ev.target.closest("tr");
selectionChecked = !row.querySelector("td:first-child input").checked;
firstIndex = 0;
while(row = row.previousSibling) {
if (row.tagName) firstIndex++;
}
lastIndex = firstIndex;
ev.preventDefault();
$rows.on("pointerenter", onChangeSelection);
$(document).one("pointerup", function(ev) {
if (onChangeSelectionHappened) {
ev.preventDefault();
onChangeSelectionHappened = false;
$checkboxes.removeAttr("data-inital");
update();
}
$rows.off("pointerenter", onChangeSelection);
});
});
var update = function() {
var nrOfChecked = $checkboxes.filter(":checked").length;
var allChecked = nrOfChecked == $checkboxes.length;
if (!nrOfChecked) countLabels.empty();
else countLabels.text(" ("+nrOfChecked+")");
if (!allChecked) $selectAll.find("input").prop("checked", false);
$actionButtons.attr("disabled", !nrOfChecked);
$toggle.prop("checked", allChecked).prop("indeterminate", nrOfChecked > 0 && !allChecked);
$selectAll.toggleClass("hidden", nrOfChecked !== $checkboxes.length).prop("hidden", nrOfChecked !== $checkboxes.length);
}
$checkboxes.change(update);
$toggle.change(function (ev) {
this.indeterminate = false;
$checkboxes.prop("checked", this.checked);
update();
});
$selectAll.find("input").change(function(ev) {
if (this.checked) countLabels.text(" ("+this.getAttribute("data-results-total")+")");
else countLabels.text(" ("+$checkboxes.filter(":checked").length+")");
})
update();
});
// Items and categories
el.find(".internal-name-wrapper").each(function () {
if ($(this).find("input").val() === "") {
var $fg = $(this).find(".form-group");
$fg.hide();
var $fgl = $("<div>").addClass("form-group").append(
$("<div>").addClass("col-md-9 col-md-offset-3").append(
$("<div>").addClass("help-block").append(
$("<a>").attr("href", "#").text(
gettext("Use a different name internally")
).click(function () {
$fg.slideDown();
$fgl.slideUp();
return false;
})
)
)
);
$(this).append($fgl);
}
});
el.find("button[data-toggle=qrcode]").click(function (e) {
e.preventDefault();
var $current = $(".qr-code-overlay[data-qrcode='" + $(this).attr("data-qrcode") + "']");
if ($current.length) {
$(".qr-code-overlay").attr("data-qrcode", "").slideUp(200);
return false;
}
$(".qr-code-overlay").remove();
var $div = $("<div>").addClass("qr-code-overlay").attr("data-qrcode", $(this).attr("data-qrcode"));
$div.appendTo($("body"));
var offset = $(this).offset();
$div.css("top", offset.top + $(this).outerHeight() + 10).css("left", offset.left);
var $child = $("<div>");
$child.appendTo($div);
$child.qrcode(
{
text: $(this).attr("data-qrcode"),
correctLevel: 0, // M
width: 196,
height: 196
}
);
var $inner = $("<div>").text($(this).attr("data-qrcode").slice(0, 10) + "…");
$inner.append($("<btn>").addClass("btn btn-link btn-xs btn-clipboard").attr("data-clipboard-text", $(this).attr("data-qrcode")).append(
$("<span>").addClass("fa fa-clipboard").attr("aria-hidden", "true")
))
$div.append($inner.get(0).innerHTML + "<br>");
$div.append(gettext("Click to close"));
$div.slideDown(200);
$div.click(function (e) {
if ($(e.target).closest(".btn").length) {
return;
}
$(".qr-code-overlay").attr("data-qrcode", "").slideUp(200);
});
return false;
});
}
$(function () {
"use strict";
$("body").removeClass("nojs");
lightbox.init();
$(document).on("click", ".variations .variations-select-all", function (e) {
$(this).parent().parent().find("input[type=checkbox]").prop("checked", true).change();
e.stopPropagation();
return false;
});
$(document).on("click", ".variations .variations-select-none", function (e) {
$(this).parent().parent().find("input[type=checkbox]").prop("checked", false).change();
e.stopPropagation();
return false;
});
if ($(".items-on-quota").length) {
$(".items-on-quota .panel").each(function () {
var $panel = $(this);
$panel.toggleClass("panel-success", $panel.find("input:checked").length > 0);
$(this).find("input").change(function () {
$panel.toggleClass("panel-success", $panel.find("input:checked").length > 0);
});
});
}
setup_basics($("body"));
form_handlers($("body"));
$(document).trigger("pretix:bind-forms");
$("#ajaxerr").on("click", ".ajaxerr-close", ajaxErrDialog.hide);
moment.locale($("body").attr("data-datetimelocale"));
add_log_expand_handlers($("body"));
});
function add_log_expand_handlers(el) {
el.find("a[data-expandlogs], a[data-expandrefund], a[data-expandpayment]").click(function (e) {
e.preventDefault();
var $a = $(this);
var id = $(this).attr("data-id");
$a.find(".fa").removeClass("fa-eye").addClass("fa-cog fa-spin");
var url = '/control/logdetail/';
if ($a.is("[data-expandrefund]")) {
url += 'refund/'
} else if ($a.is("[data-expandpayment]")) {
url += 'payment/'
}
function format_data(data) {
return Object.entries(data).map(([key, value]) =>
$("<div>").append(
$("<b>").text(key + ': '),
$("<span>").text(JSON.stringify(value, null, 2))));
}
$.getJSON(url + '?pk=' + id, function (data) {
if ($a.parent().tagName === "p") {
$("<pre>").append(format_data(data)).insertAfter($a.parent());
} else {
$("<pre>").append(format_data(data)).appendTo($a.parent());
}
$a.remove();
});
return false;
});
}
$(function () {
$('form[method=post]').filter(function () {
return $(this).find("button:not([type=button]), input[type=submit]").length > 0;
}).areYouSure( {'message': gettext('You have unsaved changes!')});
});