123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338 |
- 'use strict';
- var escapeHTML = require('../Utils').escapeHTML;
- var cmdHelper = require('../helper/CmdHelper');
- var domQuery = require('min-dom').query,
- domAttr = require('min-dom').attr,
- domClosest = require('min-dom').closest;
- var filter = require('lodash/filter'),
- forEach = require('lodash/forEach'),
- keys = require('lodash/keys');
- var domify = require('min-dom').domify;
- var entryFieldDescription = require('./EntryFieldDescription');
- var updateSelection = require('selection-update');
- var TABLE_ROW_DIV_SNIPPET = '<div class="bpp-field-wrapper bpp-table-row">';
- var DELETE_ROW_BUTTON_SNIPPET = '<button class="clear" data-action="deleteElement">' +
- '<span>X</span>' +
- '</button>';
- function createInputRowTemplate(properties, canRemove) {
- var template = TABLE_ROW_DIV_SNIPPET;
- template += createInputTemplate(properties, canRemove);
- template += canRemove ? DELETE_ROW_BUTTON_SNIPPET : '';
- template += '</div>';
- return template;
- }
- function createInputTemplate(properties, canRemove) {
- var columns = properties.length;
- var template = '';
- forEach(properties, function(prop) {
- template += '<input class="bpp-table-row-columns-' + columns + ' ' +
- (canRemove ? 'bpp-table-row-removable' : '') + '" ' +
- 'id="activiti-table-row-cell-input-value" ' +
- 'type="text" ' +
- 'name="' + escapeHTML(prop) + '" />';
- });
- return template;
- }
- function createLabelRowTemplate(labels) {
- var template = TABLE_ROW_DIV_SNIPPET;
- template += createLabelTemplate(labels);
- template += '</div>';
- return template;
- }
- function createLabelTemplate(labels) {
- var columns = labels.length;
- var template = '';
- forEach(labels, function(label) {
- template += '<label class="bpp-table-row-columns-' + columns + '">' + escapeHTML(label) + '</label>';
- });
- return template;
- }
- function pick(elements, properties) {
- return (elements || []).map(function(elem) {
- var newElement = {};
- forEach(properties, function(prop) {
- newElement[prop] = elem[prop] || '';
- });
- return newElement;
- });
- }
- function diff(element, node, values, oldValues, editable) {
- return filter(values, function(value, idx) {
- return !valueEqual(element, node, value, oldValues[idx], editable, idx);
- });
- }
- function valueEqual(element, node, value, oldValue, editable, idx) {
- if (value && !oldValue) {
- return false;
- }
- var allKeys = keys(value).concat(keys(oldValue));
- return allKeys.every(function(key) {
- var n = value[key] || undefined;
- var o = oldValue[key] || undefined;
- return !editable(element, node, key, idx) || n === o;
- });
- }
- function getEntryNode(node) {
- return domClosest(node, '[data-entry]', true);
- }
- function getContainer(node) {
- return domQuery('div[data-list-entry-container]', node);
- }
- function getSelection(node) {
- return {
- start: node.selectionStart,
- end: node.selectionEnd
- };
- }
- function setSelection(node, selection) {
- node.selectionStart = selection.start;
- node.selectionEnd = selection.end;
- }
- /**
- * @param {Object} options
- * @param {string} options.id
- * @param {string} options.description
- * @param {Array<string>} options.modelProperties
- * @param {Array<string>} options.labels
- * @param {Function} options.getElements - this callback function must return a list of business object items
- * @param {Function} options.removeElement
- * @param {Function} options.addElement
- * @param {Function} options.updateElement
- * @param {Function} options.editable
- * @param {Function} options.setControlValue
- * @param {Function} options.show
- *
- * @return {Object}
- */
- module.exports = function(options) {
- var id = options.id,
- modelProperties = options.modelProperties,
- labels = options.labels,
- description = options.description;
- var labelRow = createLabelRowTemplate(labels);
- var getElements = options.getElements;
- var removeElement = options.removeElement,
- canRemove = typeof removeElement === 'function';
- var addElement = options.addElement,
- canAdd = typeof addElement === 'function',
- addLabel = options.addLabel || 'Add Value';
- var updateElement = options.updateElement,
- canUpdate = typeof updateElement === 'function';
- var editable = options.editable || function() { return true; },
- setControlValue = options.setControlValue;
- var show = options.show,
- canBeShown = typeof show === 'function';
- var elements = function(element, node) {
- return pick(getElements(element, node), modelProperties);
- };
- var factory = {
- id: id,
- html: (canAdd ?
- '<div class="bpp-table-add-row" ' + (canBeShown ? 'data-show="show"' : '') + '>' +
- '<label>' + escapeHTML(addLabel) + '</label>' +
- '<button class="add" data-action="addElement"><span>+</span></button>' +
- '</div>' : '') +
- '<div class="bpp-table" data-show="showTable">' +
- '<div class="bpp-field-wrapper bpp-table-row">' +
- labelRow +
- '</div>' +
- '<div data-list-entry-container>' +
- '</div>' +
- '</div>' +
- // add description below table entry field
- (description ? entryFieldDescription(description) : ''),
- get: function(element, node) {
- var boElements = elements(element, node, this.__invalidValues);
- var invalidValues = this.__invalidValues;
- delete this.__invalidValues;
- forEach(invalidValues, function(value, idx) {
- var element = boElements[idx];
- forEach(modelProperties, function(prop) {
- element[prop] = value[prop];
- });
- });
- return boElements;
- },
- set: function(element, values, node) {
- var action = this.__action || {};
- delete this.__action;
- if (action.id === 'delete-element') {
- return removeElement(element, node, action.idx);
- }
- else if (action.id === 'add-element') {
- return addElement(element, node);
- }
- else if (canUpdate) {
- var commands = [],
- valuesToValidate = values;
- if (typeof options.validate !== 'function') {
- valuesToValidate = diff(element, node, values, elements(element, node), editable);
- }
- var self = this;
- forEach(valuesToValidate, function(value) {
- var validationError,
- idx = values.indexOf(value);
- if (typeof options.validate === 'function') {
- validationError = options.validate(element, value, node, idx);
- }
- if (!validationError) {
- var cmd = updateElement(element, value, node, idx);
- if (cmd) {
- commands.push(cmd);
- }
- } else {
- // cache invalid value in an object by index as key
- self.__invalidValues = self.__invalidValues || {};
- self.__invalidValues[idx] = value;
- // execute a command, which does not do anything
- commands.push(cmdHelper.updateProperties(element, {}));
- }
- });
- return commands;
- }
- },
- createListEntryTemplate: function(value, index, selectBox) {
- return createInputRowTemplate(modelProperties, canRemove);
- },
- addElement: function(element, node, event, scopeNode) {
- var template = domify(createInputRowTemplate(modelProperties, canRemove));
- var container = getContainer(node);
- container.appendChild(template);
- this.__action = {
- id: 'add-element'
- };
- return true;
- },
- deleteElement: function(element, node, event, scopeNode) {
- var container = getContainer(node);
- var rowToDelete = event.delegateTarget.parentNode;
- var idx = parseInt(domAttr(rowToDelete, 'data-index'), 10);
- container.removeChild(rowToDelete);
- this.__action = {
- id: 'delete-element',
- idx: idx
- };
- return true;
- },
- editable: function(element, rowNode, input, prop, value, idx) {
- var entryNode = domClosest(rowNode, '[data-entry]');
- return editable(element, entryNode, prop, idx);
- },
- show: function(element, entryNode, node, scopeNode) {
- entryNode = getEntryNode(entryNode);
- return show(element, entryNode, node, scopeNode);
- },
- showTable: function(element, entryNode, node, scopeNode) {
- entryNode = getEntryNode(entryNode);
- var elems = elements(element, entryNode);
- return elems && elems.length && (!canBeShown || show(element, entryNode, node, scopeNode));
- },
- validateListItem: function(element, value, node, idx) {
- if (typeof options.validate === 'function') {
- return options.validate(element, value, node, idx);
- }
- }
- };
- // Update/set the selection on the correct position.
- // It's the same code like for an input value in the PropertiesPanel.js.
- if (setControlValue) {
- factory.setControlValue = function(element, rowNode, input, prop, value, idx) {
- var entryNode = getEntryNode(rowNode);
- var isReadOnly = domAttr(input, 'readonly');
- var oldValue = input.value;
- var selection;
- // prevents input fields from having the value 'undefined'
- if (value === undefined) {
- value = '';
- }
- // when the attribute 'readonly' exists, ignore the comparison
- // with 'oldValue' and 'value'
- if (!!isReadOnly && oldValue === value) {
- return;
- }
- // update selection on undo/redo
- if (document.activeElement === input) {
- selection = updateSelection(getSelection(input), oldValue, value);
- }
- setControlValue(element, entryNode, input, prop, value, idx);
- if (selection) {
- setSelection(input, selection);
- }
- };
- }
- return factory;
- };
|