Add more block types in form

pull/1/head
yflory 4 years ago
parent 3ce5d477a1
commit 29dc1a5b3b

@ -109,6 +109,18 @@
} }
&.editable { cursor: grab; } &.editable { cursor: grab; }
} }
.cp-form-edit-max-options {
display: flex;
align-items: center;
input {
width: 100px;
margin-left: 10px;
}
}
.cp-form-edit-options-block {
display: flex;
flex-wrap: wrap;
}
.cp-form-edit-block { .cp-form-edit-block {
.cp-form-edit-block-input { .cp-form-edit-block-input {
display: flex; display: flex;
@ -158,6 +170,10 @@
} }
.cp-form-results-type-radio { .cp-form-results-type-radio {
display: table; display: table;
.cp-form-results-type-multiradio-data {
display: flex;
flex-flow: column;
}
.cp-form-results-type-radio-data { .cp-form-results-type-radio-data {
display: table-row; display: table-row;
border: 1px solid @cp_form-border; border: 1px solid @cp_form-border;
@ -175,7 +191,7 @@
} }
} }
.cp-form-type-radio { .cp-form-type-radio, .cp-form-type-checkbox {
display: flex; display: flex;
flex-flow: column; flex-flow: column;
align-items: baseline; align-items: baseline;
@ -183,6 +199,23 @@
display: inline-flex; display: inline-flex;
} }
} }
.cp-form-type-multiradio {
display: table;
& > * {
display: table-row;
& > * {
display: table-cell;
padding: 5px 20px;
vertical-align: middle;
&:first-child {
min-width: 200px;
}
.cp-radio-mark {
margin: auto;
}
}
}
}
} }

