Allow dependencies between questions (#1202)

- [x] data model
- [x] api
- [x] backend editor
- [x] backend validation logic
- [x] frontend display logic
- [x] frontend validation logic
- [x] test checkout step
- [x] test modify order in frontend
- [x] test modify order in backend
- [x] validation tests
- [x] correctly evaluate dependency tree in frontend?
- [x] copy events
This commit is contained in:
Raphael Michel
2019-03-13 16:49:20 +01:00
committed by GitHub
parent d10cbd07a7
commit f95e8f374d
22 changed files with 825 additions and 211 deletions

View File

@@ -1,13 +1,5 @@
/*global $,gettext*/
function question_page_toggle_view() {
var show = $("#id_type").val() == "C" || $("#id_type").val() == "M";
$("#answer-options").toggle(show);
show = $("#id_type").val() == "B" && $("#id_required").prop("checked");
$(".alert-required-boolean").toggle(show);
}
var waitingDialog = {
show: function (message) {
"use strict";
@@ -34,6 +26,26 @@ var ajaxErrDialog = {
}
};
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');
@@ -423,6 +435,9 @@ var form_handlers = function (el) {
}
);
});
el.find("input[name*=question], select[name*=question]").change(questions_toggle_dependent);
questions_toggle_dependent();
};
$(function () {
@@ -491,14 +506,6 @@ $(function () {
window.location.hash = e.target.hash;
});
// Question editor
if ($("#answer-options").length) {
$("#id_type").change(question_page_toggle_view);
$("#id_required").change(question_page_toggle_view);
question_page_toggle_view();
}
// Event wizard
$("#event-slug-random-generate").click(function () {
var url = $(this).attr("data-rng-url");

View File

@@ -1,5 +1,6 @@
/*global $, Morris, gettext*/
$(function () {
// Question view
if (!$("#question-stats").length) {
return;
}
@@ -73,3 +74,66 @@ $(function () {
// N, S, T
});
$(function () {
// Question editor
if (!$("#answer-options").length) {
return;
}
// Question editor
$("#id_type").change(question_page_toggle_view);
$("#id_required").change(question_page_toggle_view);
question_page_toggle_view();
function question_page_toggle_view() {
var show = $("#id_type").val() == "C" || $("#id_type").val() == "M";
$("#answer-options").toggle(show);
show = $("#id_type").val() == "B" && $("#id_required").prop("checked");
$(".alert-required-boolean").toggle(show);
}
var $val = $("#id_dependency_value");
var $dq = $("#id_dependency_question");
var oldval = $("#dependency_value_val").text();
function update_dependency_options() {
$val.parent().find(".loading-indicator").remove();
$("#id_dependency_value option").remove();
$("#id_dependency_value").prop("required", false);
var val = $dq.children("option:selected").val();
if (!val) {
$("#id_dependency_value").show();
$val.show();
return;
}
$("#id_dependency_value").prop("required", true);
$val.hide();
$val.parent().append("<div class=\"help-block loading-indicator\"><span class=\"fa" +
" fa-cog fa-spin\"></span></div>");
apiGET('/api/v1/organizers/' + $("body").attr("data-organizer") + '/events/' + $("body").attr("data-event") + '/questions/' + val + '/', function (data) {
if (data.type === "B") {
$val.append($("<option>").attr("value", "True").text(gettext("Ja")));
$val.append($("<option>").attr("value", "False").text(gettext("Nein")));
} else {
for (var i = 0; i < data.options.length; i++) {
var opt = data.options[i];
var $opt = $("<option>").attr("value", opt.identifier).text(i18nToString(opt.answer));
$val.append($opt);
}
}
if (oldval) {
$val.val(oldval);
}
$val.parent().find(".loading-indicator").remove();
$val.show();
});
}
update_dependency_options();
$dq.change(update_dependency_options);
});

View File

@@ -6,6 +6,7 @@ function gettext(msgid) {
}
return msgid;
}
function ngettext(singular, plural, count) {
if (typeof django !== 'undefined' && typeof django.ngettext !== 'undefined') {
return django.ngettext(singular, plural, count);
@@ -14,7 +15,7 @@ function ngettext(singular, plural, count) {
}
var form_handlers = function (el) {
el.find(".datetimepicker").each(function() {
el.find(".datetimepicker").each(function () {
$(this).datetimepicker({
format: $("body").attr("data-datetimeformat"),
locale: $("body").attr("data-datetimelocale"),
@@ -37,7 +38,7 @@ var form_handlers = function (el) {
}
});
el.find(".datepickerfield").each(function() {
el.find(".datepickerfield").each(function () {
var opts = {
format: $("body").attr("data-dateformat"),
locale: $("body").attr("data-datetimelocale"),
@@ -71,7 +72,7 @@ var form_handlers = function (el) {
}
});
el.find(".timepickerfield").each(function() {
el.find(".timepickerfield").each(function () {
var opts = {
format: $("body").attr("data-timeformat"),
locale: $("body").attr("data-datetimelocale"),
@@ -90,7 +91,7 @@ var form_handlers = function (el) {
}
};
$(this).datetimepicker(opts);
});
});
el.find("script[data-replace-with-qr]").each(function () {
var $div = $("<div>");
@@ -104,7 +105,10 @@ var form_handlers = function (el) {
}
);
});
}
el.find("input[name*=question], select[name*=question]").change(questions_toggle_dependent);
questions_toggle_dependent();
};
$(function () {
@@ -130,7 +134,7 @@ $(function () {
$("#voucher-box").slideDown();
$("#voucher-toggle").slideUp();
});
$('[data-toggle="tooltip"]').tooltip();
$("#ajaxerr").on("click", ".ajaxerr-close", ajaxErrDialog.hide);
@@ -140,7 +144,7 @@ $(function () {
$('.toggle-variation-description').click(function () {
$(this).parent().find('.addon-variation-description').slideToggle();
});
// Copy answers
$(".js-copy-answers").click(function (e) {
e.preventDefault();
@@ -153,7 +157,7 @@ $(function () {
// Subevent choice
if ($(".subevent-toggle").length) {
$(".subevent-list").hide();
$(".subevent-toggle").css("display", "block").click(function() {
$(".subevent-toggle").css("display", "block").click(function () {
$(".subevent-list").slideToggle(300);
});
}
@@ -165,7 +169,7 @@ $(function () {
var update_cart_form = function () {
var is_enabled = $(".product-row input[type=checkbox]:checked, .variations input[type=checkbox]:checked, .product-row input[type=radio]:checked, .variations input[type=radio]:checked").length;
if (!is_enabled) {
$(".input-item-count").each(function() {
$(".input-item-count").each(function () {
if ($(this).val() && $(this).val() !== "0") {
is_enabled = true;
}
@@ -185,18 +189,18 @@ $(function () {
// Invoice address form
$("input[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();
if (!dependent.is("[data-no-required-attr]")) {
dependent.prop('required', enabled);
}
dependent.closest('.form-group').toggleClass('required', enabled);
};
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);
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();
if (!dependent.is("[data-no-required-attr]")) {
dependent.prop('required', enabled);
}
dependent.closest('.form-group').toggleClass('required', enabled);
};
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[data-display-dependency]").each(function () {
@@ -219,16 +223,16 @@ $(function () {
dependency.closest('.form-group').find('input[name=' + dependency.attr("name") + ']').on("dp.change", update);
});
form_handlers($("body"));
form_handlers($("body"));
// Lightbox
lightbox.init();
});
function copy_answers(idx) {
var elements = $('*[data-idx="'+idx+'"] input, *[data-idx="'+idx+'"] select, *[data-idx="'+idx+'"] textarea');
function copy_answers(idx) {
var elements = $('*[data-idx="' + idx + '"] input, *[data-idx="' + idx + '"] select, *[data-idx="' + idx + '"] textarea');
var firstAnswers = $('*[data-idx="0"] input, *[data-idx="0"] select, *[data-idx="0"] textarea');
elements.each(function(index){
elements.each(function (index) {
var input = $(this),
tagName = input.prop('tagName').toLowerCase(),
attributeType = input.attr('type'),
@@ -250,14 +254,20 @@ function copy_answers(idx) {
break;
case "checkbox":
case "radio":
input.prop("checked", firstAnswers.filter("[name$=" + suffix + "]").prop("checked"));
if (input.attr('value')) {
input.prop("checked", firstAnswers.filter("[name$=" + suffix + "][value=" + input.attr('value') + "]").prop("checked"));
} else {
input.prop("checked", firstAnswers.filter("[name$=" + suffix + "]").prop("checked"));
}
break;
default:
input.val(firstAnswers.filter("[name$=" + suffix + "]").val());
}
}
break;
default:
input.val(firstAnswers.filter("[name$=" + suffix + "]").val());
}
}
});
questions_toggle_dependent(true);
}

View File

@@ -0,0 +1,71 @@
/*global $ */
function questions_toggle_dependent(ev) {
function q_should_be_shown($el) {
if (!$el.attr('data-question-dependency')) {
return true;
}
var dependency_name = $el.attr("name").split("_")[0] + "_" + $el.attr("data-question-dependency");
var dependency_value = $el.attr("data-question-dependency-value");
var $dependency_el;
if ($("select[name=" + dependency_name + "]").length) {
// dependency is type C
$dependency_el = $("select[name=" + dependency_name + "]");
if (!$dependency_el.closest(".form-group").hasClass("dependency-hidden")) { // do not show things that depend on hidden things
return q_should_be_shown($dependency_el) && $dependency_el.val() === dependency_value;
}
} else if ($("input[type=checkbox][name=" + dependency_name + "]").length) {
// dependency type is B or M
if (dependency_value === "True" || dependency_value === "False") {
$dependency_el = $("input[name=" + dependency_name + "]");
if (!$dependency_el.closest(".form-group").hasClass("dependency-hidden")) { // do not show things that depend on hidden things
if (dependency_value === "True") {
return q_should_be_shown($dependency_el) && $dependency_el.prop('checked');
} else {
return q_should_be_shown($dependency_el) && !$dependency_el.prop('checked');
}
}
} else {
$dependency_el = $("input[value=" + dependency_value + "][name=" + dependency_name + "]");
if (!$dependency_el.closest(".form-group").hasClass("dependency-hidden")) { // do not show things that depend on hidden things
return q_should_be_shown($dependency_el) && $dependency_el.prop('checked');
}
}
}
}
$("[data-question-dependency]").each(function () {
var $dependent = $(this).closest(".form-group");
var is_shown = !$dependent.hasClass("dependency-hidden");
var should_be_shown = q_should_be_shown($(this));
if (should_be_shown && !is_shown) {
$dependent.stop().removeClass("dependency-hidden");
if (!ev) {
$dependent.show();
} else {
$dependent.slideDown();
}
$dependent.find("input.required-hidden, select.required-hidden, textarea.required-hidden").each(function () {
$(this).prop("required", true).removeClass("required-hidden");
});
} else if (!should_be_shown && is_shown) {
if ($dependent.hasClass("has-error") || $dependent.find(".has-error").length) {
// Do not hide things with invalid validation
return;
}
$dependent.stop().addClass("dependency-hidden");
if (!ev) {
$dependent.hide();
} else {
$dependent.slideUp();
}
$dependent.find("input[required], select[required], textarea[required]").each(function () {
$(this).prop("required", false).addClass("required-hidden");
});
}
});
}