ruoyi-vue-nocode/ruoyi-ui/ruoyi-bpmnjs/resources/properties-panel/factory/TableEntryFactory.js
2022-08-11 16:10:41 +08:00

339 lines
9.7 KiB
JavaScript

'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;
};