@ -57,14 +57,17 @@ define([
Messages.form_invalid = "Invalid form"; Messages.form_invalid = "Invalid form";
Messages.form_editBlock = "Edit options"; Messages.form_editBlock = "Edit options";
Messages.form_editQuestion = "Edit question"; Messages.form_editQuestion = "Edit question";
Messages.form_editMax = "Max selectable options";
Messages.form_newOption = "New option";
Messages.form_default = "Your question here?"; Messages.form_default = "Your question here?";
Messages.form_type_input = "Text"; // XXX Messages.form_type_input = "Text"; // XXX
Messages.form_type_radio = "Radio"; // XXX Messages.form_type_radio = "Radio"; // XXX
Messages.form_type_multiradio = "Multiline Radio"; // XXX
Messages.form_type_checkbox = "Checkbox"; // XXX
Messages.form_type_multicheck = "Multiline Checkbox"; // XXX
Messages.form_duplicates = "Duplicate entries have been removed"; Messages.form_duplicates = "Duplicate entries have been removed";
Messages.form_maxOptions = "{0} answer(s) max";
Messages.form_submit = "Submit"; Messages.form_submit = "Submit";
Messages.form_update = "Update"; Messages.form_update = "Update";
@ -91,42 +94,109 @@ define([
Messages.form_isClosed = "This form was closed on {0}"; Messages.form_isClosed = "This form was closed on {0}";
Messages.form_willClose = "This form will close on {0}"; Messages.form_willClose = "This form will close on {0}";
Messages.form_defaultOption = "Option {0}";
Messages.form_defaultItem = "Item {0}";
Messages.form_newOption = "New option";
Messages.form_newItem = "New item";
Messages.form_add_option = "Add option";
Messages.form_add_item = "Add item";
var MAX_OPTIONS = 10; // XXX
var MAX_ITEMS = 10; // XXX
var editOptions = function (v, setCursorGetter, cb, tmp) { var editOptions = function (v, setCursorGetter, cb, tmp) {
var add = h('button.btn.btn-secondary', [ var add = h('button.btn.btn-secondary', [
h('i.fa.fa-plus'), h('i.fa.fa-plus'),
h('span', Messages.tag_add) h('span', Messages.form_add_option)
]);
var addItem = h('button.btn.btn-secondary', [
h('i.fa.fa-plus'),
h('span', Messages.form_add_item)
]); ]);
var cursor; var cursor;
if (tmp && tmp.content && Sortify(v) === Sortify(tmp.old)) { if (tmp && tmp.content && Sortify(v) === Sortify(tmp.old)) {
v = tmp.content.values; v = tmp.content;
cursor = tmp.cursor; cursor = tmp.cursor;
} }
var maxOptions, maxInput;
if (typeof(v.max) === "number") {
maxInput = h('input', {
type:"number",
value: v.max,
min: 1,
max: v.values.length
})
maxOptions = h('div.cp-form-edit-max-options', [
h('span', Messages.form_editMax),
maxInput
]);
}
// Show existing options // Show existing options
var getOption = function (val) { var $add;
var getOption = function (val, isItem, uid) {
var input = h('input', {value:val}); var input = h('input', {value:val});
if (uid) { $(input).data('uid', uid); }
// if this element was active before the remote change, restore cursor // if this element was active before the remote change, restore cursor
if (cursor && cursor.el === val) { if (cursor && cursor.el === val) {
console.log(isItem);
console.log(cursor.item);
console.log(Boolean(isItem));
console.log(Boolean(cursor.item));
}
var setCursor = function () {
input.selectionStart = cursor.start || 0; input.selectionStart = cursor.start || 0;
input.selectionEnd = cursor.end || 0; input.selectionEnd = cursor.end || 0;
setTimeout(function () { input.focus(); }); setTimeout(function () { input.focus(); });
};
if (isItem) {
if (cursor && cursor.uid === uid && cursor.item) { setCursor(); }
} else {
if (cursor && cursor.el === val && !cursor.item) { setCursor(); }
} }
var del = h('button.btn.btn-danger', h('i.fa.fa-times')); var del = h('button.btn.btn-danger', h('i.fa.fa-times'));
var el = h('div.cp-form-edit-block-input', [ input, del ]); var el = h('div.cp-form-edit-block-input', [ input, del ]);
$(del).click(function () { $(el).remove(); }); $(del).click(function () {
$(el).remove();
// We've just deleted an item/option so we should be under the MAX limit and
// we can show the "add" button again
if (isItem && $addItem) { $addItem.show(); }
if (!isItem && $add) { $add.show(); }
});
return el; return el;
}; };
var inputs = v.map(getOption); var inputs = v.values.map(function (val) { return getOption(val, false); });
inputs.push(add); inputs.push(add);
var container = h('div.cp-form-edit-block', inputs); var container = h('div.cp-form-edit-block', inputs);
// Add option var containerItems;
var $add = $(add).click(function () { if (v.items) {
$add.before(getOption(Messages.form_newOption)); var inputsItems = v.items.map(function (itemData) {
return getOption(itemData.v, true, itemData.uid);
});
inputsItems.push(addItem);
containerItems = h('div.cp-form-edit-block', inputsItems);
}
// "Add option" button handler
$add = $(add).click(function () {
$add.before(getOption(Messages.form_newOption, false));
if ($(container).find('input').length >= MAX_OPTIONS) { $add.hide(); }
});
// If multiline block, handle "Add item" button
$addItem = $(addItem).click(function () {
$addItem.before(getOption(Messages.form_newItem, true, Util.uid()));
if ($(containerItems).find('input').length >= MAX_ITEMS) { $addItem.hide(); }
}); });
if ($(container).find('input').length >= MAX_OPTIONS) { $add.hide(); }
if ($(containerItems).find('input').length >= MAX_ITEMS) { $addItem.hide(); }
// Cancel changes // Cancel changes
var cancelBlock = h('button.btn.btn-secondary', Messages.cancel); var cancelBlock = h('button.btn.btn-secondary', Messages.cancel);
@ -145,9 +215,31 @@ define([
} }
values.push($(el).val()); values.push($(el).val());
}); });
var _content = {values: values};
if (maxInput) {
_content.max = Number($(maxInput).val()) || 1;
}
if (v.items) {
var items = [];
$(containerItems).find('input').each(function (i, el) {
if (el === active) {
cursor.item = true;
cursor.uid= $(el).data('uid');
cursor.start = el.selectionStart;
cursor.end = el.selectionEnd;
}
items.push({
uid: $(el).data('uid'),
v: $(el).val()
});
});
_content.items = items;
}
return { return {
old: v, old: (tmp && tmp.old) || v,
content: {values: values}, content: _content,
cursor: cursor cursor: cursor
}; };
}); });
@ -159,6 +251,8 @@ define([
]); ]);
$(saveBlock).click(function () { $(saveBlock).click(function () {
$(saveBlock).attr('disabled', 'disabled'); $(saveBlock).attr('disabled', 'disabled');
// Get values
var values = []; var values = [];
var duplicates = false; var duplicates = false;
$(container).find('input').each(function (i, el) { $(container).find('input').each(function (i, el) {
@ -166,14 +260,43 @@ define([
if (values.indexOf(val) === -1) { values.push(val); } if (values.indexOf(val) === -1) { values.push(val); }
else { duplicates = true; } else { duplicates = true; }
}); });
var res = { values: values };
// If multiline block, get items
if (v.items) {
var items = [];
$(containerItems).find('input').each(function (i, el) {
var val = $(el).val().trim();
var uid = $(el).data('uid');
if (!items.some(function (i) { return i.uid === uid; })) {
items.push({
uid: $(el).data('uid'),
v: val
});
}
else { duplicates = true; }
});
res.items = items;
}
// Show duplicates warning
if (duplicates) { if (duplicates) {
UI.warn(Messages.form_duplicates); UI.warn(Messages.form_duplicates);
} }
cb({values: values});
// If checkboxes, get the maximum number of values the users can select
if (maxInput) {
var maxVal = Number($(maxInput).val());
if (isNaN(maxVal)) { maxVal = values.length; }
res.max = maxVal;
}
cb(res);
}); });
return [ return [
container, maxOptions,
h('div.cp-form-edit-options-block', [containerItems, container]),
h('div', [cancelBlock, saveBlock]) h('div', [cancelBlock, saveBlock])
]; ];
}; };
@ -184,6 +307,18 @@ define([
} }
}; };
var findItem = function (items, uid) {
if (!Array.isArray(items)) { return; }
var res;
items.some(function (item) {
if (item.uid === uid) {
res = item.v;
return true;
}
});
return res;
};
var TYPES = { var TYPES = {
input: { input: {
get: function () { get: function () {
@ -213,10 +348,13 @@ define([
}, },
radio: { radio: {
defaultOpts: { defaultOpts: {
values: ["Option 1", "Option 2"] // XXX? values: [1,2].map(function (i) {
return Messages._getKey('form_defaultOption', [i]);
})
}, },
get: function (opts) { get: function (opts) {
if (!opts) { opts = TYPES.radio.defaultOpts; } if (!opts) { opts = TYPES.radio.defaultOpts; }
if (!Array.isArray(opts.values)) { return; }
var name = Util.uid(); var name = Util.uid();
var els = opts.values.map(function (data, i) { var els = opts.values.map(function (data, i) {
var radio = UI.createRadio(name, 'cp-form-'+name+'-'+i, var radio = UI.createRadio(name, 'cp-form-'+name+'-'+i,
@ -232,15 +370,17 @@ define([
getValue: function () { getValue: function () {
var res; var res;
els.some(function (el, i) { els.some(function (el, i) {
if (Util.isChecked($(el).find('input'))) { var $i = $(el).find('input');
res = opts.values[i]; if (Util.isChecked($i)) {
res = $i.data('val');
return true;
} }
}); });
return res; return res;
}, },
reset: function () { $(tag).find('input').removeAttr('checked'); }, reset: function () { $(tag).find('input').removeAttr('checked'); },
edit: function (cb, tmp) { edit: function (cb, tmp) {
var v = opts.values.slice(); var v = Util.clone(opts);
return editOptions(v, setCursorGetter, cb, tmp); return editOptions(v, setCursorGetter, cb, tmp);
}, },
getCursor: function () { return cursorGetter(); }, getCursor: function () { return cursorGetter(); },
@ -279,9 +419,328 @@ define([
return h('div.cp-form-results-type-radio', results); return h('div.cp-form-results-type-radio', results);
}, },
icon: h('i.fa.fa-list-ul') icon: h('i.fa.fa-list-ul')
},
multiradio: {
defaultOpts: {
items: [1,2].map(function (i) {
return {
uid: Util.uid(),
v: Messages._getKey('form_defaultItem', [i])
};
}),
values: [1,2].map(function (i) {
return Messages._getKey('form_defaultOption', [i]);
})
},
get: function (opts) {
if (!opts) { opts = TYPES.multiradio.defaultOpts; }
if (!Array.isArray(opts.items) || !Array.isArray(opts.values)) { return; }
var lines = opts.items.map(function (itemData) {
var name = itemData.uid;
var item = itemData.v;
var els = opts.values.map(function (data, i) {
var radio = UI.createRadio(name, 'cp-form-'+name+'-'+i,
'', false, { mark: { tabindex:1 } });
$(radio).find('input').data('uid', name);
$(radio).find('input').data('val', data);
return radio;
});
els.unshift(h('div.cp-form-multiradio-item', item));
return h('div.radio-group', {'data-uid':name}, els);
});
var header = opts.values.map(function (v) { return h('span', v); });
header.unshift(h('span'));
lines.unshift(h('div.cp-form-multiradio-header', header));
var tag = h('div.radio-group.cp-form-type-multiradio', lines);
var cursorGetter;
var setCursorGetter = function (f) { cursorGetter = f; };
return {
tag: tag,
getValue: function () {
var res = {};
var l = lines.slice(1);
l.forEach(function (el, i) {
var $el = $(el);
var uid = $el.attr('data-uid');
var $l = $el.find('input').each(function (i, input) {
var $i = $(input);
if (res[uid]) { return; }
if (Util.isChecked($i)) { res[uid] = $i.data('val'); }
});
});
return res;
},
reset: function () { $(tag).find('input').removeAttr('checked'); },
edit: function (cb, tmp) {
var v = Util.clone(opts);
return editOptions(v, setCursorGetter, cb, tmp);
},
getCursor: function () { return cursorGetter(); },
setValue: function (val) {
this.reset();
Object.keys(val || {}).forEach(function (uid) {
$(tag).find('[name="'+uid+'"]').each(function (i, el) {
if ($(el).data('val') !== val[uid]) { return; }
$(el).prop('checked', true);
});
});
} }
}; };
},
printResults: function (answers, uid, form) {
var structure = form[uid];
if (!structure) { return; }
var results = [];
var empty = 0;
var count = {};
Object.keys(answers).forEach(function (author) {
var obj = answers[author];
var answer = obj.msg[uid];
if (!answer || !Object.keys(answer).length) { return empty++; }
//count[answer] = count[answer] || {};
Object.keys(answer).forEach(function (q_uid) {
var c = count[q_uid] = count[q_uid] || {};
var res = answer[q_uid];
if (!res || !res.trim()) { return; }
c[res] = c[res] || 0;
c[res]++;
});
});
Object.keys(count).forEach(function (q_uid) {
var q = findItem(structure.opts.items, q_uid);
var c = count[q_uid];
var values = Object.keys(c).map(function (res) {
return h('div.cp-form-results-type-radio-data', [
h('span.cp-value', res),
h('span.cp-count', c[res])
]);
});
results.push(h('div.cp-form-results-type-multiradio-data', [
h('span.cp-mr-q', q),
h('span.cp-mr-value', values)
]));
});
results.push(getEmpty(empty));
return h('div.cp-form-results-type-radio', results);
},
icon: h('i.fa.fa-list-ul')
},
checkbox: {
defaultOpts: {
max: 3,
values: [1, 2, 3].map(function (i) {
return Messages._getKey('form_defaultOption', [i]);
})
},
get: function (opts) {
if (!opts) { opts = TYPES.checkbox.defaultOpts; }
if (!Array.isArray(opts.values)) { return; }
var name = Util.uid();
var els = opts.values.map(function (data, i) {
var cbox = UI.createCheckbox('cp-form-'+name+'-'+i,
data, false, { mark: { tabindex:1 } });
$(cbox).find('input').data('val', data);
return cbox;
});
var tag = h('div', [
h('div.cp-form-max-options', Messages._getKey('form_maxOptions', [opts.max])),
h('div.radio-group.cp-form-type-checkbox', els)
]);
var $tag = $(tag);
$tag.find('input').on('change', function () {
var selected = $tag.find('input:checked').length;
if (selected >= opts.max) {
$tag.find('input:not(:checked)').attr('disabled', 'disabled');
} else {
$tag.find('input').removeAttr('disabled');
}
});
var cursorGetter;
var setCursorGetter = function (f) { cursorGetter = f; };
return {
tag: tag,
getValue: function () {
var res = [];
els.forEach(function (el, i) {
var $i = $(el).find('input');
if (Util.isChecked($i)) {
res.push($i.data('val'));
}
});
return res;
},
reset: function () { $(tag).find('input').removeAttr('checked'); },
edit: function (cb, tmp) {
var v = Util.clone(opts);
return editOptions(v, setCursorGetter, cb, tmp);
},
getCursor: function () { return cursorGetter(); },
setValue: function (val) {
this.reset();
if (!Array.isArray(val)) { return; }
els.forEach(function (el) {
var $el = $(el).find('input');
if (val.indexOf($el.data('val')) !== -1) {
$el.prop('checked', true);
}
});
}
};
},
printResults: function (answers, uid) {
var results = [];
var empty = 0;
var count = {};
Object.keys(answers).forEach(function (author) {
var obj = answers[author];
var answer = obj.msg[uid];
if (!Array.isArray(answer) || !answer.length) { return empty++; }
answer.forEach(function (val) {
count[val] = count[val] || 0;
count[val]++;
});
});
Object.keys(count).forEach(function (value) {
results.push(h('div.cp-form-results-type-radio-data', [
h('span.cp-value', value),
h('span.cp-count', count[value])
]));
});
results.push(getEmpty(empty));
return h('div.cp-form-results-type-radio', results);
},
icon: h('i.fa.fa-check-square-o')
},
multicheck: {
defaultOpts: {
max: 3,
items: [1,2].map(function (i) {
return {
uid: Util.uid(),
v: Messages._getKey('form_defaultItem', [i])
};
}),
values: [1,2,3].map(function (i) {
return Messages._getKey('form_defaultOption', [i]);
})
},
get: function (opts) {
if (!opts) { opts = TYPES.multicheck.defaultOpts; }
if (!Array.isArray(opts.items) || !Array.isArray(opts.values)) { return; }
var lines = opts.items.map(function (itemData) {
var name = itemData.uid;
var item = itemData.v;
var els = opts.values.map(function (data, i) {
var cbox = UI.createCheckbox('cp-form-'+name+'-'+i,
'', false, { mark: { tabindex:1 } });
$(cbox).find('input').data('uid', name);
$(cbox).find('input').data('val', data);
return cbox;
});
els.unshift(h('div.cp-form-multiradio-item', item));
return h('div.radio-group', {'data-uid':name}, els);
});
lines.forEach(function (l) {
$(l).find('input').on('change', function () {
var selected = $(l).find('input:checked').length;
if (selected >= opts.max) {
$(l).find('input:not(:checked)').attr('disabled', 'disabled');
} else {
$(l).find('input').removeAttr('disabled');
}
});
});
var header = opts.values.map(function (v) { return h('span', v); });
header.unshift(h('span'));
lines.unshift(h('div.cp-form-multiradio-header', header));
var tag = h('div.radio-group.cp-form-type-multiradio', lines);
var cursorGetter;
var setCursorGetter = function (f) { cursorGetter = f; };
return {
tag: tag,
getValue: function () {
var res = {};
var l = lines.slice(1);
l.forEach(function (el, i) {
var $el = $(el);
var uid = $el.attr('data-uid');
res[uid] = [];
var $l = $el.find('input').each(function (i, input) {
var $i = $(input);
if (Util.isChecked($i)) { res[uid].push($i.data('val')); }
});
});
return res;
},
reset: function () { $(tag).find('input').removeAttr('checked'); },
edit: function (cb, tmp) {
var v = Util.clone(opts);
return editOptions(v, setCursorGetter, cb, tmp);
},
getCursor: function () { return cursorGetter(); },
setValue: function (val) {
this.reset();
Object.keys(val || {}).forEach(function (uid) {
if (!Array.isArray(val[uid])) { return; }
$(tag).find('[data-uid="'+uid+'"] input').each(function (i, el) {
if (val[uid].indexOf($(el).data('val')) === -1) { return; }
$(el).prop('checked', true);
});
});
}
};
},
printResults: function (answers, uid, form) {
var structure = form[uid];
if (!structure) { return; }
var results = [];
var empty = 0;
var count = {};
Object.keys(answers).forEach(function (author) {
var obj = answers[author];
var answer = obj.msg[uid];
if (!answer || !Object.keys(answer).length) { return empty++; }
Object.keys(answer).forEach(function (q_uid) {
var c = count[q_uid] = count[q_uid] || {};
var res = answer[q_uid];
if (!Array.isArray(res) || !res.length) { return; }
res.forEach(function (v) {
c[v] = c[v] || 0;
c[v]++;
});
});
});
Object.keys(count).forEach(function (q_uid) {
var q = findItem(structure.opts.items, q_uid);
var c = count[q_uid];
var values = Object.keys(c).map(function (res) {
return h('div.cp-form-results-type-radio-data', [
h('span.cp-value', res),
h('span.cp-count', c[res])
]);
});
results.push(h('div.cp-form-results-type-multiradio-data', [
h('span.cp-mr-q', q),
h('span.cp-mr-value', values)
]));
});
results.push(getEmpty(empty));
return h('div.cp-form-results-type-radio', results);
},
icon: h('i.fa.fa-list-ul')
},
};
var renderResults = function (content, answers) { var renderResults = function (content, answers) {
var $container = $('div.cp-form-creator-results').empty(); var $container = $('div.cp-form-creator-results').empty();
var form = content.form; var form = content.form;
@ -290,7 +749,7 @@ define([
var type = block.type; var type = block.type;
var model = TYPES[type]; var model = TYPES[type];
if (!model || !model.printResults) { return; } if (!model || !model.printResults) { return; }
var print = model.printResults(answers, uid); var print = model.printResults(answers, uid, form);
var q = h('div.cp-form-block-question', block.q || Messages.form_default); var q = h('div.cp-form-block-question', block.q || Messages.form_default);
return h('div.cp-form-block', [ return h('div.cp-form-block', [
@ -336,6 +795,7 @@ define([
console.error(err || data.error); console.error(err || data.error);
return void UI.warn(Messages.error); return void UI.warn(Messages.error);
} }
$send.removeAttr('disabled');
UI.alert(Messages.form_sent); UI.alert(Messages.form_sent);
$send.text(Messages.form_update); $send.text(Messages.form_update);
}); });
@ -379,6 +839,7 @@ define([
if (!model) { return; } if (!model) { return; }
var data = model.get(block.opts); var data = model.get(block.opts);
if (!data) { return; }
data.uid = uid; data.uid = uid;
if (answers && answers[uid]) { data.setValue(answers[uid]); } if (answers && answers[uid]) { data.setValue(answers[uid]); }
@ -476,6 +937,7 @@ define([
$(editButtons).show(); $(editButtons).show();
UI.log(Messages.saved); UI.log(Messages.saved);
data = model.get(newOpts); data = model.get(newOpts);
if (!data) { data = {}; }
$oldTag.before(data.tag).remove(); $oldTag.before(data.tag).remove();
}); });
}; };
@ -752,6 +1214,7 @@ define([
var endDate; var endDate;
var endDateTo; var endDateTo;
var refreshEndDateBanner = function (force) { var refreshEndDateBanner = function (force) {
if (APP.isEditor) { return; }
var _endDate = content.answers.endDate; var _endDate = content.answers.endDate;
if (_endDate === endDate && !force) { return; } if (_endDate === endDate && !force) { return; }
endDate = _endDate; endDate = _endDate;

Loading…
Cancel
Save