TableEntryFactory.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. 'use strict';
  2. var escapeHTML = require('../Utils').escapeHTML;
  3. var cmdHelper = require('../helper/CmdHelper');
  4. var domQuery = require('min-dom').query,
  5. domAttr = require('min-dom').attr,
  6. domClosest = require('min-dom').closest;
  7. var filter = require('lodash/filter'),
  8. forEach = require('lodash/forEach'),
  9. keys = require('lodash/keys');
  10. var domify = require('min-dom').domify;
  11. var entryFieldDescription = require('./EntryFieldDescription');
  12. var updateSelection = require('selection-update');
  13. var TABLE_ROW_DIV_SNIPPET = '<div class="bpp-field-wrapper bpp-table-row">';
  14. var DELETE_ROW_BUTTON_SNIPPET = '<button class="clear" data-action="deleteElement">' +
  15. '<span>X</span>' +
  16. '</button>';
  17. function createInputRowTemplate(properties, canRemove) {
  18. var template = TABLE_ROW_DIV_SNIPPET;
  19. template += createInputTemplate(properties, canRemove);
  20. template += canRemove ? DELETE_ROW_BUTTON_SNIPPET : '';
  21. template += '</div>';
  22. return template;
  23. }
  24. function createInputTemplate(properties, canRemove) {
  25. var columns = properties.length;
  26. var template = '';
  27. forEach(properties, function(prop) {
  28. template += '<input class="bpp-table-row-columns-' + columns + ' ' +
  29. (canRemove ? 'bpp-table-row-removable' : '') + '" ' +
  30. 'id="activiti-table-row-cell-input-value" ' +
  31. 'type="text" ' +
  32. 'name="' + escapeHTML(prop) + '" />';
  33. });
  34. return template;
  35. }
  36. function createLabelRowTemplate(labels) {
  37. var template = TABLE_ROW_DIV_SNIPPET;
  38. template += createLabelTemplate(labels);
  39. template += '</div>';
  40. return template;
  41. }
  42. function createLabelTemplate(labels) {
  43. var columns = labels.length;
  44. var template = '';
  45. forEach(labels, function(label) {
  46. template += '<label class="bpp-table-row-columns-' + columns + '">' + escapeHTML(label) + '</label>';
  47. });
  48. return template;
  49. }
  50. function pick(elements, properties) {
  51. return (elements || []).map(function(elem) {
  52. var newElement = {};
  53. forEach(properties, function(prop) {
  54. newElement[prop] = elem[prop] || '';
  55. });
  56. return newElement;
  57. });
  58. }
  59. function diff(element, node, values, oldValues, editable) {
  60. return filter(values, function(value, idx) {
  61. return !valueEqual(element, node, value, oldValues[idx], editable, idx);
  62. });
  63. }
  64. function valueEqual(element, node, value, oldValue, editable, idx) {
  65. if (value && !oldValue) {
  66. return false;
  67. }
  68. var allKeys = keys(value).concat(keys(oldValue));
  69. return allKeys.every(function(key) {
  70. var n = value[key] || undefined;
  71. var o = oldValue[key] || undefined;
  72. return !editable(element, node, key, idx) || n === o;
  73. });
  74. }
  75. function getEntryNode(node) {
  76. return domClosest(node, '[data-entry]', true);
  77. }
  78. function getContainer(node) {
  79. return domQuery('div[data-list-entry-container]', node);
  80. }
  81. function getSelection(node) {
  82. return {
  83. start: node.selectionStart,
  84. end: node.selectionEnd
  85. };
  86. }
  87. function setSelection(node, selection) {
  88. node.selectionStart = selection.start;
  89. node.selectionEnd = selection.end;
  90. }
  91. /**
  92. * @param {Object} options
  93. * @param {string} options.id
  94. * @param {string} options.description
  95. * @param {Array<string>} options.modelProperties
  96. * @param {Array<string>} options.labels
  97. * @param {Function} options.getElements - this callback function must return a list of business object items
  98. * @param {Function} options.removeElement
  99. * @param {Function} options.addElement
  100. * @param {Function} options.updateElement
  101. * @param {Function} options.editable
  102. * @param {Function} options.setControlValue
  103. * @param {Function} options.show
  104. *
  105. * @return {Object}
  106. */
  107. module.exports = function(options) {
  108. var id = options.id,
  109. modelProperties = options.modelProperties,
  110. labels = options.labels,
  111. description = options.description;
  112. var labelRow = createLabelRowTemplate(labels);
  113. var getElements = options.getElements;
  114. var removeElement = options.removeElement,
  115. canRemove = typeof removeElement === 'function';
  116. var addElement = options.addElement,
  117. canAdd = typeof addElement === 'function',
  118. addLabel = options.addLabel || 'Add Value';
  119. var updateElement = options.updateElement,
  120. canUpdate = typeof updateElement === 'function';
  121. var editable = options.editable || function() { return true; },
  122. setControlValue = options.setControlValue;
  123. var show = options.show,
  124. canBeShown = typeof show === 'function';
  125. var elements = function(element, node) {
  126. return pick(getElements(element, node), modelProperties);
  127. };
  128. var factory = {
  129. id: id,
  130. html: (canAdd ?
  131. '<div class="bpp-table-add-row" ' + (canBeShown ? 'data-show="show"' : '') + '>' +
  132. '<label>' + escapeHTML(addLabel) + '</label>' +
  133. '<button class="add" data-action="addElement"><span>+</span></button>' +
  134. '</div>' : '') +
  135. '<div class="bpp-table" data-show="showTable">' +
  136. '<div class="bpp-field-wrapper bpp-table-row">' +
  137. labelRow +
  138. '</div>' +
  139. '<div data-list-entry-container>' +
  140. '</div>' +
  141. '</div>' +
  142. // add description below table entry field
  143. (description ? entryFieldDescription(description) : ''),
  144. get: function(element, node) {
  145. var boElements = elements(element, node, this.__invalidValues);
  146. var invalidValues = this.__invalidValues;
  147. delete this.__invalidValues;
  148. forEach(invalidValues, function(value, idx) {
  149. var element = boElements[idx];
  150. forEach(modelProperties, function(prop) {
  151. element[prop] = value[prop];
  152. });
  153. });
  154. return boElements;
  155. },
  156. set: function(element, values, node) {
  157. var action = this.__action || {};
  158. delete this.__action;
  159. if (action.id === 'delete-element') {
  160. return removeElement(element, node, action.idx);
  161. }
  162. else if (action.id === 'add-element') {
  163. return addElement(element, node);
  164. }
  165. else if (canUpdate) {
  166. var commands = [],
  167. valuesToValidate = values;
  168. if (typeof options.validate !== 'function') {
  169. valuesToValidate = diff(element, node, values, elements(element, node), editable);
  170. }
  171. var self = this;
  172. forEach(valuesToValidate, function(value) {
  173. var validationError,
  174. idx = values.indexOf(value);
  175. if (typeof options.validate === 'function') {
  176. validationError = options.validate(element, value, node, idx);
  177. }
  178. if (!validationError) {
  179. var cmd = updateElement(element, value, node, idx);
  180. if (cmd) {
  181. commands.push(cmd);
  182. }
  183. } else {
  184. // cache invalid value in an object by index as key
  185. self.__invalidValues = self.__invalidValues || {};
  186. self.__invalidValues[idx] = value;
  187. // execute a command, which does not do anything
  188. commands.push(cmdHelper.updateProperties(element, {}));
  189. }
  190. });
  191. return commands;
  192. }
  193. },
  194. createListEntryTemplate: function(value, index, selectBox) {
  195. return createInputRowTemplate(modelProperties, canRemove);
  196. },
  197. addElement: function(element, node, event, scopeNode) {
  198. var template = domify(createInputRowTemplate(modelProperties, canRemove));
  199. var container = getContainer(node);
  200. container.appendChild(template);
  201. this.__action = {
  202. id: 'add-element'
  203. };
  204. return true;
  205. },
  206. deleteElement: function(element, node, event, scopeNode) {
  207. var container = getContainer(node);
  208. var rowToDelete = event.delegateTarget.parentNode;
  209. var idx = parseInt(domAttr(rowToDelete, 'data-index'), 10);
  210. container.removeChild(rowToDelete);
  211. this.__action = {
  212. id: 'delete-element',
  213. idx: idx
  214. };
  215. return true;
  216. },
  217. editable: function(element, rowNode, input, prop, value, idx) {
  218. var entryNode = domClosest(rowNode, '[data-entry]');
  219. return editable(element, entryNode, prop, idx);
  220. },
  221. show: function(element, entryNode, node, scopeNode) {
  222. entryNode = getEntryNode(entryNode);
  223. return show(element, entryNode, node, scopeNode);
  224. },
  225. showTable: function(element, entryNode, node, scopeNode) {
  226. entryNode = getEntryNode(entryNode);
  227. var elems = elements(element, entryNode);
  228. return elems && elems.length && (!canBeShown || show(element, entryNode, node, scopeNode));
  229. },
  230. validateListItem: function(element, value, node, idx) {
  231. if (typeof options.validate === 'function') {
  232. return options.validate(element, value, node, idx);
  233. }
  234. }
  235. };
  236. // Update/set the selection on the correct position.
  237. // It's the same code like for an input value in the PropertiesPanel.js.
  238. if (setControlValue) {
  239. factory.setControlValue = function(element, rowNode, input, prop, value, idx) {
  240. var entryNode = getEntryNode(rowNode);
  241. var isReadOnly = domAttr(input, 'readonly');
  242. var oldValue = input.value;
  243. var selection;
  244. // prevents input fields from having the value 'undefined'
  245. if (value === undefined) {
  246. value = '';
  247. }
  248. // when the attribute 'readonly' exists, ignore the comparison
  249. // with 'oldValue' and 'value'
  250. if (!!isReadOnly && oldValue === value) {
  251. return;
  252. }
  253. // update selection on undo/redo
  254. if (document.activeElement === input) {
  255. selection = updateSelection(getSelection(input), oldValue, value);
  256. }
  257. setControlValue(element, entryNode, input, prop, value, idx);
  258. if (selection) {
  259. setSelection(input, selection);
  260. }
  261. };
  262. }
  263. return factory;
  264. };