From ec50d9dc6f47ed750c40c4d1c64efd9ddd393d46 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 3 Sep 2021 17:26:05 +0200 Subject: [PATCH] Improve condition creation in forms --- www/common/common-ui-elements.js | 6 +- www/form/inner.js | 995 +++++++++++++++++-------------- 2 files changed, 548 insertions(+), 453 deletions(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index ac24b1ab9..ccd7f139a 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -1579,10 +1579,14 @@ define([ }, 1000); }); - $container.setValue = function (val, name) { + $container.setValue = function (val, name, sync) { value = val; var $val = $innerblock.find('[data-value="'+val+'"]'); var textValue = name || $val.html() || val; + if (sync) { + $button.find('.cp-dropdown-button-title').html(textValue); + return; + } setTimeout(function () { $button.find('.cp-dropdown-button-title').html(textValue); }); diff --git a/www/form/inner.js b/www/form/inner.js index c30bc99d1..9498b3379 100644 --- a/www/form/inner.js +++ b/www/form/inner.js @@ -880,6 +880,21 @@ define([ return res; }; + var getFullOrder = function (content) { + var order = content.order.slice(); + getSections(content).forEach(function (uid) { + var block = content.form[uid]; + if (!block.opts || !Array.isArray(block.opts.questions)) { return; } + var idx = order.indexOf(uid); + if (idx === -1) { return; } + idx++; + block.opts.questions.forEach(function (el, i) { + order.splice(idx+i, 0, el); + }); + }); + return order; + }; + var getBlockAnswers = function (answers, uid, filterCurve) { if (!answers) { return; } return Object.keys(answers || {}).map(function (user) { @@ -1020,43 +1035,387 @@ define([ defaultOpts: { questions: [] }, - get: function (opts, a, n, ev, block) { + get: function (opts, a, n, ev, data) { var sortable = h('div.cp-form-section-sortable'); var tag = h('div.cp-form-section-edit', [ sortable ]); - if (APP.isEditor) { - if (!opts) { opts = block.opts = STATIC_TYPES.section.defaultOpts; } - Sortable.create(sortable, { - group: { - name: 'nested', - put: function (to, from, el) { - // Make sure sections dan't be dropped into other sections - return $(el).attr('data-type') !== 'section'; + if (!APP.isEditor) { + return { + tag: tag, + noViewMode: true + }; + } + + var block = data.block; + if (!opts) { opts = block.opts = STATIC_TYPES.section.defaultOpts; } + var content = data.content; + var uid = data.uid; + var form = content.form; + var framework = APP.framework; + var tmp = data.tmp; + + var getConditionsValues = function () { + var order = getFullOrder(content); + var blockIdx = order.indexOf(uid); + var blocks = order.slice(0, blockIdx); // Get all previous questions + var values = blocks.map(function(uid) { + var block = form[uid]; + var type = block.type; + if (['radio', 'checkbox'].indexOf(type) === -1) { return; } + var obj = { + uid: uid, + type: type, + q: block.q || Messages.form_default + }; + if (type === 'radio') { + obj.values = block.opts ? block.opts.values + : TYPES.radio.defaultOpts.values; + } + if (type === 'checkbox') { + obj.values = block.opts ? block.opts.values + : TYPES.checkbox.defaultOpts.values; + } + return obj; + }).filter(Boolean); + return values; + }; + Messages.form_conditional_add = "Add condition OR"; // XXX + Messages.form_conditional_addAnd = "Add condition AND"; // XXX + var addCondition = h('button.btn.btn-secondary', [ + h('i.fa.fa-plus'), + h('span', Messages.form_conditional_add) + ]); + var $addC = $(addCondition); + var getConditions; + var getAddAndButton = function ($container, rules) { + var btn = h('button.btn.btn-secondary.cp-form-add-and', [ + h('i.fa.fa-plus'), + h('span', Messages.form_conditional_addAnd) + ]); + var $b = $(btn).click(function () { + getConditions($container, true, rules, undefined, $b); + }); + $container.append(btn); + return $b; + }; + getConditions = function ($container, isNew, rules, condition, $btn) { + Messages.form_condition_q = "Choose a question"; // XXX + Messages.form_condition_v = "Choose a value"; // XXX + + condition = condition || {}; + condition.uid = condition.uid || Util.uid(); + + var content = h('div.cp-form-condition', { + 'data-uid': condition.uid, + }); + var $content = $(content); + + var qSelect, iSelect, vSelect; + var onChange = function (resetV) { + var w = block.opts.when = block.opts.when || []; + + if (qSelect) { condition.q = qSelect.getValue(); } + if (iSelect) { condition.is = Number(iSelect.getValue()); } + if (resetV) { delete condition.v; } + else if (vSelect) { condition.v = vSelect.getValue(); } + + if (isNew) { + if (!Array.isArray(rules)) { // new set of rules (OR) + rules = [condition]; + w.push(rules); + $btn = getAddAndButton($container, rules); + } else { + rules.push(condition); } + isNew = false; + } + + framework.localChange(); + }; + + onChange(); + + var values = getConditionsValues(); + var qOptions = values.map(function (obj) { + return { + tag: 'a', + attributes: { + 'class': 'cp-form-condition-question', + 'data-value': obj.uid, + 'href': '#', + }, + content: obj.q + }; + }); + var qConfig = { + text: Messages.form_condition_q, // Button initial text + options: qOptions, // Entries displayed in the menu + isSelect: true, + caretDown: true, + buttonCls: 'btn btn-secondary' + }; + qSelect = UIElements.createDropdown(qConfig); + qSelect[0].dropdown = qSelect; + $(qSelect).attr('data-drop', 'q'); + Messages.form_condition_is = 'is'; // XXX + Messages.form_condition_isnot = 'is not'; // XXX + Messages.form_condition_has = 'has'; // XXX + Messages.form_condition_hasnot = 'has not'; // XXX + + var isOn = !condition || condition.is !== 0; + var iOptions = [{ + tag: 'a', + attributes: { + 'data-value': 1, + 'href': '#', }, - direction: "vertical", - filter: "input, button, .CodeMirror, .cp-form-type-sort, .cp-form-block-type.editable", - preventOnFilter: false, - draggable: ".cp-form-block", - //forceFallback: true, - fallbackTolerance: 5, - onStart: function () { - var $container = $('div.cp-form-creator-content'); - $container.find('.cp-form-creator-add-inline').remove(); + content: Messages.form_condition_is + }, { + tag: 'a', + attributes: { + 'data-value': 0, + 'href': '#', }, - store: { - set: function (s) { - opts.questions = s.toArray(); - setTimeout(APP.framework.localChange); - if (APP.updateAddInline) { APP.updateAddInline(); } + content: Messages.form_condition_isnot + }]; + var iConfig = { + options: iOptions, // Entries displayed in the menu + isSelect: true, + caretDown: true, + buttonCls: 'btn btn-default' + }; + iSelect = UIElements.createDropdown(iConfig); + iSelect[0].dropdown = iSelect; + iSelect.setValue(isOn ? 1 : 0, undefined, true); + $(iSelect).attr('data-drop', 'i').hide(); + iSelect.onChange.reg(function () { onChange(); }); + + var remove = h('button.btn.btn-danger-alt.cp-condition-remove', [ + h('i.fa.fa-times.nomargin') + ]); + $(remove).on('click', function () { + var w = block.opts.when = block.opts.when || []; + var deleteRule = false; + if (rules.length === 1) { + var rIdx = w.indexOf(rules); + w.splice(rIdx, 1); + deleteRule = true; + } else { + var idx = rules.indexOf(condition); + rules.splice(idx, 1); + } + framework.localChange(); + framework._.cpNfInner.chainpad.onSettle(function () { + if (deleteRule) { + $content.closest('.cp-form-condition-rule').remove(); + return; } + $content.remove(); + }); + }); + + $content.append(qSelect).append(iSelect).append(remove); + if ($container.find('button.cp-form-add-and').length) { + $container.find('button.cp-form-add-and').before($content); + } else { + $container.append($content); + } + + var isChange; + qSelect.onChange.reg(function (prettyVal, val, init) { + qSelect.find('button').removeClass('btn-secondary') + .addClass('btn-default'); + onChange(!init); + $(iSelect).show(); + var res, type; + values.some(function (obj) { + if (String(obj.uid) === String(val)) { + res = obj.values; + type = obj.type; + return true; + } + }); + + var $selDiv = $(iSelect); + if (type === 'checkbox') { + $selDiv.find('[data-value="0"]').text(Messages.form_condition_hasnot); + $selDiv.find('[data-value="1"]').text(Messages.form_condition_has); + } else { + $selDiv.find('[data-value="0"]').text(Messages.form_condition_isnot); + $selDiv.find('[data-value="1"]').text(Messages.form_condition_is); + } + iSelect.setValue(iSelect.getValue(), undefined, true); + + $content.find('.cp-form-condition-values').remove(); + if (!res) { return; } + var vOptions = res.map(function (str) { + return { + tag: 'a', + attributes: { + 'class': 'cp-form-condition-value', + 'data-value': str, + 'href': '#', + }, + content: str + }; + }); + var vConfig = { + text: Messages.form_condition_v, // Button initial text + options: vOptions, // Entries displayed in the menu + //left: true, // Open to the left of the button + //container: $(type), + isSelect: true, + caretDown: true, + buttonCls: 'btn btn-secondary' + }; + vSelect = UIElements.createDropdown(vConfig); + vSelect[0].dropdown = vSelect; + vSelect.addClass('cp-form-condition-values').attr('data-drop', 'v'); + $content.append(vSelect).append(remove); + + vSelect.onChange.reg(function () { + vSelect.find('button').removeClass('btn-secondary') + .addClass('btn-default'); + $(remove).off('click').click(function () { + var w = block.opts.when = block.opts.when || []; + var deleteRule = false; + if (rules.length === 1) { + var rIdx = w.indexOf(rules); + w.splice(rIdx, 1); + deleteRule = true; + } else { + var idx = rules.indexOf(condition); + rules.splice(idx, 1); + } + framework.localChange(); + framework._.cpNfInner.chainpad.onSettle(function () { + if (deleteRule) { + $content.closest('.cp-form-condition-rule').remove(); + return; + } + $content.remove(); + }); + }).appendTo($content); + onChange(); + }); + + if (condition && condition.v && init) { + vSelect.setValue(condition.v, undefined, true); + vSelect.onChange.fire(condition.v, condition.v); + } + + if (!tmp || tmp.uid !== condition.uid) { return; } + if (tmp.type === 'v') { + vSelect.click(); + } + }); + if (condition && condition.q) { + qSelect.setValue(condition.q, undefined, true); + qSelect.onChange.fire(condition.q, condition.q, true); + } + + if (!tmp || tmp.uid !== condition.uid) { return; } + + if (tmp.type === 'q') { qSelect.click(); } + else if (tmp.type === 'i') { iSelect.click(); } + }; + Messages.form_conditional = "Only show this section when:"; // XXX + + var conditionalDiv = h('div.cp-form-conditional', [ + h('div.cp-form-conditional-hint', Messages.form_conditional), + addCondition + ]); + var $condition = $(conditionalDiv).prependTo(tag); + var redraw = function () { + var w = block.opts.when = block.opts.when || []; + w.forEach(function (rules) { + var rulesC = h('div.cp-form-condition-rule'); + var $rulesC = $(rulesC); + var $b = getAddAndButton($rulesC, rules); + rules.forEach(function (obj) { + getConditions($rulesC, false, rules, obj, $b); + }); + $addC.before($rulesC); // XXX + }); + }; + redraw(); + + $addC.click(function () { + var rulesC = h('div.cp-form-condition-rule'); + var $rulesC = $(rulesC); + getConditions($rulesC, true); + $addC.before($rulesC); + }); + if (getConditionsValues().length) { + $condition.show(); + } else { + $condition.hide(); + } + // XXX unreg these 2 events + evShowConditions.reg(function () { + if (getConditionsValues().length) { + $condition.show(); + } else { + $condition.hide(); + } + }); + evCheckConditions.reg(function (_uid) { + if (uid !== _uid) { return; } + // If our conditions are invalid, redraw them + if (getConditionsValues().length) { + $condition.show(); + } else { + $condition.hide(); + } + $condition.find('.cp-form-condition-rule').remove(); + redraw(); + }); + + var cursorGetter = function () { + var $activeDrop = $condition.find('.cp-dropdown-content:visible').first(); + if (!$activeDrop.length) { return; } + var uid = $activeDrop.closest('.cp-form-condition').attr('data-uid'); + var type = $activeDrop.closest('.cp-dropdown-container').attr('data-drop'); + var $btn = $activeDrop.closest('.cp-dropdown-container').find('button'); + var y = $btn && $btn.length && $btn[0].getBoundingClientRect().y; + return { + uid: uid, + type: type, + y: y + }; + }; + + Sortable.create(sortable, { + group: { + name: 'nested', + put: function (to, from, el) { + // Make sure sections dan't be dropped into other sections + return $(el).attr('data-type') !== 'section'; } - }); - } + }, + direction: "vertical", + filter: "input, button, .CodeMirror, .cp-form-type-sort, .cp-form-block-type.editable", + preventOnFilter: false, + draggable: ".cp-form-block", + //forceFallback: true, + fallbackTolerance: 5, + onStart: function () { + var $container = $('div.cp-form-creator-content'); + $container.find('.cp-form-creator-add-inline').remove(); + }, + store: { + set: function (s) { + opts.questions = s.toArray(); + setTimeout(APP.framework.localChange); + if (APP.updateAddInline) { APP.updateAddInline(); } + } + } + }); return { tag: tag, - noViewMode: true + noViewMode: true, + getCursor: cursorGetter, }; }, printResults: function () { return; }, @@ -2460,20 +2819,6 @@ define([ }); } }; - var getFullOrder = function (content) { - var order = content.order.slice(); - getSections(content).forEach(function (uid) { - var block = content.form[uid]; - if (!block.opts || !Array.isArray(block.opts.questions)) { return; } - var idx = order.indexOf(uid); - if (idx === -1) { return; } - idx++; - block.opts.questions.forEach(function (el, i) { - order.splice(idx+i, 0, el); - }); - }); - return order; - }; var checkResults = {}; var checkCondition = function (block) { @@ -2857,428 +3202,147 @@ define([ if (!full) { add = h('button.btn.cp-form-creator-inline-add', { title: Messages.tag_add - }, [ - h('i.fa.fa-plus.add-open'), - h('i.fa.fa-times.add-close') - ]); - var $b = $(buttons).hide(); - $(add).click(function () { - $b.toggle(); - $(add).toggleClass('displayed'); - }); - } - else { - $(add).append(h('span', Messages.tag_add)); - } - - var inlineCls = full ? '-full' : '-inline'; - return h('div.cp-form-creator-add'+inlineCls, [ - add, - buttons - ]); - - }; - - var updateAddInline = APP.updateAddInline = function () { - $container.find('.cp-form-creator-add-inline').remove(); - // Add before existing question - $container.find('.cp-form-block').each(function (i, el) { - var $el = $(el); - var uid = $el.attr('data-id'); - $el.before(getFormCreator(uid)); - }); - // Add to the end of a section - $container.find('.cp-form-section-sortable').each(function (i, el) { - var $el = $(el); - var uid = $el.closest('.cp-form-block').attr('data-id'); - $el.append(getFormCreator(uid, true)); - }); - }; - - - var elements = []; - var n = 1; // Question number - - var order = getFullOrder(content); - - order.forEach(function (uid, blockIdx) { - var block = form[uid]; - var type = block.type; - var model = TYPES[type] || STATIC_TYPES[type]; - var isStatic = Boolean(STATIC_TYPES[type]); - if (!model) { return; } - - var _answers, name; - if (type === 'poll') { - var metadataMgr = framework._.cpNfInner.metadataMgr; - var user = metadataMgr.getUserData(); - // If we are a participant, our results shouldn't be in the table but in the - // editable part: remove them from _answers - _answers = getBlockAnswers(APP.answers, uid, !editable && user.curvePublic); - name = user.name; - } - - var data = model.get(block.opts, _answers, name, evOnChange, block); - if (!data) { return; } - data.uid = uid; - if (answers && answers[uid] && data.setValue) { data.setValue(answers[uid]); } - - if (data.pageBreak && !editable) { - elements.push(data); - return; - } - if (data.noViewMode && !editable) { - elements.push(data); - return; - } - - Messages.form_required = "Required"; // XXX - var requiredTag; - if (block.opts && block.opts.required) { - requiredTag = h('span.cp-form-required-tag', Messages.form_required); - } - - var dragHandle; - var q = h('div.cp-form-block-question', [ - h('span.cp-form-block-question-number', (n++)+'.'), - h('span.cp-form-block-question-text', block.q || Messages.form_default), - requiredTag - ]); - // Static blocks don't have questions ("q" is not used) so we can decrement n - if (isStatic) { n--; } - - var editButtons, editContainer; - - APP.formBlocks.push(data); - - Messages.form_preview = "Preview:"; // XXX - var previewDiv = h('div.cp-form-preview', Messages.form_preview); - - Messages.form_required_answer = "Answer: "; - Messages.form_required_on = "required"; - Messages.form_required_off = "optional"; - // Required radio displayed only for types that have an "isEmpty" function - var requiredDiv, conditionalDiv; - if (APP.isEditor && !isStatic && data.isEmpty) { - if (!block.opts) { block.opts = TYPES[type].defaultOpts; } - var isRequired = Boolean(block.opts.required); - var radioOn = UI.createRadio('cp-form-required-'+uid, 'cp-form-required-on', - Messages.form_required_on, isRequired, { - input: { value: 1 }, - }); - var radioOff = UI.createRadio('cp-form-required-'+uid, 'cp-form-required-off', - Messages.form_required_off, !isRequired, { - input: { value: 0 }, - }); - var radioContainer = h('div.cp-form-required-radio', [ - h('span', Messages.form_required_answer), - radioOff, - radioOn - ]); - requiredDiv = h('div.cp-form-required', [ - radioContainer - ]); - $(radioContainer).find('input[type="radio"]').on('change', function() { - var val = $('input:radio[name="cp-form-required-'+uid+'"]:checked').val(); - val = Number(val) || 0; - block.opts.required = Boolean(val); - framework.localChange(); - framework._.cpNfInner.chainpad.onSettle(function () { - UI.log(Messages.saved); - }); - }); - } - - if (APP.isEditor && type === "section") { - var getConditionsValues = function () { - order = getFullOrder(content); - var blockIdx = order.indexOf(uid); - var blocks = order.slice(0, blockIdx); // Get all previous questions - var values = blocks.map(function(uid) { - var block = form[uid]; - var type = block.type; - if (['radio', 'checkbox'].indexOf(type) === -1) { return; } - var obj = { - uid: uid, - type: type, - q: block.q || Messages.form_default - }; - if (type === 'radio') { - obj.values = block.opts ? block.opts.values - : TYPES.radio.defaultOpts.values; - } - if (type === 'checkbox') { - obj.values = block.opts ? block.opts.values - : TYPES.checkbox.defaultOpts.values; - } - return obj; - }).filter(Boolean); - return values; - }; - Messages.form_conditional_add = "Add condition OR"; // XXX - Messages.form_conditional_addAnd = "Add condition AND"; // XXX - var addCondition = h('button.btn.btn-secondary', [ - h('i.fa.fa-plus'), - h('span', Messages.form_conditional_add) - ]); - var $addC = $(addCondition); - var getConditions; - var getAddAndButton = function ($container, rules) { - var btn = h('button.btn.btn-secondary.cp-form-add-and', [ - h('i.fa.fa-plus'), - h('span', Messages.form_conditional_addAnd) - ]); - $(btn).click(function () { - getConditions($container, true, rules); - }); - $container.append(btn); - }; - getConditions = function ($container, isNew, rules, condition) { - Messages.form_condition_q = "Choose a question"; // XXX - Messages.form_condition_v = "Choose a value"; // XXX - - var content = h('div.cp-form-condition'); - var $content = $(content); - var values = getConditionsValues(); - var qOptions = values.map(function (obj) { - return { - tag: 'a', - attributes: { - 'class': 'cp-form-condition-question', - 'data-value': obj.uid, - 'href': '#', - }, - content: obj.q - }; - }); - var qConfig = { - text: Messages.form_condition_q, // Button initial text - options: qOptions, // Entries displayed in the menu - isSelect: true, - caretDown: true, - buttonCls: 'btn btn-default' - }; - var qSelect = UIElements.createDropdown(qConfig); - Messages.form_condition_is = 'is'; // XXX - Messages.form_condition_isnot = 'is not'; // XXX - Messages.form_condition_has = 'has'; // XXX - Messages.form_condition_hasnot = 'has not'; // XXX - - var isOn = !condition || condition.is !== 0; - var iOptions = [{ - tag: 'a', - attributes: { - 'data-value': 1, - 'href': '#', - }, - content: Messages.form_condition_is - }, { - tag: 'a', - attributes: { - 'data-value': 0, - 'href': '#', - }, - content: Messages.form_condition_isnot - }]; - var iConfig = { - options: iOptions, // Entries displayed in the menu - isSelect: true, - caretDown: true, - buttonCls: 'btn btn-default' - }; - var iSelect = UIElements.createDropdown(iConfig); - iSelect.setValue(isOn ? 1 : 0); - $(iSelect).hide(); - - var remove = h('button.btn.btn-danger-alt.cp-condition-remove', [ - h('i.fa.fa-times.nomargin') - ]); - $(remove).on('click', function () { - $content.remove(); - if ($container.is(':empty')) { $container.remove(); } - $addC.show(); - }); + }, [ + h('i.fa.fa-plus.add-open'), + h('i.fa.fa-times.add-close') + ]); + var $b = $(buttons).hide(); + $(add).click(function () { + $b.toggle(); + $(add).toggleClass('displayed'); + }); + } + else { + $(add).append(h('span', Messages.tag_add)); + } - $content.append(qSelect).append(iSelect).append(remove); - if ($container.find('button.cp-form-add-and').length) { - $container.find('button.cp-form-add-and').before($content); - } else { - $container.append($content); - } + var inlineCls = full ? '-full' : '-inline'; + return h('div.cp-form-creator-add'+inlineCls, [ + add, + buttons + ]); - var isChange; - qSelect.onChange.reg(function (prettyVal, val, init) { - $(iSelect).show(); - var res, type; - values.some(function (obj) { - if (String(obj.uid) === String(val)) { - res = obj.values; - type = obj.type; - return true; - } - }); + }; - var $selDiv = $(iSelect); - if (type === 'checkbox') { - $selDiv.find('[data-value="0"]').text(Messages.form_condition_hasnot); - $selDiv.find('[data-value="1"]').text(Messages.form_condition_has); - } else { - $selDiv.find('[data-value="0"]').text(Messages.form_condition_isnot); - $selDiv.find('[data-value="1"]').text(Messages.form_condition_is); - } - iSelect.setValue(iSelect.getValue()); + var updateAddInline = APP.updateAddInline = function () { + $container.find('.cp-form-creator-add-inline').remove(); + // Add before existing question + $container.find('.cp-form-block').each(function (i, el) { + var $el = $(el); + var uid = $el.attr('data-id'); + $el.before(getFormCreator(uid)); + }); + // Add to the end of a section + $container.find('.cp-form-section-sortable').each(function (i, el) { + var $el = $(el); + var uid = $el.closest('.cp-form-block').attr('data-id'); + $el.append(getFormCreator(uid, true)); + }); + }; - $content.find('.cp-form-condition-values').remove(); - if (!res) { return; } - var vOptions = res.map(function (str) { - return { - tag: 'a', - attributes: { - 'class': 'cp-form-condition-value', - 'data-value': str, - 'href': '#', - }, - content: str - }; - }); - var vConfig = { - text: Messages.form_condition_v, // Button initial text - options: vOptions, // Entries displayed in the menu - //left: true, // Open to the left of the button - //container: $(type), - isSelect: true, - caretDown: true, - buttonCls: 'btn btn-default' - }; - var vSelect = UIElements.createDropdown(vConfig); - vSelect.addClass('cp-form-condition-values'); - $content.append(vSelect).append(remove); - var onChange = function () { - var w = block.opts.when = block.opts.when || []; + var elements = []; + var n = 1; // Question number - condition = condition || {}; - condition.q = val; - condition.is = Number(iSelect.getValue()); - condition.v = vSelect.getValue(); + var order = getFullOrder(content); - var wasNew = isNew; - if (isNew) { - if (!Array.isArray(rules)) { // new set of rules (OR) - rules = [condition]; - w.push(rules); - getAddAndButton($container, rules); - } else { - rules.push(condition); - } - isNew = false; - } + order.forEach(function (uid, blockIdx) { + var block = form[uid]; + var type = block.type; + var model = TYPES[type] || STATIC_TYPES[type]; + var isStatic = Boolean(STATIC_TYPES[type]); + if (!model) { return; } - framework.localChange(); - framework._.cpNfInner.chainpad.onSettle(function () { - UI.log(Messages.saved); - if (wasNew) { $addC.show(); } - }); + var _answers, name; + if (type === 'poll') { + var metadataMgr = framework._.cpNfInner.metadataMgr; + var user = metadataMgr.getUserData(); + // If we are a participant, our results shouldn't be in the table but in the + // editable part: remove them from _answers + _answers = getBlockAnswers(APP.answers, uid, !editable && user.curvePublic); + name = user.name; + } - }; + var data = model.get(block.opts, _answers, name, evOnChange, { + block: block, + content: content, + uid: uid, + tmp: temp && temp[uid] + }); + if (!data) { return; } + data.uid = uid; + if (answers && answers[uid] && data.setValue) { data.setValue(answers[uid]); } - if (isChange) { iSelect.onChange.unreg(isChange); } - isChange = function () { - if (!vSelect.getValue()) { return; } - onChange(); - }; - iSelect.onChange.reg(isChange); + if (data.pageBreak && !editable) { + elements.push(data); + return; + } + if (data.noViewMode && !editable) { + elements.push(data); + return; + } - vSelect.onChange.reg(function () { - $(remove).off('click').click(function () { - var w = block.opts.when = block.opts.when || []; - var deleteRule = false; - if (rules.length === 1) { - var rIdx = w.indexOf(rules); - w.splice(rIdx, 1); - deleteRule = true; - } else { - var idx = rules.indexOf(condition); - rules.splice(idx, 1); - } - framework.localChange(); - framework._.cpNfInner.chainpad.onSettle(function () { - if (deleteRule) { - $content.closest('.cp-form-condition-rule').remove(); - return; - } - $content.remove(); - }); - }).appendTo($content); - onChange(); - }); + Messages.form_required = "Required"; // XXX + var requiredTag; + if (block.opts && block.opts.required) { + requiredTag = h('span.cp-form-required-tag', Messages.form_required); + } - if (condition && condition.v && init) { - vSelect.setValue(condition.v); - vSelect.onChange.fire(condition.v, condition.v); - } + var dragHandle; + var q = h('div.cp-form-block-question', [ + h('span.cp-form-block-question-number', (n++)+'.'), + h('span.cp-form-block-question-text', block.q || Messages.form_default), + requiredTag + ]); + // Static blocks don't have questions ("q" is not used) so we can decrement n + if (isStatic) { n--; } - }); - if (condition && condition.q) { - qSelect.setValue(condition.q); // XXX check if exists? or integrity - qSelect.onChange.fire(condition.q, condition.q, true); - } + var editButtons, editContainer; + APP.formBlocks.push(data); - }; - Messages.form_conditional = "Only show this section when:"; // XXX + Messages.form_preview = "Preview:"; // XXX + var previewDiv = h('div.cp-form-preview', Messages.form_preview); - conditionalDiv = h('div.cp-form-conditional', [ - h('div.cp-form-conditional-hint', Messages.form_conditional), - addCondition - ]); - var $condition = $(conditionalDiv); - var redraw = function () { - var w = block.opts.when = block.opts.when || []; - w.forEach(function (rules) { - var rulesC = h('div.cp-form-condition-rule'); - var $rulesC = $(rulesC); - getAddAndButton($rulesC, rules); - rules.forEach(function (obj) { - getConditions($rulesC, false, rules, obj); + Messages.form_required_answer = "Answer: "; + Messages.form_required_on = "required"; + Messages.form_required_off = "optional"; + // Required radio displayed only for types that have an "isEmpty" function + var requiredDiv, conditionalDiv; + if (APP.isEditor && !isStatic && data.isEmpty) { + if (!block.opts) { block.opts = TYPES[type].defaultOpts; } + var isRequired = Boolean(block.opts.required); + var radioOn = UI.createRadio('cp-form-required-'+uid, 'cp-form-required-on', + Messages.form_required_on, isRequired, { + input: { value: 1 }, }); - $addC.before($rulesC); // XXX + var radioOff = UI.createRadio('cp-form-required-'+uid, 'cp-form-required-off', + Messages.form_required_off, !isRequired, { + input: { value: 0 }, + }); + var radioContainer = h('div.cp-form-required-radio', [ + h('span', Messages.form_required_answer), + radioOff, + radioOn + ]); + requiredDiv = h('div.cp-form-required', [ + radioContainer + ]); + $(radioContainer).find('input[type="radio"]').on('change', function() { + var val = $('input:radio[name="cp-form-required-'+uid+'"]:checked').val(); + val = Number(val) || 0; + block.opts.required = Boolean(val); + framework.localChange(); + framework._.cpNfInner.chainpad.onSettle(function () { + UI.log(Messages.saved); }); - }; - redraw(); - - $addC.click(function () { - $addC.hide(); - var rulesC = h('div.cp-form-condition-rule'); - var $rulesC = $(rulesC); - getConditions($rulesC, true); - $addC.before($rulesC); - }); - if (getConditionsValues().length) { - $condition.show(); - } else { - $condition.hide(); - } - evShowConditions.reg(function () { - if (getConditionsValues().length) { - $condition.show(); - } else { - $condition.hide(); - } - }); - evCheckConditions.reg(function (_uid) { - if (uid !== _uid) { return; } - // If our conditions are invalid, redraw them - if (getConditionsValues().length) { - $condition.show(); - } else { - $condition.hide(); - } - $condition.find('.cp-form-condition-rule').remove(); - redraw(); }); } + if (APP.isEditor && type === "section") { + data.editing = true; + } + var changeType; if (editable) { // Drag handle @@ -3474,7 +3538,6 @@ define([ isStatic ? undefined : q, h('div.cp-form-block-content', [ APP.isEditor && !isStatic ? requiredDiv : undefined, - APP.isEditor ? conditionalDiv : undefined, APP.isEditor && !isStatic ? previewDiv : undefined, data.tag, editContainer, @@ -3553,6 +3616,31 @@ define([ }); updateAddInline(); + // Fix cursor in conditions dropdown + // If we had a condition dropdown displayed, we're going to make sure + // it doesn't move on the screen after the redraw + // To do so, we need to adjust the "scrollTop" value saved before redrawing + getSections(content).forEach(function (uid) { + var block = content.form[uid]; + if (!block.opts || !Array.isArray(block.opts.questions)) { return; } + var $block = $container.find('.cp-form-block[data-id="'+uid+'"] .cp-form-section-sortable'); + + if (temp && temp[uid]) { + var tmp = temp[uid]; + var u = tmp.uid; + var t = tmp.type; + var $c = $block.closest('.cp-form-block').find('.cp-form-condition[data-uid="'+u+'"]'); + var $b = $c.find('[data-drop="'+t+'"]').find('button'); + var pos = $b && $b.length && $b[0].getBoundingClientRect().y; + // If we know the old position of the button and the new one, we can fix the scroll + // accordingly + if (typeof(pos) === "number" && typeof(tmp.y) === "number") { + var diff = pos - tmp.y; + APP.sTop += diff; + } + } + }); + if (editable) { if (APP.mainSortable) { APP.mainSortable.destroy(); } APP.mainSortable = Sortable.create($container[0], { @@ -4049,9 +4137,10 @@ define([ return; } rules.forEach(function (obj) { + if (!obj.q) { return; } var idx = available.indexOf(String(obj.q)); // If this question doesn't exist before the section, remove the condition - if (!obj.q || idx === -1) { + if (idx === -1) { var cIdx = rules.indexOf(obj); rules.splice(cIdx, 1); errors = true; @@ -4317,34 +4406,36 @@ define([ // If we try to redraw in the first 500ms following a redraw, we'll // redraw again when the timer allows it. If we call "redrawRemote" // repeatedly, we'll only "redraw" once with the latest content. - var _redraw = Util.notAgainForAnother(updateForm, 500); + var _redraw = Util.notAgainForAnother(function (framework, content) { + var answers, temp; + if (!APP.isEditor) { answers = getFormResults(); } + else { temp = getTempFields(); } + updateForm(framework, content, APP.isEditor, answers, temp); + }, 500); var redrawTo; var redrawRemote = function () { var $main = $('.cp-form-creator-container'); var args = Array.prototype.slice.call(arguments); - var sTop = $main.scrollTop(); + APP.sTop = $main.scrollTop(); var until = _redraw.apply(null, args); if (until) { clearTimeout(redrawTo); redrawTo = setTimeout(function (){ - sTop = $main.scrollTop(); + APP.sTop = $main.scrollTop(); _redraw.apply(null, args); - $main.scrollTop(sTop); + $main.scrollTop(APP.sTop); }, until+1); return; } // Only restore scroll if we were able to redraw - $main.scrollTop(sTop); + $main.scrollTop(APP.sTop); }; framework.onContentUpdate(function (newContent) { content = newContent; evOnChange.fire(); refreshEndDateBanner(); - var answers, temp; - if (!APP.isEditor) { answers = getFormResults(); } - else { temp = getTempFields(); } - redrawRemote(framework, content, APP.isEditor, answers, temp); + redrawRemote(framework, content); }); framework.setContentGetter(function () {