Files
pretix_cgo/src/pretix/static/pretixcontrol/js/ui/main.js
Richard Schreiber 0de96ed066 Add links to invalid inputs on error alert (Z#23149061) (#4114)
* Add links to invalid inputs on error alert

* add errors in sub-forms to message, fix issues with multi-checkboxes labels and inputs

* add scrollTarget.scrollIntoView

* add missing semi-colon

* improve comment

* add style for links in alert-danger

* fix link color for all alert-boxes

* Update src/pretix/static/pretixcontrol/js/ui/main.js

---------

Co-authored-by: Raphael Michel <michel@rami.io>
2024-04-30 10:18:32 +02:00

1106 lines
44 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 waitingDialog = {
show: function (message) {
"use strict";
$("#loadingmodal").find("h1").html(message);
$("body").addClass("loading");
},
hide: function () {
"use strict";
$("body").removeClass("loading");
}
};
var ajaxErrDialog = {
show: function (c) {
"use strict";
$("#ajaxerr").html(c);
$("#ajaxerr .links").html("<a class='btn btn-default ajaxerr-close'>"
+ gettext("Close message") + "</a>");
$("body").addClass("ajaxerr");
},
hide: function () {
"use strict";
$("body").removeClass("ajaxerr");
}
};
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);
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() === "") {
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 luminanace(r, g, b) {
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) {
var l1 = luminanace(rgb1[0], rgb1[1], rgb1[2]) + 0.05,
l2 = luminanace(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) {
var rgb = $(this).colorpicker('color').toRGB();
var c = contrast([255,255,255], [rgb.r, rgb.g, rgb.b]);
var mark = 'times';
if ($(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();
}
if (c > 7) {
$note.html("<span class='fa fa-fw fa-check-circle'></span>")
.append(gettext('Your color has great contrast and is very easy to read!'));
$note.addClass("text-success").removeClass("text-warning").removeClass("text-danger");
} else if (c > 2.5) {
$note.html("<span class='fa fa-fw fa-info-circle'></span>")
.append(gettext('Your color has decent contrast and is probably good-enough to read!'));
$note.removeClass("text-success").removeClass("text-warning").removeClass("text-danger");
} else {
$note.html("<span class='fa fa-fw fa-warning'></span>")
.append(gettext('Your color has bad contrast for text on white background, please choose a darker ' +
'shade.'));
$note.addClass("text-danger").removeClass("text-success").removeClass("text-warning");
}
});
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.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.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.closest('.form-group').find('[name=' + dependency.attr("name") + ']').on("change", update);
dependency.closest('.form-group').find('[name=' + dependency.attr("name") + ']').on("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);
});
$("input[name$=vat_id][data-countries-with-vat-id]").each(function () {
var dependent = $(this),
dependency_country = $(this).closest(".panel-body, form").find('select[name$=country]'),
dependency_id_is_business_1 = $(this).closest(".panel-body, form").find('input[id$=id_is_business_1]'),
update = function (ev) {
if (dependency_id_is_business_1.length && !dependency_id_is_business_1.prop("checked")) {
dependent.closest(".form-group").hide();
} else if (dependent.attr('data-countries-with-vat-id').split(',').includes(dependency_country.val())) {
dependent.closest(".form-group").show();
} else {
dependent.closest(".form-group").hide();
}
};
update();
dependency_country.on("change", update);
dependency_id_is_business_1.on("change", update);
});
$("select[name$=state]:not([data-static])").each(function () {
var dependent = $(this),
counter = 0,
dependency = $(this).closest(".panel-body, form").find('select[name$=country]'),
update = function (ev) {
counter++;
var curCounter = counter;
dependent.prop("disabled", true);
dependency.closest(".form-group").find("label").prepend("<span class='fa fa-cog fa-spin'></span> ");
$.getJSON('/js_helpers/states/?country=' + dependency.val(), function (data) {
if (counter > curCounter) {
return; // Lost race
}
dependent.find("option").filter(function (t) {return !!$(this).attr("value")}).remove();
if (data.data.length > 0) {
$.each(data.data, function (k, s) {
dependent.append($("<option>").attr("value", s.code).text(s.name));
});
dependent.closest(".form-group").show();
dependent.prop('required', dependency.prop("required"));
} else {
dependent.closest(".form-group").hide();
dependent.prop("required", false);
}
dependent.prop("disabled", false);
dependency.closest(".form-group").find("label .fa-spin").remove();
});
};
if (dependent.find("option").length === 1) {
dependent.closest(".form-group").hide();
} else {
dependent.prop('required', dependency.prop("required"));
}
dependency.on("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('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);
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().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/'
}
$.getJSON(url + '?pk=' + id, function (data) {
if ($a.parent().tagName === "p") {
$("<pre>").text(JSON.stringify(data.data, null, 2)).insertAfter($a.parent());
} else {
$("<pre>").text(JSON.stringify(data.data, null, 2)).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!')});
});