Disable having duplicate nodes (except whilst typing)

This commit is contained in:
jos 2019-03-30 13:35:51 +01:00
parent 55c4c91370
commit 00baaacacc
4 changed files with 111 additions and 56 deletions

View File

@ -379,7 +379,7 @@ Node.prototype.setField = function(field, fieldEditable) {
*/
Node.prototype.getField = function() {
if (this.field === undefined) {
this._getDomField();
this._getDomField(false);
}
return this.field;
@ -1891,14 +1891,26 @@ Node.prototype._getDomField = function(silent) {
this.fieldInnerText = util.getInnerText(this.dom.field);
}
if (this.fieldInnerText != undefined) {
if (this.fieldInnerText !== undefined) {
try {
var field = this._unescapeHTML(this.fieldInnerText);
if (field !== this.field) {
var existingFieldNames = this.parent.getFieldNames(this);
var isDuplicate = existingFieldNames.indexOf(field) !== -1;
if (!isDuplicate) {
this.field = field;
this._debouncedOnChangeField();
}
else {
if (!silent) {
// fix duplicate field: change it into a unique name
this.field = util.findUniqueName(field, existingFieldNames);
this._debouncedOnChangeField();
}
}
}
}
catch (err) {
this.field = undefined;
@ -1936,54 +1948,6 @@ Node.prototype._updateDomDefault = function () {
}
};
/**
* Validate this node and all it's childs
* @return {Array.<{node: Node, error: {message: string}}>} Returns a list with duplicates
*/
Node.prototype.validate = function () {
var errors = [];
// find duplicate keys
if (this.type === 'object') {
var keys = {};
var duplicateKeys = [];
for (var i = 0; i < this.childs.length; i++) {
var child = this.childs[i];
if (keys.hasOwnProperty(child.field)) {
duplicateKeys.push(child.field);
}
keys[child.field] = true;
}
if (duplicateKeys.length > 0) {
errors = this.childs
.filter(function (node) {
return duplicateKeys.indexOf(node.field) !== -1;
})
.map(function (node) {
return {
node: node,
error: {
message: translate('duplicateKey') + ' "' + node.field + '"'
}
}
});
}
}
// recurse over the childs
if (this.childs) {
for (var i = 0; i < this.childs.length; i++) {
var e = this.childs[i].validate();
if (e.length > 0) {
errors = errors.concat(e);
}
}
}
return errors;
};
/**
* Clear the dom of the node
*/
@ -2958,6 +2922,13 @@ Node.prototype.onEvent = function (event) {
if (target == domField) {
switch (type) {
case 'blur':
this._getDomField(false);
this._updateDomField();
if (this.field) {
domField.innerHTML = this._escapeHTML(this.field);
}
break;
case 'change':
this._getDomField(true);
this._updateDomField();
@ -3462,6 +3433,25 @@ Node.prototype._showColorPicker = function () {
}
};
/**
* Get all field names of an object
* @param {Node} [excludeNode] Optional node to be excluded from the returned field names
* @return {string[]}
*/
Node.prototype.getFieldNames = function (excludeNode) {
if (this.type === 'object') {
return this.childs
.filter(function (child) {
return child !== excludeNode;
})
.map(function (child) {
return child.field;
});
}
return [];
}
/**
* Remove nodes
* @param {Node[] | Node} nodes
@ -3526,6 +3516,8 @@ Node.onDuplicate = function(nodes) {
var afterNode = lastNode;
var clones = nodes.map(function (node) {
var clone = node.clone();
var existingFieldNames = node.parent.getFieldNames();
clone.field = util.findUniqueName(node.field, existingFieldNames);
parent.insertAfter(clone, afterNode);
afterNode = clone;
return clone;

View File

@ -577,9 +577,6 @@ treemode.validate = function () {
var json = root.getValue();
// check for duplicate keys
var duplicateErrors = root.validate();
// execute JSON schema validation
var schemaErrors = [];
if (this.validateSchema) {
@ -611,7 +608,7 @@ treemode.validate = function () {
.then(function (customValidationErrors) {
// only apply when there was no other validation started whilst resolving async results
if (seq === me.validationSequence) {
var errorNodes = [].concat(duplicateErrors, schemaErrors, customValidationErrors || []);
var errorNodes = [].concat(schemaErrors, customValidationErrors || []);
me._renderValidationErrors(errorNodes);
}
})

View File

@ -1150,3 +1150,23 @@ exports.makeFieldTooltip = function (schema, locale) {
return tooltip;
}
/**
* Find a unique name. Suffix the name with ' (copy)', '(copy 2)', etc
* until a unique name is found
* @param {string} name
* @param {Array} existingPropNames Array with existing prop names
*/
exports.findUniqueName = function(name, existingPropNames) {
var strippedName = name.replace(/ \(copy( \d+)?\)$/, '')
var validName = strippedName
var i = 1
while (existingPropNames.indexOf(validName) !== -1) {
var copy = 'copy' + (i > 1 ? (' ' + i) : '')
validName = `${strippedName} (${copy})`
i++
}
return validName
}

View File

@ -224,5 +224,51 @@ describe('util', function () {
assert.strictEqual(util.makeFieldTooltip({examples: ['foo']}, 'pt-BR'), 'Exemplos\n"foo"');
});
});
it('should find a unique name', function () {
assert.strictEqual(util.findUniqueName('other', [
'a',
'b',
'c'
]), 'other')
assert.strictEqual(util.findUniqueName('b', [
'a',
'b',
'c'
]), 'b (copy)')
assert.strictEqual(util.findUniqueName('b', [
'a',
'b',
'c',
'b (copy)'
]), 'b (copy 2)')
assert.strictEqual(util.findUniqueName('b', [
'a',
'b',
'c',
'b (copy)',
'b (copy 2)'
]), 'b (copy 3)')
assert.strictEqual(util.findUniqueName('b (copy)', [
'a',
'b',
'b (copy)',
'b (copy 2)',
'c'
]), 'b (copy 3)')
assert.strictEqual(util.findUniqueName('b (copy 2)', [
'a',
'b',
'b (copy)',
'b (copy 2)',
'c'
]), 'b (copy 3)')
})
// TODO: thoroughly test all util methods
});