|
|
|
|
define([
|
|
|
|
|
'jquery',
|
|
|
|
|
'json.sortify',
|
|
|
|
|
'/bower_components/chainpad-crypto/crypto.js',
|
|
|
|
|
'/common/sframe-app-framework.js',
|
|
|
|
|
'/common/toolbar.js',
|
|
|
|
|
'/bower_components/nthen/index.js',
|
|
|
|
|
'/common/sframe-common.js',
|
|
|
|
|
'/common/common-util.js',
|
|
|
|
|
'/common/common-hash.js',
|
|
|
|
|
'/common/common-interface.js',
|
|
|
|
|
'/common/common-ui-elements.js',
|
|
|
|
|
'/common/clipboard.js',
|
|
|
|
|
'/common/inner/common-mediatag.js',
|
|
|
|
|
'/common/hyperscript.js',
|
|
|
|
|
'/customize/messages.js',
|
|
|
|
|
'/customize/application_config.js',
|
|
|
|
|
'/common/diffMarked.js',
|
|
|
|
|
'/common/sframe-common-codemirror.js',
|
|
|
|
|
'cm/lib/codemirror',
|
|
|
|
|
|
|
|
|
|
'/common/inner/share.js',
|
|
|
|
|
'/common/inner/access.js',
|
|
|
|
|
'/common/inner/properties.js',
|
|
|
|
|
|
|
|
|
|
'/lib/datepicker/flatpickr.js',
|
|
|
|
|
'/bower_components/sortablejs/Sortable.min.js',
|
|
|
|
|
|
|
|
|
|
'cm/addon/display/placeholder',
|
|
|
|
|
'cm/mode/markdown/markdown',
|
|
|
|
|
'css!cm/lib/codemirror.css',
|
|
|
|
|
|
|
|
|
|
'css!/bower_components/codemirror/lib/codemirror.css',
|
|
|
|
|
'css!/bower_components/codemirror/addon/dialog/dialog.css',
|
|
|
|
|
'css!/bower_components/codemirror/addon/fold/foldgutter.css',
|
|
|
|
|
'css!/lib/datepicker/flatpickr.min.css',
|
|
|
|
|
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
|
|
|
|
'less!/form/app-form.less',
|
|
|
|
|
], function (
|
|
|
|
|
$,
|
|
|
|
|
Sortify,
|
|
|
|
|
Crypto,
|
|
|
|
|
Framework,
|
|
|
|
|
Toolbar,
|
|
|
|
|
nThen,
|
|
|
|
|
SFCommon,
|
|
|
|
|
Util,
|
|
|
|
|
Hash,
|
|
|
|
|
UI,
|
|
|
|
|
UIElements,
|
|
|
|
|
Clipboard,
|
|
|
|
|
MT,
|
|
|
|
|
h,
|
|
|
|
|
Messages,
|
|
|
|
|
AppConfig,
|
|
|
|
|
DiffMd,
|
|
|
|
|
SFCodeMirror,
|
|
|
|
|
CMeditor,
|
|
|
|
|
Share, Access, Properties,
|
|
|
|
|
Flatpickr,
|
|
|
|
|
Sortable
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
var APP = window.APP = {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var is24h = false;
|
|
|
|
|
var dateFormat = "Y-m-d H:i";
|
|
|
|
|
var timeFormat = "H:i";
|
|
|
|
|
try {
|
|
|
|
|
is24h = !new Intl.DateTimeFormat(navigator.language, { hour: 'numeric' }).format(0).match(/AM/);
|
|
|
|
|
} catch (e) {}
|
|
|
|
|
is24h = false;
|
|
|
|
|
if (!is24h) {
|
|
|
|
|
dateFormat = "Y-m-d h:i K";
|
|
|
|
|
timeFormat = "h:i K";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Messages.button_newform = "New Form"; // XXX
|
|
|
|
|
Messages.form_invalid = "Invalid form";
|
|
|
|
|
Messages.form_editBlock = "Edit options";
|
|
|
|
|
Messages.form_editQuestion = "Edit question";
|
|
|
|
|
Messages.form_editMax = "Max selectable options";
|
|
|
|
|
Messages.form_editType = "Options type";
|
|
|
|
|
|
|
|
|
|
Messages.form_poll_text = "Text";
|
|
|
|
|
Messages.form_poll_day = "Day";
|
|
|
|
|
Messages.form_poll_time = "Time";
|
|
|
|
|
|
|
|
|
|
Messages.form_default = "Your question here?";
|
|
|
|
|
Messages.form_type_input = "Text"; // 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_type_poll = "Poll"; // XXX
|
|
|
|
|
|
|
|
|
|
Messages.form_type_md = "Description"; // XXX
|
|
|
|
|
Messages.form_type_page = "Page break"; // XXX
|
|
|
|
|
|
|
|
|
|
Messages.form_duplicates = "Duplicate entries have been removed";
|
|
|
|
|
Messages.form_maxOptions = "{0} answer(s) max";
|
|
|
|
|
|
|
|
|
|
Messages.form_submit = "Submit";
|
|
|
|
|
Messages.form_update = "Update";
|
|
|
|
|
Messages.form_reset = "Reset";
|
|
|
|
|
Messages.form_sent = "Sent";
|
|
|
|
|
Messages.form_delete = "Delete block";
|
|
|
|
|
|
|
|
|
|
Messages.form_cantFindAnswers = "Unable to retrieve your existing answers for this form.";
|
|
|
|
|
Messages.form_answered = "You already answered this form";
|
|
|
|
|
|
|
|
|
|
Messages.form_viewResults = "Go to responses";
|
|
|
|
|
Messages.form_viewCreator = "Go to form creator";
|
|
|
|
|
Messages.form_showIndividual = "Show individual answers";
|
|
|
|
|
Messages.form_showSummary = "Show summary";
|
|
|
|
|
Messages.form_answerAnonymous = "Anonymous answer on <em>{0}</em>";
|
|
|
|
|
Messages.form_viewButton = "View";
|
|
|
|
|
Messages.form_backButton = "Back";
|
|
|
|
|
Messages.form_answerName = "Answer from {0} on <em>{1}</em>";
|
|
|
|
|
Messages.form_answerWarning = "Unconfirmed identity";
|
|
|
|
|
|
|
|
|
|
Messages.form_notAnswered = "And <b>{0}</b> empty answers";
|
|
|
|
|
|
|
|
|
|
Messages.form_makePublic = "Make public";
|
|
|
|
|
Messages.form_makePublicWarning = "Are you sure you want to make the results of this form public? This can't be undone.";
|
|
|
|
|
Messages.form_isPublic = "Results are public";
|
|
|
|
|
Messages.form_isPrivate = "Results are private";
|
|
|
|
|
|
|
|
|
|
Messages.form_open = "Open";
|
|
|
|
|
Messages.form_setEnd = "Set closing date";
|
|
|
|
|
Messages.form_removeEnd = "Remove closing date";
|
|
|
|
|
Messages.form_isOpen = "This form is open";
|
|
|
|
|
Messages.form_isClosed = "This form was closed on {0}";
|
|
|
|
|
Messages.form_willClose = "This form will close on {0}";
|
|
|
|
|
|
|
|
|
|
Messages.form_anonymous_on = "Anonymous answers are allowed";
|
|
|
|
|
Messages.form_anonymous_off = "Anonymous answers are blocked";
|
|
|
|
|
Messages.form_anonymous_button_on = "Block anonymous answers";
|
|
|
|
|
Messages.form_anonymous_button_off = "Allow anonymous answers";
|
|
|
|
|
Messages.form_anonymous_blocked = "Anonymous responses are blocked for this form. You must log in or register to submit answers.";
|
|
|
|
|
|
|
|
|
|
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";
|
|
|
|
|
Messages.form_addMultiple = "Add all";
|
|
|
|
|
Messages.form_clear = "Clear";
|
|
|
|
|
|
|
|
|
|
Messages.form_page_prev = "Previous";
|
|
|
|
|
Messages.form_page = "Page {0}/{1}";
|
|
|
|
|
Messages.form_page_next = "Next";
|
|
|
|
|
|
|
|
|
|
Messages.form_anonymousBox = "Answer anonymously";
|
|
|
|
|
|
|
|
|
|
var MAX_OPTIONS = 15; // XXX
|
|
|
|
|
var MAX_ITEMS = 10; // XXX
|
|
|
|
|
|
|
|
|
|
var editOptions = function (v, setCursorGetter, cb, tmp) {
|
|
|
|
|
var add = h('button.btn.btn-secondary', [
|
|
|
|
|
h('i.fa.fa-plus'),
|
|
|
|
|
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;
|
|
|
|
|
if (tmp && tmp.content && Sortify(v) === Sortify(tmp.old)) {
|
|
|
|
|
v = tmp.content;
|
|
|
|
|
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
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var type, typeSelect;
|
|
|
|
|
if (v.type) {
|
|
|
|
|
var options = ['text', 'day', 'time'].map(function (t) {
|
|
|
|
|
return {
|
|
|
|
|
tag: 'a',
|
|
|
|
|
attributes: {
|
|
|
|
|
'class': 'cp-form-type-value',
|
|
|
|
|
'data-value': t,
|
|
|
|
|
'href': '#',
|
|
|
|
|
},
|
|
|
|
|
content: Messages['form_poll_'+t]
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
var dropdownConfig = {
|
|
|
|
|
text: '', // Button initial text
|
|
|
|
|
options: options, // Entries displayed in the menu
|
|
|
|
|
//left: true, // Open to the left of the button
|
|
|
|
|
//container: $(type),
|
|
|
|
|
isSelect: true,
|
|
|
|
|
caretDown: true,
|
|
|
|
|
buttonCls: 'btn btn-secondary'
|
|
|
|
|
};
|
|
|
|
|
typeSelect = UIElements.createDropdown(dropdownConfig);
|
|
|
|
|
typeSelect.setValue(v.type);
|
|
|
|
|
|
|
|
|
|
type = h('div.cp-form-edit-type', [
|
|
|
|
|
h('span', Messages.form_editType),
|
|
|
|
|
typeSelect[0]
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Show existing options
|
|
|
|
|
var $add, $addItem;
|
|
|
|
|
var addMultiple;
|
|
|
|
|
var getOption = function (val, isItem, uid) {
|
|
|
|
|
var input = h('input', {value:val});
|
|
|
|
|
if (uid) { $(input).data('uid', uid); }
|
|
|
|
|
|
|
|
|
|
// If the input is a date, initialize flatpickr
|
|
|
|
|
if (v.type && v.type !== 'text') {
|
|
|
|
|
if (v.type === 'time') {
|
|
|
|
|
Flatpickr(input, {
|
|
|
|
|
enableTime: true,
|
|
|
|
|
time_24hr: is24h,
|
|
|
|
|
dateFormat: dateFormat,
|
|
|
|
|
defaultDate: val ? new Date(val) : undefined
|
|
|
|
|
});
|
|
|
|
|
} else if (v.type === 'day') {
|
|
|
|
|
/*Flatpickr(input, {
|
|
|
|
|
defaultDate: val ? new Date(val) : undefined
|
|
|
|
|
});*/
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if this element was active before the remote change, restore cursor
|
|
|
|
|
var setCursor = function () {
|
|
|
|
|
if (v.type && v.type !== 'text') { return; }
|
|
|
|
|
input.selectionStart = cursor.start || 0;
|
|
|
|
|
input.selectionEnd = cursor.end || 0;
|
|
|
|
|
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 el = h('div.cp-form-edit-block-input', [
|
|
|
|
|
h('span.cp-form-handle', [
|
|
|
|
|
h('i.fa.fa-ellipsis-v'),
|
|
|
|
|
h('i.fa.fa-ellipsis-v'),
|
|
|
|
|
]),
|
|
|
|
|
input,
|
|
|
|
|
del
|
|
|
|
|
]);
|
|
|
|
|
$(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();
|
|
|
|
|
if (v.type === "time") { $(addMultiple).show(); }
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return el;
|
|
|
|
|
};
|
|
|
|
|
var inputs = v.values.map(function (val) { return getOption(val, false); });
|
|
|
|
|
inputs.push(add);
|
|
|
|
|
|
|
|
|
|
var container = h('div.cp-form-edit-block', inputs);
|
|
|
|
|
var $container = $(container);
|
|
|
|
|
|
|
|
|
|
Sortable.create(container, {
|
|
|
|
|
direction: "vertical",
|
|
|
|
|
handle: ".cp-form-handle",
|
|
|
|
|
draggable: ".cp-form-edit-block-input",
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var containerItems;
|
|
|
|
|
if (v.items) {
|
|
|
|
|
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);
|
|
|
|
|
Sortable.create(containerItems, {
|
|
|
|
|
direction: "vertical",
|
|
|
|
|
handle: ".cp-form-handle",
|
|
|
|
|
draggable: ".cp-form-edit-block-input",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Calendar...
|
|
|
|
|
var calendarView;
|
|
|
|
|
if (v.type) {
|
|
|
|
|
var calendarInput = h('input');
|
|
|
|
|
calendarView = h('div', calendarInput);
|
|
|
|
|
var calendarDefault = v.type === "day" ? v.values.map(function (time) {
|
|
|
|
|
if (!time) { return; }
|
|
|
|
|
var d = new Date(time);
|
|
|
|
|
if (!isNaN(d)) { return d; }
|
|
|
|
|
}).filter(Boolean) : undefined;
|
|
|
|
|
Flatpickr(calendarInput, {
|
|
|
|
|
mode: 'multiple',
|
|
|
|
|
inline: true,
|
|
|
|
|
defaultDate: calendarDefault,
|
|
|
|
|
appendTo: calendarView
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Calendar time // XXX
|
|
|
|
|
if (v.type) {
|
|
|
|
|
var multipleInput = h('input');
|
|
|
|
|
var multipleClearButton = h('button.btn', Messages.form_clear);
|
|
|
|
|
var addMultipleButton = h('button.btn', [
|
|
|
|
|
h('i.fa.fa-plus'),
|
|
|
|
|
h('span', Messages.form_addMultiple)
|
|
|
|
|
]);
|
|
|
|
|
addMultiple = h('div', { style: "display: none;" }, [
|
|
|
|
|
multipleInput,
|
|
|
|
|
addMultipleButton,
|
|
|
|
|
multipleClearButton
|
|
|
|
|
]);
|
|
|
|
|
var multiplePickr = Flatpickr(multipleInput, {
|
|
|
|
|
mode: 'multiple',
|
|
|
|
|
enableTime: true,
|
|
|
|
|
dateFormat: dateFormat,
|
|
|
|
|
});
|
|
|
|
|
$(multipleClearButton).click(function () {
|
|
|
|
|
multiplePickr.clear();
|
|
|
|
|
});
|
|
|
|
|
$(addMultipleButton).click(function () {
|
|
|
|
|
multiplePickr.selectedDates.some(function (date) {
|
|
|
|
|
$add.before(getOption(date, false));
|
|
|
|
|
var l = $container.find('input').length;
|
|
|
|
|
$(maxInput).attr('max', l);
|
|
|
|
|
if (l >= MAX_OPTIONS) {
|
|
|
|
|
$add.hide();
|
|
|
|
|
$(addMultiple).hide();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
multiplePickr.clear();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var refreshView = function () {
|
|
|
|
|
if (!v.type) { return; }
|
|
|
|
|
var $calendar = $(calendarView);
|
|
|
|
|
if (v.type !== "day") {
|
|
|
|
|
$calendar.hide();
|
|
|
|
|
$container.show();
|
|
|
|
|
var l = $container.find('input').length;
|
|
|
|
|
if (v.type === "time" && l < MAX_OPTIONS) {
|
|
|
|
|
$(addMultiple).show();
|
|
|
|
|
} else {
|
|
|
|
|
$(addMultiple).hide();
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
$calendar.show();
|
|
|
|
|
$container.hide();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
refreshView();
|
|
|
|
|
|
|
|
|
|
// Doodle type change: empty current values and change input types?
|
|
|
|
|
if (typeSelect) {
|
|
|
|
|
typeSelect.onChange.reg(function (prettyVal, val) {
|
|
|
|
|
v.type = val;
|
|
|
|
|
refreshView();
|
|
|
|
|
if (val !== "text") {
|
|
|
|
|
$container.find('.cp-form-edit-block-input').remove();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
$container.find('input').each(function (i, input) {
|
|
|
|
|
if (input._flatpickr) {
|
|
|
|
|
input._flatpickr.destroy();
|
|
|
|
|
delete input._flatpickr;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// "Add option" button handler
|
|
|
|
|
$add = $(add).click(function () {
|
|
|
|
|
var txt = v.type ? '' : Messages.form_newOption;
|
|
|
|
|
$add.before(getOption(txt, false));
|
|
|
|
|
var l = $container.find('input').length;
|
|
|
|
|
$(maxInput).attr('max', l);
|
|
|
|
|
if (l >= 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
|
|
|
|
|
var cancelBlock = h('button.btn.btn-secondary', Messages.cancel);
|
|
|
|
|
$(cancelBlock).click(function () { cb(); });
|
|
|
|
|
|
|
|
|
|
// Set cursor getter (to handle remote changes to the form)
|
|
|
|
|
setCursorGetter(function () {
|
|
|
|
|
var values = [];
|
|
|
|
|
var active = document.activeElement;
|
|
|
|
|
var cursor = {};
|
|
|
|
|
$container.find('input').each(function (i, el) {
|
|
|
|
|
if (el === active && !el._flatpickr) {
|
|
|
|
|
cursor.el= $(el).val();
|
|
|
|
|
cursor.start = el.selectionStart;
|
|
|
|
|
cursor.end = el.selectionEnd;
|
|
|
|
|
}
|
|
|
|
|
values.push($(el).val());
|
|
|
|
|
});
|
|
|
|
|
if (v.type === "day") {
|
|
|
|
|
var dayPickr = $(calendarView).find('input')[0]._flatpickr;
|
|
|
|
|
values = dayPickr.selectedDates.map(function (date) {
|
|
|
|
|
return +date;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
var _content = {values: values};
|
|
|
|
|
|
|
|
|
|
if (maxInput) {
|
|
|
|
|
_content.max = Number($(maxInput).val()) || 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (typeSelect) {
|
|
|
|
|
_content.type = typeSelect.getValue();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 {
|
|
|
|
|
old: (tmp && tmp.old) || v,
|
|
|
|
|
content: _content,
|
|
|
|
|
cursor: cursor
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Save changes
|
|
|
|
|
var saveBlock = h('button.btn.btn-primary', [
|
|
|
|
|
h('i.fa.fa-floppy-o'),
|
|
|
|
|
h('span', Messages.settings_save)
|
|
|
|
|
]);
|
|
|
|
|
$(saveBlock).click(function () {
|
|
|
|
|
$(saveBlock).attr('disabled', 'disabled');
|
|
|
|
|
|
|
|
|
|
// Get values
|
|
|
|
|
var values = [];
|
|
|
|
|
var duplicates = false;
|
|
|
|
|
if (v.type === "day") {
|
|
|
|
|
var dayPickr = $(calendarView).find('input')[0]._flatpickr;
|
|
|
|
|
values = dayPickr.selectedDates.map(function (date) {
|
|
|
|
|
return +date;
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
$container.find('input').each(function (i, el) {
|
|
|
|
|
var val = $(el).val().trim();
|
|
|
|
|
if (v.type === "day" || v.type === "time") {
|
|
|
|
|
var f = el._flatpickr;
|
|
|
|
|
if (f && f.selectedDates && f.selectedDates.length) {
|
|
|
|
|
val = +f.selectedDates[0];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (values.indexOf(val) === -1) { values.push(val); }
|
|
|
|
|
else { duplicates = true; }
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
if (!values.length) {
|
|
|
|
|
return void UI.warn(Messages.error); // XXX error message: no values
|
|
|
|
|
}
|
|
|
|
|
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) {
|
|
|
|
|
UI.warn(Messages.form_duplicates);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (typeSelect) {
|
|
|
|
|
res.type = typeSelect.getValue();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cb(res);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
type,
|
|
|
|
|
maxOptions,
|
|
|
|
|
calendarView,
|
|
|
|
|
h('div.cp-form-edit-options-block', [containerItems, container]),
|
|
|
|
|
addMultiple,
|
|
|
|
|
h('div', [cancelBlock, saveBlock])
|
|
|
|
|
];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var makePollTable = function (answers, opts) {
|
|
|
|
|
// Sort date values
|
|
|
|
|
if (opts.type !== "text") {
|
|
|
|
|
opts.values.sort(function (a, b) {
|
|
|
|
|
return +new Date(a) - +new Date(b);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
// Create first line with options
|
|
|
|
|
var els = opts.values.map(function (data) {
|
|
|
|
|
if (opts.type === "day") {
|
|
|
|
|
var _date = new Date(data);
|
|
|
|
|
data = _date.toLocaleDateString();
|
|
|
|
|
}
|
|
|
|
|
if (opts.type === "time") {
|
|
|
|
|
var _dateT = new Date(data);
|
|
|
|
|
data = Flatpickr.formatDate(_dateT, timeFormat);
|
|
|
|
|
}
|
|
|
|
|
return h('div.cp-poll-cell.cp-form-poll-option', data);
|
|
|
|
|
});
|
|
|
|
|
// Insert axis switch button
|
|
|
|
|
var switchAxis = h('button.btn', [
|
|
|
|
|
h('i.fa.fa-exchange'),
|
|
|
|
|
]);
|
|
|
|
|
els.unshift(h('div.cp-poll-cell.cp-poll-switch', switchAxis));
|
|
|
|
|
var lines = [h('div', els)];
|
|
|
|
|
|
|
|
|
|
// Add an initial row to "time" values containing the days
|
|
|
|
|
if (opts.type === "time") {
|
|
|
|
|
var days = [h('div.cp-poll-cell')];
|
|
|
|
|
var _days = {};
|
|
|
|
|
opts.values.forEach(function (d) {
|
|
|
|
|
var date = new Date(d);
|
|
|
|
|
var day = date.toLocaleDateString();
|
|
|
|
|
_days[day] = _days[day] || 0;
|
|
|
|
|
_days[day]++;
|
|
|
|
|
});
|
|
|
|
|
var dayValues = Object.keys(_days).map(function (d) { return _days[d]; });
|
|
|
|
|
Object.keys(_days).forEach(function (day) {
|
|
|
|
|
days.push(h('div.cp-poll-cell.cp-poll-time-day', {
|
|
|
|
|
style: 'flex-grow:'+(_days[day]-1)+';'
|
|
|
|
|
}, day));
|
|
|
|
|
});
|
|
|
|
|
lines.unshift(h('div', days));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add answers
|
|
|
|
|
if (Array.isArray(answers)) {
|
|
|
|
|
answers.forEach(function (answer) {
|
|
|
|
|
if (!answer.name || !answer.values) { return; }
|
|
|
|
|
var _name = answer.name;
|
|
|
|
|
var values = answer.values || {};
|
|
|
|
|
var els = opts.values.map(function (data) {
|
|
|
|
|
var res = values[data] || 0;
|
|
|
|
|
var v = (Number(res) === 1) ? h('i.fa.fa-check.cp-yes') : undefined;
|
|
|
|
|
var cell = h('div.cp-poll-cell.cp-form-poll-answer', {
|
|
|
|
|
'data-value': res
|
|
|
|
|
}, v);
|
|
|
|
|
return cell;
|
|
|
|
|
});
|
|
|
|
|
els.unshift(h('div.cp-poll-cell.cp-poll-answer-name', _name));
|
|
|
|
|
lines.push(h('div', els));
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var $s = $(switchAxis).click(function () {
|
|
|
|
|
$s.closest('.cp-form-type-poll').toggleClass('cp-form-poll-switch');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return lines;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var getEmpty = function (empty) {
|
|
|
|
|
if (empty) {
|
|
|
|
|
return UI.setHTML(h('div.cp-form-results-type-text-empty'), Messages._getKey('form_notAnswered', [empty]));
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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 getBlockAnswers = function (answers, uid, filterCurve) {
|
|
|
|
|
return Object.keys(answers || {}).map(function (user) {
|
|
|
|
|
if (filterCurve && user === filterCurve) { return; }
|
|
|
|
|
try {
|
|
|
|
|
return answers[user].msg[uid];
|
|
|
|
|
} catch (e) { console.error(e); }
|
|
|
|
|
}).filter(Boolean);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var STATIC_TYPES = {
|
|
|
|
|
md: {
|
|
|
|
|
defaultOpts: {
|
|
|
|
|
text: "Your text here" // XXX
|
|
|
|
|
},
|
|
|
|
|
get: function (opts) {
|
|
|
|
|
if (!opts) { opts = STATIC_TYPES.md.defaultOpts; }
|
|
|
|
|
var tag = h('div', {
|
|
|
|
|
id: 'form'+Util.uid()
|
|
|
|
|
}, opts.text);
|
|
|
|
|
var $tag = $(tag);
|
|
|
|
|
DiffMd.apply(DiffMd.render(opts.text || ''), $tag, APP.common);
|
|
|
|
|
return {
|
|
|
|
|
tag: tag,
|
|
|
|
|
edit: function (cb, tmp) {
|
|
|
|
|
var t = h('textarea');
|
|
|
|
|
var block = h('div.cp-form-edit-options-block', [t]);
|
|
|
|
|
var cm = SFCodeMirror.create("gfm", CMeditor, t);
|
|
|
|
|
var editor = cm.editor;
|
|
|
|
|
editor.setOption('lineNumbers', true);
|
|
|
|
|
editor.setOption('lineWrapping', true);
|
|
|
|
|
editor.setOption('styleActiveLine', true);
|
|
|
|
|
editor.setOption('readOnly', false);
|
|
|
|
|
|
|
|
|
|
var text = opts.text;
|
|
|
|
|
var cursor;
|
|
|
|
|
if (tmp && tmp.content && tmp.old.text === text) {
|
|
|
|
|
text = tmp.content.text;
|
|
|
|
|
cursor = tmp.cursor;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setTimeout(function () {
|
|
|
|
|
editor.setValue(text);
|
|
|
|
|
if (cursor) {
|
|
|
|
|
if (Sortify(cursor.start) === Sortify(cursor.end)) {
|
|
|
|
|
editor.setCursor(cursor.start);
|
|
|
|
|
} else {
|
|
|
|
|
editor.setSelection(cursor.start, cursor.end);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
editor.refresh();
|
|
|
|
|
editor.save();
|
|
|
|
|
editor.focus();
|
|
|
|
|
});
|
|
|
|
|
if (APP.common) {
|
|
|
|
|
var markdownTb = APP.common.createMarkdownToolbar(editor);
|
|
|
|
|
$(block).prepend(markdownTb.toolbar);
|
|
|
|
|
$(markdownTb.toolbar).show();
|
|
|
|
|
cm.configureTheme(APP.common, function () {});
|
|
|
|
|
}
|
|
|
|
|
// Cancel changes
|
|
|
|
|
var cancelBlock = h('button.btn.btn-secondary', Messages.cancel);
|
|
|
|
|
$(cancelBlock).click(function () { cb(); });
|
|
|
|
|
// Save changes
|
|
|
|
|
var saveBlock = h('button.btn.btn-primary', [
|
|
|
|
|
h('i.fa.fa-floppy-o'),
|
|
|
|
|
h('span', Messages.settings_save)
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
var getContent = function () {
|
|
|
|
|
return {
|
|
|
|
|
text: editor.getValue()
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
$(saveBlock).click(function () {
|
|
|
|
|
$(saveBlock).attr('disabled', 'disabled');
|
|
|
|
|
cb(getContent());
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
cursorGetter = function () {
|
|
|
|
|
if (document.activeElement && block.contains(document.activeElement)) {
|
|
|
|
|
cursor = {
|
|
|
|
|
start: editor.getCursor('from'),
|
|
|
|
|
end: editor.getCursor('to')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
old: opts,
|
|
|
|
|
content: getContent(),
|
|
|
|
|
cursor: cursor
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
block,
|
|
|
|
|
h('div', [cancelBlock, saveBlock])
|
|
|
|
|
];
|
|
|
|
|
},
|
|
|
|
|
getCursor: function () { return cursorGetter(); },
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
printResults: function () { return; },
|
|
|
|
|
icon: h('i.fa.fa-info')
|
|
|
|
|
},
|
|
|
|
|
page: {
|
|
|
|
|
get: function () {
|
|
|
|
|
var tag = h('div.cp-form-page-break-edit', [
|
|
|
|
|
h('i.fa.fa-hand-o-right'),
|
|
|
|
|
h('span', Messages.form_type_page)
|
|
|
|
|
]);
|
|
|
|
|
var $tag = $(tag);
|
|
|
|
|
return {
|
|
|
|
|
tag: tag,
|
|
|
|
|
pageBreak: true
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
printResults: function () { return; },
|
|
|
|
|
icon: h('i.fa.fa-hand-o-right')
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
var TYPES = {
|
|
|
|
|
input: {
|
|
|
|
|
get: function () {
|
|
|
|
|
var tag = h('input');
|
|
|
|
|
var $tag = $(tag);
|
|
|
|
|
return {
|
|
|
|
|
tag: tag,
|
|
|
|
|
getValue: function () { return $tag.val(); },
|
|
|
|
|
setValue: function (val) { $tag.val(val); },
|
|
|
|
|
reset: function () { $tag.val(''); }
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
printResults: function (answers, uid) {
|
|
|
|
|
var results = [];
|
|
|
|
|
var empty = 0;
|
|
|
|
|
Object.keys(answers).forEach(function (author) {
|
|
|
|
|
var obj = answers[author];
|
|
|
|
|
var answer = obj.msg[uid];
|
|
|
|
|
if (!answer || !answer.trim()) { return empty++; }
|
|
|
|
|
results.push(h('div.cp-form-results-type-text-data', answer));
|
|
|
|
|
});
|
|
|
|
|
results.push(getEmpty(empty));
|
|
|
|
|
|
|
|
|
|
return h('div.cp-form-results-type-text', results);
|
|
|
|
|
},
|
|
|
|
|
icon: h('i.fa.fa-font')
|
|
|
|
|
},
|
|
|
|
|
radio: {
|
|
|
|
|
defaultOpts: {
|
|
|
|
|
values: [1,2].map(function (i) {
|
|
|
|
|
return Messages._getKey('form_defaultOption', [i]);
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
get: function (opts) {
|
|
|
|
|
if (!opts) { opts = TYPES.radio.defaultOpts; }
|
|
|
|
|
if (!Array.isArray(opts.values)) { return; }
|
|
|
|
|
var name = Util.uid();
|
|
|
|
|
var els = opts.values.map(function (data, i) {
|
|
|
|
|
var radio = UI.createRadio(name, 'cp-form-'+name+'-'+i,
|
|
|
|
|
data, false, { mark: { tabindex:1 } });
|
|
|
|
|
$(radio).find('input').data('val', data);
|
|
|
|
|
return radio;
|
|
|
|
|
});
|
|
|
|
|
var tag = h('div.radio-group.cp-form-type-radio', els);
|
|
|
|
|
var cursorGetter;
|
|
|
|
|
var setCursorGetter = function (f) { cursorGetter = f; };
|
|
|
|
|
return {
|
|
|
|
|
tag: tag,
|
|
|
|
|
getValue: function () {
|
|
|
|
|
var res;
|
|
|
|
|
els.some(function (el) {
|
|
|
|
|
var $i = $(el).find('input');
|
|
|
|
|
if (Util.isChecked($i)) {
|
|
|
|
|
res = $i.data('val');
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
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();
|
|
|
|
|
els.some(function (el) {
|
|
|
|
|
var $el = $(el).find('input');
|
|
|
|
|
if ($el.data('val') === val) {
|
|
|
|
|
$el.prop('checked', true);
|
|
|
|
|
return 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 (!answer || !answer.trim()) { return empty++; }
|
|
|
|
|
count[answer] = count[answer] || 0;
|
|
|
|
|
count[answer]++;
|
|
|
|
|
});
|
|
|
|
|
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-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) {
|
|
|
|
|
var $el = $(el);
|
|
|
|
|
var uid = $el.attr('data-uid');
|
|
|
|
|
$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 opts = structure.opts || TYPES.multiradio.defaultOpts;
|
|
|
|
|
var q = findItem(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) {
|
|
|
|
|
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) {
|
|
|
|
|
var $el = $(el);
|
|
|
|
|
var uid = $el.attr('data-uid');
|
|
|
|
|
res[uid] = [];
|
|
|
|
|
$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')
|
|
|
|
|
},
|
|
|
|
|
poll: {
|
|
|
|
|
defaultOpts: {
|
|
|
|
|
type: 'text', // Text or Days or Time
|
|
|
|
|
values: [1, 2, 3].map(function (i) {
|
|
|
|
|
return Messages._getKey('form_defaultOption', [i]);
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
get: function (opts, answers, username) {
|
|
|
|
|
if (!opts) { opts = TYPES.poll.defaultOpts; }
|
|
|
|
|
if (!Array.isArray(opts.values)) { return; }
|
|
|
|
|
|
|
|
|
|
var lines = makePollTable(answers, opts);
|
|
|
|
|
|
|
|
|
|
// Add form
|
|
|
|
|
var addLine = opts.values.map(function (data) {
|
|
|
|
|
var cell = h('div.cp-poll-cell.cp-form-poll-choice', [
|
|
|
|
|
h('i.fa.fa-times.cp-no'),
|
|
|
|
|
h('i.fa.fa-check.cp-yes'),
|
|
|
|
|
h('i.fa.fa-question.cp-maybe'),
|
|
|
|
|
]);
|
|
|
|
|
var $c = $(cell);
|
|
|
|
|
$c.data('option', data);
|
|
|
|
|
var val = 0;
|
|
|
|
|
$c.attr('data-value', val);
|
|
|
|
|
$c.click(function () {
|
|
|
|
|
val = (val+1)%3;
|
|
|
|
|
$c.attr('data-value', val);
|
|
|
|
|
});
|
|
|
|
|
cell._setValue = function (v) {
|
|
|
|
|
val = v;
|
|
|
|
|
$c.attr('data-value', val);
|
|
|
|
|
};
|
|
|
|
|
return cell;
|
|
|
|
|
});
|
|
|
|
|
// Name input
|
|
|
|
|
var nameInput = h('input', { value: username || Messages.anonymous });
|
|
|
|
|
addLine.unshift(h('div.cp-poll-cell', nameInput));
|
|
|
|
|
lines.push(h('div', addLine));
|
|
|
|
|
|
|
|
|
|
var tag = h('div.cp-form-type-poll', lines);
|
|
|
|
|
var $tag = $(tag);
|
|
|
|
|
|
|
|
|
|
var cursorGetter;
|
|
|
|
|
var setCursorGetter = function (f) { cursorGetter = f; };
|
|
|
|
|
return {
|
|
|
|
|
tag: tag,
|
|
|
|
|
getValue: function () {
|
|
|
|
|
var res = {};
|
|
|
|
|
var name = $(nameInput).val().trim() || Messages.anonymous;
|
|
|
|
|
$tag.find('.cp-form-poll-choice').each(function (i, el) {
|
|
|
|
|
var $el = $(el);
|
|
|
|
|
res[$el.data('option')] = $el.attr('data-value');
|
|
|
|
|
});
|
|
|
|
|
return {
|
|
|
|
|
name: name,
|
|
|
|
|
values: res
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
reset: function () {
|
|
|
|
|
$tag.find('.cp-form-poll-choice').attr('data-value', 0);
|
|
|
|
|
},
|
|
|
|
|
edit: function (cb, tmp) {
|
|
|
|
|
var v = Util.clone(opts);
|
|
|
|
|
return editOptions(v, setCursorGetter, cb, tmp);
|
|
|
|
|
},
|
|
|
|
|
getCursor: function () { return cursorGetter(); },
|
|
|
|
|
setValue: function (res) {
|
|
|
|
|
this.reset();
|
|
|
|
|
if (!res || !res.values || !res.name) { return; }
|
|
|
|
|
var val = res.values;
|
|
|
|
|
$(nameInput).val(res.name);
|
|
|
|
|
$tag.find('.cp-form-poll-choice').each(function (i, el) {
|
|
|
|
|
if (!el._setValue) { return; }
|
|
|
|
|
var $el = $(el);
|
|
|
|
|
el._setValue(val[$el.data('option')] || 0);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
printResults: function (answers, uid, form) {
|
|
|
|
|
var _answers = getBlockAnswers(answers, uid);
|
|
|
|
|
var lines = makePollTable(_answers, form[uid].opts);
|
|
|
|
|
return h('div.cp-form-type-poll', lines);
|
|
|
|
|
},
|
|
|
|
|
icon: h('i.cptools.cptools-poll')
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var renderResults = function (content, answers) {
|
|
|
|
|
var $container = $('div.cp-form-creator-results').empty();
|
|
|
|
|
var controls = h('div.cp-form-creator-results-controls');
|
|
|
|
|
var $controls = $(controls).appendTo($container);
|
|
|
|
|
var results = h('div.cp-form-creator-results-content');
|
|
|
|
|
var $results = $(results).appendTo($container);
|
|
|
|
|
|
|
|
|
|
var summary = true;
|
|
|
|
|
var form = content.form;
|
|
|
|
|
|
|
|
|
|
var switchMode = h('button.btn.btn-primary', Messages.form_showIndividual);
|
|
|
|
|
$controls.hide().append(switchMode);
|
|
|
|
|
|
|
|
|
|
var show = function (answers, header) {
|
|
|
|
|
var elements = content.order.map(function (uid) {
|
|
|
|
|
var block = form[uid];
|
|
|
|
|
var type = block.type;
|
|
|
|
|
var model = TYPES[type];
|
|
|
|
|
if (!model || !model.printResults) { return; }
|
|
|
|
|
var print = model.printResults(answers, uid, form);
|
|
|
|
|
|
|
|
|
|
var q = h('div.cp-form-block-question', block.q || Messages.form_default);
|
|
|
|
|
return h('div.cp-form-block', [
|
|
|
|
|
h('div.cp-form-block-type', [
|
|
|
|
|
TYPES[type].icon.cloneNode(),
|
|
|
|
|
h('span', Messages['form_type_'+type])
|
|
|
|
|
]),
|
|
|
|
|
q,
|
|
|
|
|
h('div.cp-form-block-content', print),
|
|
|
|
|
]);
|
|
|
|
|
});
|
|
|
|
|
$results.empty().append(elements);
|
|
|
|
|
if (header) { $results.prepend(header); }
|
|
|
|
|
};
|
|
|
|
|
show(answers);
|
|
|
|
|
|
|
|
|
|
if (APP.isEditor || APP.isAuditor) { $controls.show(); }
|
|
|
|
|
|
|
|
|
|
var $s = $(switchMode).click(function () {
|
|
|
|
|
$results.empty();
|
|
|
|
|
if (!summary) {
|
|
|
|
|
$s.text(Messages.form_showIndividual);
|
|
|
|
|
summary = true;
|
|
|
|
|
show(answers);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
summary = false;
|
|
|
|
|
$s.text(Messages.form_showSummary);
|
|
|
|
|
|
|
|
|
|
var origin, priv;
|
|
|
|
|
if (APP.common) {
|
|
|
|
|
var metadataMgr = APP.common.getMetadataMgr();
|
|
|
|
|
priv = metadataMgr.getPrivateData();
|
|
|
|
|
origin = priv.origin;
|
|
|
|
|
}
|
|
|
|
|
var getHref = function (hash) {
|
|
|
|
|
if (APP.common) {
|
|
|
|
|
return origin + Hash.hashToHref(hash, 'profile');
|
|
|
|
|
}
|
|
|
|
|
return '#';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var els = Object.keys(answers).map(function (curve) {
|
|
|
|
|
var obj = answers[curve];
|
|
|
|
|
var answer = obj.msg;
|
|
|
|
|
var date = new Date(obj.time).toLocaleString();
|
|
|
|
|
var text, warning, badge;
|
|
|
|
|
if (!answer._userdata || !answer._userdata.name) {
|
|
|
|
|
text = Messages._getKey('form_answerAnonymous', [date]);
|
|
|
|
|
} else {
|
|
|
|
|
var ud = answer._userdata;
|
|
|
|
|
var user;
|
|
|
|
|
if (ud.profile) {
|
|
|
|
|
if (priv && priv.friends[curve]) {
|
|
|
|
|
badge = h('span.cp-form-friend', [
|
|
|
|
|
h('i.fa.fa-address-book'),
|
|
|
|
|
Messages._getKey('isContact', [ud.name || Messages.anonymous])
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
user = h('a', {
|
|
|
|
|
href: getHref(ud.profile) // Only used visually
|
|
|
|
|
}, Util.fixHTML(ud.name || Messages.anonymous));
|
|
|
|
|
if (curve !== ud.curvePublic || 1) {
|
|
|
|
|
warning = h('span.cp-form-warning', Messages.form_answerWarning);
|
|
|
|
|
}
|
|
|
|
|
// XXX back
|
|
|
|
|
// XXX confirm curve and ud.curvePublic match
|
|
|
|
|
} else {
|
|
|
|
|
user = h('b', Util.fixHTML(ud.name || Messages.anonymous));
|
|
|
|
|
}
|
|
|
|
|
text = Messages._getKey('form_answerName', [user.outerHTML, date]);
|
|
|
|
|
}
|
|
|
|
|
var span = UI.setHTML(h('span'), text);
|
|
|
|
|
var viewButton = h('button.btn.btn-secondary.small', Messages.form_viewButton);
|
|
|
|
|
var div = h('div.cp-form-individual', [span, viewButton, warning, badge]);
|
|
|
|
|
$(viewButton).click(function () {
|
|
|
|
|
var res = {};
|
|
|
|
|
res[curve] = obj;
|
|
|
|
|
var back = h('button.btn.btn-secondary.small', Messages.form_backButton);
|
|
|
|
|
$(back).click(function () {
|
|
|
|
|
summary = true;
|
|
|
|
|
$s.click();
|
|
|
|
|
});
|
|
|
|
|
var header = h('div.cp-form-individual', [
|
|
|
|
|
span.cloneNode(true),
|
|
|
|
|
back
|
|
|
|
|
]);
|
|
|
|
|
show(res, header);
|
|
|
|
|
});
|
|
|
|
|
$(div).find('a').click(function (e) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
APP.common.openURL(Hash.hashToHref(ud.profile, 'profile'));
|
|
|
|
|
});
|
|
|
|
|
return div;
|
|
|
|
|
});
|
|
|
|
|
$results.append(els);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var getFormResults = function () {
|
|
|
|
|
if (!Array.isArray(APP.formBlocks)) { return; }
|
|
|
|
|
var results = {};
|
|
|
|
|
APP.formBlocks.forEach(function (data) {
|
|
|
|
|
if (!data.getValue) { return; }
|
|
|
|
|
results[data.uid] = data.getValue();
|
|
|
|
|
});
|
|
|
|
|
return results;
|
|
|
|
|
};
|
|
|
|
|
var makeFormControls = function (framework, content, update) {
|
|
|
|
|
var loggedIn = framework._.sfCommon.isLoggedIn();
|
|
|
|
|
var metadataMgr = framework._.cpNfInner.metadataMgr;
|
|
|
|
|
|
|
|
|
|
if (!loggedIn && !content.answers.anonymous) { return; }
|
|
|
|
|
|
|
|
|
|
var cbox;
|
|
|
|
|
cbox = UI.createCheckbox('cp-form-anonymous',
|
|
|
|
|
Messages.form_anonymousBox, true, { mark: { tabindex:1 } });
|
|
|
|
|
if (loggedIn) {
|
|
|
|
|
if (!content.answers.anonymous || APP.cantAnon) {
|
|
|
|
|
$(cbox).hide().find('input').attr('disabled', 'disabled').prop('checked', false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var send = h('button.cp-open.btn.btn-primary', update ? Messages.form_update : Messages.form_submit);
|
|
|
|
|
var reset = h('button.cp-open.btn.btn-danger-alt', Messages.form_reset);
|
|
|
|
|
$(reset).click(function () {
|
|
|
|
|
if (!Array.isArray(APP.formBlocks)) { return; }
|
|
|
|
|
APP.formBlocks.forEach(function (data) {
|
|
|
|
|
if (typeof(data.reset) === "function") { data.reset(); }
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
var $send = $(send).click(function () {
|
|
|
|
|
$send.attr('disabled', 'disabled');
|
|
|
|
|
var results = getFormResults();
|
|
|
|
|
if (!results) { return; }
|
|
|
|
|
|
|
|
|
|
var user = metadataMgr.getUserData();
|
|
|
|
|
if (!Util.isChecked($(cbox).find('input'))) {
|
|
|
|
|
results._userdata = loggedIn ? {
|
|
|
|
|
avatar: user.avatar,
|
|
|
|
|
name: user.name,
|
|
|
|
|
notifications: user.notifications,
|
|
|
|
|
curvePublic: user.curvePublic,
|
|
|
|
|
profile: user.profile
|
|
|
|
|
} : { name: user.name };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var sframeChan = framework._.sfCommon.getSframeChannel();
|
|
|
|
|
sframeChan.query('Q_FORM_SUBMIT', {
|
|
|
|
|
mailbox: content.answers,
|
|
|
|
|
results: results,
|
|
|
|
|
anonymous: !loggedIn || Util.isChecked($(cbox).find('input'))
|
|
|
|
|
}, function (err, data) {
|
|
|
|
|
$send.attr('disabled', 'disabled');
|
|
|
|
|
if (err || (data && data.error)) {
|
|
|
|
|
if (data.error === "EANSWERED") {
|
|
|
|
|
return void UI.warn(Messages.form_answered);
|
|
|
|
|
}
|
|
|
|
|
console.error(err || data.error);
|
|
|
|
|
return void UI.warn(Messages.error);
|
|
|
|
|
}
|
|
|
|
|
$send.removeAttr('disabled');
|
|
|
|
|
UI.alert(Messages.form_sent);
|
|
|
|
|
$send.text(Messages.form_update);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var viewResults;
|
|
|
|
|
if (content.answers.privateKey) {
|
|
|
|
|
viewResults = h('button.btn.btn-primary', [
|
|
|
|
|
h('span.cp-app-form-button-results', Messages.form_viewResults),
|
|
|
|
|
]);
|
|
|
|
|
var sframeChan = framework._.sfCommon.getSframeChannel();
|
|
|
|
|
var $v = $(viewResults).click(function () {
|
|
|
|
|
$v.attr('disabled', 'disabled');
|
|
|
|
|
sframeChan.query("Q_FORM_FETCH_ANSWERS", content.answers, function (err, obj) {
|
|
|
|
|
var answers = obj && obj.results;
|
|
|
|
|
if (answers) { APP.answers = answers; }
|
|
|
|
|
$v.removeAttr('disabled');
|
|
|
|
|
$('body').addClass('cp-app-form-results');
|
|
|
|
|
renderResults(content, answers);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (APP.isClosed) {
|
|
|
|
|
send = undefined;
|
|
|
|
|
reset = undefined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return h('div.cp-form-send-container', [
|
|
|
|
|
cbox ? h('div', cbox) : undefined,
|
|
|
|
|
send, reset, viewResults
|
|
|
|
|
]);
|
|
|
|
|
};
|
|
|
|
|
var updateForm = function (framework, content, editable, answers, temp) {
|
|
|
|
|
var $container = $('div.cp-form-creator-content');
|
|
|
|
|
if (!$container.length) { return; } // Not ready
|
|
|
|
|
|
|
|
|
|
var form = content.form;
|
|
|
|
|
|
|
|
|
|
APP.formBlocks = [];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var getFormCreator = function (uid) {
|
|
|
|
|
var full = !uid;
|
|
|
|
|
var idx = content.order.indexOf(uid);
|
|
|
|
|
var addControl = function (type) {
|
|
|
|
|
var btn = h('button.btn.small', {
|
|
|
|
|
title: full ? undefined : Messages['form_type_'+type]
|
|
|
|
|
}, [
|
|
|
|
|
(TYPES[type] || STATIC_TYPES[type]).icon.cloneNode(),
|
|
|
|
|
full ? h('span', Messages['form_type_'+type]) : undefined
|
|
|
|
|
]);
|
|
|
|
|
$(btn).click(function () {
|
|
|
|
|
var uid = Util.uid();
|
|
|
|
|
content.form[uid] = {
|
|
|
|
|
//q: Messages.form_default,
|
|
|
|
|
//opts: opts
|
|
|
|
|
type: type,
|
|
|
|
|
};
|
|
|
|
|
if (full) {
|
|
|
|
|
content.order.push(uid);
|
|
|
|
|
} else {
|
|
|
|
|
content.order.splice(idx, 0, uid);
|
|
|
|
|
}
|
|
|
|
|
framework.localChange();
|
|
|
|
|
updateForm(framework, content, true);
|
|
|
|
|
});
|
|
|
|
|
return btn;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var controls = Object.keys(TYPES).map(addControl);
|
|
|
|
|
var staticControls = Object.keys(STATIC_TYPES).map(addControl);
|
|
|
|
|
|
|
|
|
|
var buttons = h('div.cp-form-creator-control-inline', [
|
|
|
|
|
h('div.cp-form-creator-types', controls),
|
|
|
|
|
h('div.cp-form-creator-types', staticControls)
|
|
|
|
|
]);
|
|
|
|
|
var add = h('div', Messages.tag_add);
|
|
|
|
|
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');
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var inlineCls = full ? '-full' : '-inline'
|
|
|
|
|
return h('div.cp-form-creator-add'+inlineCls, [
|
|
|
|
|
h('div', add),
|
|
|
|
|
buttons
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var updateAddInline = function () {
|
|
|
|
|
console.log($container, $container.find('.cp-form-block'));
|
|
|
|
|
$container.find('.cp-form-creator-add-inline').remove();
|
|
|
|
|
$container.find('.cp-form-block').each(function (i, el) {
|
|
|
|
|
console.log(i, el);
|
|
|
|
|
if (i === 0) { return; }
|
|
|
|
|
var $el = $(el);
|
|
|
|
|
var uid = $el.attr('data-id');
|
|
|
|
|
$el.before(getFormCreator(uid));
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var elements = [];
|
|
|
|
|
content.order.forEach(function (uid, i) {
|
|
|
|
|
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);
|
|
|
|
|
if (!data) { return; }
|
|
|
|
|
data.uid = uid;
|
|
|
|
|
if (answers && answers[uid] && data.setValue) { data.setValue(answers[uid]); }
|
|
|
|
|
|
|
|
|
|
if (data.pageBreak && !editable) {
|
|
|
|
|
elements.push(data);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var q = h('div.cp-form-block-question', block.q || Messages.form_default);
|
|
|
|
|
var editButtons, editContainer;
|
|
|
|
|
|
|
|
|
|
APP.formBlocks.push(data);
|
|
|
|
|
|
|
|
|
|
if (editable) {
|
|
|
|
|
// Question
|
|
|
|
|
|
|
|
|
|
var inputQ = h('input', {
|
|
|
|
|
value: block.q || Messages.form_default
|
|
|
|
|
});
|
|
|
|
|
var $inputQ = $(inputQ);
|
|
|
|
|
var saveQ = h('button.btn.btn-primary.small', [
|
|
|
|
|
h('i.fa.fa-pencil.cp-form-edit'),
|
|
|
|
|
h('span.cp-form-edit', Messages.form_editQuestion),
|
|
|
|
|
h('i.fa.fa-floppy-o.cp-form-save'),
|
|
|
|
|
h('span.cp-form-save', Messages.settings_save)
|
|
|
|
|
]);
|
|
|
|
|
var dragHandle = h('i.fa.fa-arrows-v.cp-form-block-drag');
|
|
|
|
|
|
|
|
|
|
var $saveQ = $(saveQ).click(function () {
|
|
|
|
|
if (!$(q).hasClass('editing')) {
|
|
|
|
|
$(q).addClass('editing');
|
|
|
|
|
$inputQ.focus();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
var v = $inputQ.val();
|
|
|
|
|
if (!v || !v.trim()) { return void UI.warn(Messages.error); }
|
|
|
|
|
block.q = v.trim();
|
|
|
|
|
framework.localChange();
|
|
|
|
|
$saveQ.attr('disabled', 'disabled');
|
|
|
|
|
framework._.cpNfInner.chainpad.onSettle(function () {
|
|
|
|
|
$(q).removeClass('editing');
|
|
|
|
|
$saveQ.removeAttr('disabled');
|
|
|
|
|
$inputQ.blur();
|
|
|
|
|
UI.log(Messages.saved);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
var onCancelQ = function (e) {
|
|
|
|
|
if (e && e.relatedTarget && e.relatedTarget === saveQ) { return; }
|
|
|
|
|
$inputQ.val(block.q || Messages.form_default);
|
|
|
|
|
if (!e) { $inputQ.blur(); }
|
|
|
|
|
$(q).removeClass('editing');
|
|
|
|
|
};
|
|
|
|
|
$inputQ.keydown(function (e) {
|
|
|
|
|
if (e.which === 13) { return void $saveQ.click(); }
|
|
|
|
|
if (e.which === 27) { return void onCancelQ(); }
|
|
|
|
|
});
|
|
|
|
|
$inputQ.focus(function () {
|
|
|
|
|
$(q).addClass('editing');
|
|
|
|
|
});
|
|
|
|
|
$inputQ.blur(onCancelQ);
|
|
|
|
|
q = h('div.cp-form-input-block', [inputQ, saveQ, dragHandle]);
|
|
|
|
|
|
|
|
|
|
// Delete question
|
|
|
|
|
var edit;
|
|
|
|
|
var del = h('button.btn.btn-danger', [
|
|
|
|
|
h('i.fa.fa-trash-o'),
|
|
|
|
|
h('span', Messages.form_delete)
|
|
|
|
|
]);
|
|
|
|
|
UI.confirmButton(del, {
|
|
|
|
|
classes: 'btn-danger',
|
|
|
|
|
new: true
|
|
|
|
|
}, function () {
|
|
|
|
|
delete content.form[uid];
|
|
|
|
|
var idx = content.order.indexOf(uid);
|
|
|
|
|
content.order.splice(idx, 1);
|
|
|
|
|
$('.cp-form-block[data-id="'+uid+'"]').remove();
|
|
|
|
|
framework.localChange();
|
|
|
|
|
updateAddInline();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Values
|
|
|
|
|
if (data.edit) {
|
|
|
|
|
edit = h('button.btn.btn-primary.cp-form-edit-button', [
|
|
|
|
|
h('i.fa.fa-pencil'),
|
|
|
|
|
h('span', Messages.form_editBlock)
|
|
|
|
|
]);
|
|
|
|
|
editContainer = h('div');
|
|
|
|
|
var onSave = function (newOpts) {
|
|
|
|
|
data.editing = false;
|
|
|
|
|
if (!newOpts) { // Cancel edit
|
|
|
|
|
$(editContainer).empty();
|
|
|
|
|
$(editButtons).show();
|
|
|
|
|
$(data.tag).show();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
$(editContainer).empty();
|
|
|
|
|
block.opts = newOpts;
|
|
|
|
|
framework.localChange();
|
|
|
|
|
var $oldTag = $(data.tag);
|
|
|
|
|
framework._.cpNfInner.chainpad.onSettle(function () {
|
|
|
|
|
$(editButtons).show();
|
|
|
|
|
UI.log(Messages.saved);
|
|
|
|
|
var _answers = getBlockAnswers(APP.answers, uid);
|
|
|
|
|
data = model.get(newOpts, _answers);
|
|
|
|
|
if (!data) { data = {}; }
|
|
|
|
|
$oldTag.before(data.tag).remove();
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
var onEdit = function (tmp) {
|
|
|
|
|
data.editing = true;
|
|
|
|
|
$(data.tag).hide();
|
|
|
|
|
$(editContainer).append(data.edit(onSave, tmp));
|
|
|
|
|
$(editButtons).hide();
|
|
|
|
|
};
|
|
|
|
|
$(edit).click(function () {
|
|
|
|
|
onEdit();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// If we were editing this field, recover our unsaved changes
|
|
|
|
|
if (temp && temp[uid]) {
|
|
|
|
|
setTimeout(function () {
|
|
|
|
|
onEdit(temp[uid]);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
editButtons = h('div.cp-form-edit-buttons-container', [
|
|
|
|
|
edit, del
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
var editableCls = editable ? ".editable" : "";
|
|
|
|
|
elements.push(h('div.cp-form-block'+editableCls, {
|
|
|
|
|
'data-id':uid
|
|
|
|
|
}, [
|
|
|
|
|
isStatic ? undefined : q,
|
|
|
|
|
h('div.cp-form-block-content', [
|
|
|
|
|
data.tag,
|
|
|
|
|
editButtons
|
|
|
|
|
]),
|
|
|
|
|
editContainer
|
|
|
|
|
]));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (APP.isEditor) {
|
|
|
|
|
elements.push(getFormCreator());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var _content = elements;
|
|
|
|
|
if (!editable) {
|
|
|
|
|
_content = [];
|
|
|
|
|
var div = h('div.cp-form-page');
|
|
|
|
|
var pages = 1;
|
|
|
|
|
var wasPage = false;
|
|
|
|
|
elements.forEach(function (obj) {
|
|
|
|
|
if (obj && obj.pageBreak) {
|
|
|
|
|
if (wasPage) { return; } // Prevent double page break
|
|
|
|
|
_content.push(div);
|
|
|
|
|
pages++;
|
|
|
|
|
div = h('div.cp-form-page');
|
|
|
|
|
wasPage = true;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
wasPage = false;
|
|
|
|
|
$(div).append(obj);
|
|
|
|
|
});
|
|
|
|
|
_content.push(div);
|
|
|
|
|
|
|
|
|
|
if (pages > 1) {
|
|
|
|
|
var pageContainer = h('div.cp-form-page-container');
|
|
|
|
|
var $page = $(pageContainer);
|
|
|
|
|
_content.push(pageContainer);
|
|
|
|
|
var refreshPage = function (current) {
|
|
|
|
|
$page.empty();
|
|
|
|
|
if (!current || current < 1) { current = 1; }
|
|
|
|
|
if (current > pages) { current = pages; }
|
|
|
|
|
var left = h('button.btn.btn-secondary.small.cp-prev', [
|
|
|
|
|
h('i.fa.fa-chevron-left'),
|
|
|
|
|
h('span', Messages.form_page_prev)
|
|
|
|
|
]);
|
|
|
|
|
var state = h('span', Messages._getKey('form_page', [current, pages]));
|
|
|
|
|
var right = h('button.btn.btn-secondary.small.cp-next', [
|
|
|
|
|
h('span', Messages.form_page_next),
|
|
|
|
|
h('i.fa.fa-chevron-right'),
|
|
|
|
|
]);
|
|
|
|
|
$(left).click(function () { refreshPage(current - 1); });
|
|
|
|
|
$(right).click(function () { refreshPage(current + 1); });
|
|
|
|
|
$page.append([left, state, right]);
|
|
|
|
|
$container.find('.cp-form-page').hide();
|
|
|
|
|
$($container.find('.cp-form-page').get(current-1)).show();
|
|
|
|
|
if (current !== pages) {
|
|
|
|
|
$container.find('.cp-form-send-container').hide();
|
|
|
|
|
} else {
|
|
|
|
|
$container.find('.cp-form-send-container').show();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
setTimeout(refreshPage);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$container.empty().append(_content);
|
|
|
|
|
updateAddInline();
|
|
|
|
|
|
|
|
|
|
if (editable) {
|
|
|
|
|
Sortable.create($container[0], {
|
|
|
|
|
direction: "vertical",
|
|
|
|
|
filter: "input, button, .CodeMirror",
|
|
|
|
|
preventOnFilter: false,
|
|
|
|
|
store: {
|
|
|
|
|
set: function (s) {
|
|
|
|
|
content.order = s.toArray();
|
|
|
|
|
framework.localChange();
|
|
|
|
|
updateAddInline();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// In view mode, add "Submit" and "reset" buttons
|
|
|
|
|
$container.append(makeFormControls(framework, content, Boolean(answers)));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var getTempFields = function () {
|
|
|
|
|
if (!Array.isArray(APP.formBlocks)) { return; }
|
|
|
|
|
var temp = {};
|
|
|
|
|
APP.formBlocks.forEach(function (data) {
|
|
|
|
|
if (data.editing) {
|
|
|
|
|
var cursor = data.getCursor && data.getCursor();
|
|
|
|
|
temp[data.uid] = cursor;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return temp;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var andThen = function (framework) {
|
|
|
|
|
framework.start();
|
|
|
|
|
var evOnChange = Util.mkEvent();
|
|
|
|
|
var content = {};
|
|
|
|
|
|
|
|
|
|
APP.common = framework._.sfCommon;
|
|
|
|
|
var sframeChan = framework._.sfCommon.getSframeChannel();
|
|
|
|
|
var metadataMgr = framework._.cpNfInner.metadataMgr;
|
|
|
|
|
var user = metadataMgr.getUserData();
|
|
|
|
|
|
|
|
|
|
var priv = metadataMgr.getPrivateData();
|
|
|
|
|
APP.isEditor = Boolean(priv.form_public);
|
|
|
|
|
var $body = $('body');
|
|
|
|
|
|
|
|
|
|
var $toolbarContainer = $('#cp-toolbar');
|
|
|
|
|
var helpMenu = framework._.sfCommon.createHelpMenu(['text', 'pad']);
|
|
|
|
|
$toolbarContainer.after(helpMenu.menu);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var makeFormSettings = function () {
|
|
|
|
|
// Private / public status
|
|
|
|
|
var resultsType = h('div.cp-form-results-type-container');
|
|
|
|
|
var $results = $(resultsType);
|
|
|
|
|
var refreshPublic = function () {
|
|
|
|
|
$results.empty();
|
|
|
|
|
var makePublic = h('button.btn.btn-primary', Messages.form_makePublic);
|
|
|
|
|
if (content.answers.privateKey) { makePublic = undefined; }
|
|
|
|
|
var publicText = content.answers.privateKey ? Messages.form_isPublic : Messages.form_isPrivate;
|
|
|
|
|
$results.append(h('span.cp-form-results-type', publicText));
|
|
|
|
|
$results.append(makePublic);
|
|
|
|
|
var $makePublic = $(makePublic).click(function () {
|
|
|
|
|
UI.confirm(Messages.form_makePublicWarning, function (yes) {
|
|
|
|
|
if (!yes) { return; }
|
|
|
|
|
$makePublic.attr('disabled', 'disabled');
|
|
|
|
|
var priv = metadataMgr.getPrivateData();
|
|
|
|
|
content.answers.privateKey = priv.form_private;
|
|
|
|
|
framework.localChange();
|
|
|
|
|
framework._.cpNfInner.chainpad.onSettle(function () {
|
|
|
|
|
UI.log(Messages.saved);
|
|
|
|
|
refreshPublic();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
refreshPublic();
|
|
|
|
|
|
|
|
|
|
// Allow anonymous answers
|
|
|
|
|
var privacyContainer = h('div.cp-form-privacy-container');
|
|
|
|
|
var $privacy = $(privacyContainer);
|
|
|
|
|
var refreshPrivacy = function () {
|
|
|
|
|
$privacy.empty();
|
|
|
|
|
var anonymous = content.answers.anonymous;
|
|
|
|
|
var key = anonymous ? 'on' : 'off';
|
|
|
|
|
var button = h('button.btn.btn-secondary', Messages['form_anonymous_button_'+key]);
|
|
|
|
|
var $b = $(button).click(function () {
|
|
|
|
|
$b.attr('disabled', 'disabled');
|
|
|
|
|
content.answers.anonymous = !anonymous;
|
|
|
|
|
framework.localChange();
|
|
|
|
|
framework._.cpNfInner.chainpad.onSettle(function () {
|
|
|
|
|
UI.log(Messages.saved);
|
|
|
|
|
refreshPrivacy();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
$privacy.append(h('div.cp-form-status', Messages['form_anonymous_'+key]));
|
|
|
|
|
$privacy.append(h('div.cp-form-actions', button));
|
|
|
|
|
};
|
|
|
|
|
refreshPrivacy();
|
|
|
|
|
|
|
|
|
|
// End date / Closed state
|
|
|
|
|
var endDateContainer = h('div.cp-form-status-container');
|
|
|
|
|
var $endDate = $(endDateContainer);
|
|
|
|
|
var refreshEndDate = function () {
|
|
|
|
|
$endDate.empty();
|
|
|
|
|
|
|
|
|
|
var endDate = content.answers.endDate;
|
|
|
|
|
var date = new Date(endDate).toLocaleString();
|
|
|
|
|
var now = +new Date();
|
|
|
|
|
var text = Messages.form_isOpen;
|
|
|
|
|
var buttonTxt = Messages.form_setEnd;
|
|
|
|
|
if (endDate <= now) {
|
|
|
|
|
text = Messages._getKey('form_isClosed', [date]);
|
|
|
|
|
buttonTxt = Messages.form_open;
|
|
|
|
|
} else if (endDate > now) {
|
|
|
|
|
text = Messages._getKey('form_willClose', [date]);
|
|
|
|
|
buttonTxt = Messages.form_removeEnd;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var button = h('button.btn.btn-secondary', buttonTxt);
|
|
|
|
|
|
|
|
|
|
var $button = $(button).click(function () {
|
|
|
|
|
$button.attr('disabled', 'disabled');
|
|
|
|
|
// If there is an end date, remove it
|
|
|
|
|
if (endDate) {
|
|
|
|
|
delete content.answers.endDate;
|
|
|
|
|
framework.localChange();
|
|
|
|
|
refreshEndDate();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// Otherwise add it
|
|
|
|
|
var datePicker = h('input');
|
|
|
|
|
var picker = Flatpickr(datePicker, {
|
|
|
|
|
enableTime: true,
|
|
|
|
|
time_24hr: is24h,
|
|
|
|
|
dateFormat: dateFormat,
|
|
|
|
|
minDate: new Date()
|
|
|
|
|
});
|
|
|
|
|
var save = h('button.btn.btn-primary', Messages.settings_save);
|
|
|
|
|
$(save).click(function () {
|
|
|
|
|
var d = picker.parseDate(datePicker.value);
|
|
|
|
|
content.answers.endDate = +d;
|
|
|
|
|
framework.localChange();
|
|
|
|
|
refreshEndDate();
|
|
|
|
|
});
|
|
|
|
|
var confirmContent = h('div', [
|
|
|
|
|
h('div', Messages.form_setEnd),
|
|
|
|
|
h('div.cp-form-input-block', [datePicker, save]),
|
|
|
|
|
]);
|
|
|
|
|
$button.after(confirmContent);
|
|
|
|
|
$button.remove();
|
|
|
|
|
picker.open();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
$endDate.append(h('div.cp-form-status', text));
|
|
|
|
|
$endDate.append(h('div.cp-form-actions', button));
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
refreshEndDate();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var viewResults = h('button.btn.btn-primary', [
|
|
|
|
|
h('span.cp-app-form-button-results', Messages.form_viewResults),
|
|
|
|
|
h('span.cp-app-form-button-creator', Messages.form_viewCreator),
|
|
|
|
|
]);
|
|
|
|
|
var $v = $(viewResults).click(function () {
|
|
|
|
|
if ($body.hasClass('cp-app-form-results')) {
|
|
|
|
|
$body.removeClass('cp-app-form-results');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
$v.attr('disabled', 'disabled');
|
|
|
|
|
sframeChan.query("Q_FORM_FETCH_ANSWERS", {
|
|
|
|
|
channel: content.answers.channel,
|
|
|
|
|
validateKey: content.answers.validateKey,
|
|
|
|
|
publicKey: content.answers.publicKey
|
|
|
|
|
}, function (err, obj) {
|
|
|
|
|
var answers = obj && obj.results;
|
|
|
|
|
if (answers) { APP.answers = answers; }
|
|
|
|
|
$v.removeAttr('disabled');
|
|
|
|
|
$body.addClass('cp-app-form-results');
|
|
|
|
|
renderResults(content, answers);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
evOnChange.reg(refreshPublic);
|
|
|
|
|
evOnChange.reg(refreshPrivacy);
|
|
|
|
|
evOnChange.reg(refreshEndDate);
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
endDateContainer,
|
|
|
|
|
privacyContainer,
|
|
|
|
|
resultsType,
|
|
|
|
|
viewResults,
|
|
|
|
|
];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var checkIntegrity = function (getter) {
|
|
|
|
|
if (!content.order || !content.form) { return; }
|
|
|
|
|
var changed = false;
|
|
|
|
|
content.order.forEach(function (uid) {
|
|
|
|
|
if (!content.form[uid]) {
|
|
|
|
|
var idx = content.order.indexOf(uid);
|
|
|
|
|
content.order.splice(idx, 1);
|
|
|
|
|
changed = true;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
Object.keys(content.form).forEach(function (uid) {
|
|
|
|
|
var idx = content.order.indexOf(uid);
|
|
|
|
|
if (idx === -1) {
|
|
|
|
|
changed = true;
|
|
|
|
|
content.order.push(uid);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!getter && changed) { framework.localChange(); }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var makeFormCreator = function () {
|
|
|
|
|
|
|
|
|
|
var controlContainer;
|
|
|
|
|
if (APP.isEditor) {
|
|
|
|
|
var settings = makeFormSettings();
|
|
|
|
|
|
|
|
|
|
controlContainer = h('div.cp-form-creator-control', [
|
|
|
|
|
h('div.cp-form-creator-settings', settings),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var contentContainer = h('div.cp-form-creator-content');
|
|
|
|
|
var resultsContainer = h('div.cp-form-creator-results');
|
|
|
|
|
var div = h('div.cp-form-creator-container', [
|
|
|
|
|
controlContainer,
|
|
|
|
|
contentContainer,
|
|
|
|
|
resultsContainer
|
|
|
|
|
]);
|
|
|
|
|
return div;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var endDateEl = h('div.alert.alert-warning.cp-burn-after-reading');
|
|
|
|
|
var endDate;
|
|
|
|
|
var endDateTo;
|
|
|
|
|
var refreshEndDateBanner = function (force) {
|
|
|
|
|
if (APP.isEditor) { return; }
|
|
|
|
|
var _endDate = content.answers.endDate;
|
|
|
|
|
if (_endDate === endDate && !force) { return; }
|
|
|
|
|
endDate = _endDate;
|
|
|
|
|
var date = new Date(endDate).toLocaleString();
|
|
|
|
|
var text = Messages._getKey('form_isClosed', [date]);
|
|
|
|
|
if (endDate > +new Date()) {
|
|
|
|
|
text = Messages._getKey('form_willClose', [date]);
|
|
|
|
|
}
|
|
|
|
|
if ($('.cp-help-container').length && endDate) {
|
|
|
|
|
$(endDateEl).text(text);
|
|
|
|
|
$('.cp-help-container').before(endDateEl);
|
|
|
|
|
} else {
|
|
|
|
|
$(endDateEl).remove();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
APP.isClosed = endDate && endDate < (+new Date());
|
|
|
|
|
clearTimeout(endDateTo);
|
|
|
|
|
if (!APP.isClosed && endDate) {
|
|
|
|
|
setTimeout(function () {
|
|
|
|
|
refreshEndDateBanner(true);
|
|
|
|
|
$('.cp-form-send-container').find('.cp-open').remove();
|
|
|
|
|
},(endDate - +new Date() + 100));
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
framework.onReady(function () {
|
|
|
|
|
var priv = metadataMgr.getPrivateData();
|
|
|
|
|
|
|
|
|
|
if (APP.isEditor) {
|
|
|
|
|
if (!content.form) {
|
|
|
|
|
content.form = {};
|
|
|
|
|
framework.localChange();
|
|
|
|
|
}
|
|
|
|
|
if (!content.order) {
|
|
|
|
|
content.order = [];
|
|
|
|
|
framework.localChange();
|
|
|
|
|
}
|
|
|
|
|
if (!content.answers || !content.answers.channel || !content.answers.publicKey || !content.answers.validateKey) {
|
|
|
|
|
content.answers = {
|
|
|
|
|
channel: Hash.createChannelId(),
|
|
|
|
|
publicKey: priv.form_public,
|
|
|
|
|
validateKey: priv.form_answerValidateKey
|
|
|
|
|
};
|
|
|
|
|
framework.localChange();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sframeChan.event('EV_FORM_PIN', {channel: content.answers.channel});
|
|
|
|
|
|
|
|
|
|
var $container = $('#cp-app-form-container');
|
|
|
|
|
$container.append(makeFormCreator());
|
|
|
|
|
|
|
|
|
|
if (!content.answers || !content.answers.channel || !content.answers.publicKey || !content.answers.validateKey) {
|
|
|
|
|
return void UI.errorLoadingScreen(Messages.form_invalid);
|
|
|
|
|
}
|
|
|
|
|
// XXX fetch answers and
|
|
|
|
|
// * viewers ==> check if you've already answered and show form (new or edit)
|
|
|
|
|
// * editors ==> show schema and warn users if existing questions already have answers
|
|
|
|
|
|
|
|
|
|
var getResults = function (key) {
|
|
|
|
|
sframeChan.query("Q_FORM_FETCH_ANSWERS", {
|
|
|
|
|
channel: content.answers.channel,
|
|
|
|
|
validateKey: content.answers.validateKey,
|
|
|
|
|
publicKey: content.answers.publicKey,
|
|
|
|
|
privateKey: key
|
|
|
|
|
}, function (err, obj) {
|
|
|
|
|
var answers = obj && obj.results;
|
|
|
|
|
if (answers) { APP.answers = answers; }
|
|
|
|
|
$body.addClass('cp-app-form-results');
|
|
|
|
|
renderResults(content, answers);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
if (priv.form_auditorKey) {
|
|
|
|
|
APP.isAuditor = true;
|
|
|
|
|
getResults(priv.form_auditorKey);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (APP.isEditor) {
|
|
|
|
|
sframeChan.query("Q_FORM_FETCH_ANSWERS", {
|
|
|
|
|
channel: content.answers.channel,
|
|
|
|
|
validateKey: content.answers.validateKey,
|
|
|
|
|
publicKey: content.answers.publicKey
|
|
|
|
|
}, function (err, obj) {
|
|
|
|
|
var answers = obj && obj.results;
|
|
|
|
|
if (answers) { APP.answers = answers; }
|
|
|
|
|
checkIntegrity(false);
|
|
|
|
|
updateForm(framework, content, true);
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
refreshEndDateBanner();
|
|
|
|
|
|
|
|
|
|
var loggedIn = framework._.sfCommon.isLoggedIn();
|
|
|
|
|
if (!loggedIn && !content.answers.anonymous) {
|
|
|
|
|
UI.alert(Messages.form_anonymous_blocked);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If the results are public and there is at least one doodle, fetch the results now
|
|
|
|
|
if (content.answers.privateKey && Object.keys(content.form).some(function (uid) {
|
|
|
|
|
return content.form[uid].type === "poll";
|
|
|
|
|
})) {
|
|
|
|
|
sframeChan.query("Q_FORM_FETCH_ANSWERS", {
|
|
|
|
|
channel: content.answers.channel,
|
|
|
|
|
validateKey: content.answers.validateKey,
|
|
|
|
|
publicKey: content.answers.publicKey,
|
|
|
|
|
privateKey: content.answers.privateKey,
|
|
|
|
|
}, function (err, obj) {
|
|
|
|
|
var answers = obj && obj.results;
|
|
|
|
|
if (answers) { APP.answers = answers; }
|
|
|
|
|
|
|
|
|
|
if (obj && obj.noDriveAnswered) {
|
|
|
|
|
// No drive mode already answered: can't answer again
|
|
|
|
|
if (answers) {
|
|
|
|
|
$body.addClass('cp-app-form-results');
|
|
|
|
|
renderResults(content, answers);
|
|
|
|
|
} else {
|
|
|
|
|
return void UI.errorLoadingScreen(Messages.form_answered);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
checkIntegrity(false);
|
|
|
|
|
var myAnswers;
|
|
|
|
|
var curve1 = user.curvePublic;
|
|
|
|
|
var curve2 = obj && obj.myKey; // Anonymous answer key
|
|
|
|
|
if (answers) {
|
|
|
|
|
var myAnswersObj = answers[curve1] || answers[curve2] || undefined;
|
|
|
|
|
if (myAnswersObj) {
|
|
|
|
|
myAnswers = myAnswersObj.msg;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// If we have a non-anon answer, we can't answer anonymously later
|
|
|
|
|
if (answers[curve1]) { APP.cantAnon = true; }
|
|
|
|
|
updateForm(framework, content, false, myAnswers);
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sframeChan.query("Q_FETCH_MY_ANSWERS", {
|
|
|
|
|
channel: content.answers.channel,
|
|
|
|
|
validateKey: content.answers.validateKey,
|
|
|
|
|
publicKey: content.answers.publicKey
|
|
|
|
|
}, function (err, obj) {
|
|
|
|
|
if (obj && obj.error) {
|
|
|
|
|
if (obj.error === "EANSWERED") {
|
|
|
|
|
// No drive mode already answered: can't answer again
|
|
|
|
|
if (content.answers.privateKey) {
|
|
|
|
|
return void getResults(content.answers.privateKey);
|
|
|
|
|
}
|
|
|
|
|
// Here, we know results are private so we can use an error screen
|
|
|
|
|
return void UI.errorLoadingScreen(Messages.form_answered);
|
|
|
|
|
}
|
|
|
|
|
UI.warn(Messages.form_cantFindAnswers);
|
|
|
|
|
}
|
|
|
|
|
var answers;
|
|
|
|
|
if (obj && !obj.error) {
|
|
|
|
|
answers = obj;
|
|
|
|
|
// If we have a non-anon answer, we can't answer anonymously later
|
|
|
|
|
if (!obj._isAnon) { APP.cantAnon = true; }
|
|
|
|
|
}
|
|
|
|
|
checkIntegrity(false);
|
|
|
|
|
updateForm(framework, content, false, answers);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
framework.onContentUpdate(function (newContent) {
|
|
|
|
|
content = newContent;
|
|
|
|
|
evOnChange.fire();
|
|
|
|
|
refreshEndDateBanner();
|
|
|
|
|
var answers, temp;
|
|
|
|
|
if (!APP.isEditor) { answers = getFormResults(); }
|
|
|
|
|
else { temp = getTempFields(); }
|
|
|
|
|
updateForm(framework, content, APP.isEditor, answers, temp);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
framework.setContentGetter(function () {
|
|
|
|
|
checkIntegrity(true);
|
|
|
|
|
return content;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Framework.create({
|
|
|
|
|
toolbarContainer: '#cp-toolbar',
|
|
|
|
|
contentContainer: '#cp-app-form-editor',
|
|
|
|
|
}, andThen);
|
|
|
|
|
});
|