diff --git a/db/migrations/0160__attr_def_short.js b/db/migrations/0160__attr_def_short.js
index 091d12e4b..3e01a97e5 100644
--- a/db/migrations/0160__attr_def_short.js
+++ b/db/migrations/0160__attr_def_short.js
@@ -10,16 +10,16 @@ module.exports = () => {
tokens.push('promoted');
}
- if (obj.labelType) {
- tokens.push(obj.labelType);
- }
-
if (obj.multiplicityType === 'singlevalue') {
tokens.push('single');
} else if (obj.multiplicityType === 'multivalue') {
tokens.push('multi');
}
+ if (obj.labelType) {
+ tokens.push(obj.labelType);
+ }
+
if (obj.numberPrecision) {
tokens.push('precision='+obj.numberPrecision);
}
@@ -38,16 +38,16 @@ module.exports = () => {
tokens.push('promoted');
}
- if (obj.inverseRelation) {
- tokens.push('inverse=' + obj.inverseRelation);
- }
-
if (obj.multiplicityType === 'singlevalue') {
tokens.push('single');
} else if (obj.multiplicityType === 'multivalue') {
tokens.push('multi');
}
+ if (obj.inverseRelation) {
+ tokens.push('inverse=' + obj.inverseRelation);
+ }
+
const newValue = tokens.join(',');
sql.execute('UPDATE attributes SET value = ? WHERE attributeId = ?', [newValue, attr.attributeId]);
diff --git a/src/public/app/entities/attribute.js b/src/public/app/entities/attribute.js
index 9a4f937af..48dee04fe 100644
--- a/src/public/app/entities/attribute.js
+++ b/src/public/app/entities/attribute.js
@@ -1,3 +1,5 @@
+import promotedAttributeDefinitionParser from '../services/promoted_attribute_definition_parser.js';
+
class Attribute {
constructor(treeCache, row) {
this.treeCache = treeCache;
@@ -76,35 +78,7 @@ class Attribute {
}
getDefinition() {
- const tokens = this.value.split(',').map(t => t.trim());
- const defObj = {};
-
- for (const token of tokens) {
- if (token === 'promoted') {
- defObj.isPromoted = true;
- }
- else if (['text', 'number', 'boolean', 'date', 'url'].includes(token)) {
- defObj.labelType = token;
- }
- else if (['single', 'multi'].includes(token)) {
- defObj.multiplicity = token;
- }
- else if (token.startsWith('precision')) {
- const chunks = token.split('=');
-
- defObj.numberPrecision = parseInt(chunks[1]);
- }
- else if (token.startsWith('inverse')) {
- const chunks = token.split('=');
-
- defObj.inverseRelation = chunks[1];
- }
- else {
- console.log("Unrecognized attribute definition token:", token);
- }
- }
-
- return defObj;
+ return promotedAttributeDefinitionParser.parse(this.value);
}
}
diff --git a/src/public/app/services/promoted_attribute_definition_parser.js b/src/public/app/services/promoted_attribute_definition_parser.js
new file mode 100644
index 000000000..1fcc69751
--- /dev/null
+++ b/src/public/app/services/promoted_attribute_definition_parser.js
@@ -0,0 +1,35 @@
+function parse(value) {
+ const tokens = value.split(',').map(t => t.trim());
+ const defObj = {};
+
+ for (const token of tokens) {
+ if (token === 'promoted') {
+ defObj.isPromoted = true;
+ }
+ else if (['text', 'number', 'boolean', 'date', 'url'].includes(token)) {
+ defObj.labelType = token;
+ }
+ else if (['single', 'multi'].includes(token)) {
+ defObj.multiplicity = token;
+ }
+ else if (token.startsWith('precision')) {
+ const chunks = token.split('=');
+
+ defObj.numberPrecision = parseInt(chunks[1]);
+ }
+ else if (token.startsWith('inverse')) {
+ const chunks = token.split('=');
+
+ defObj.inverseRelation = chunks[1];
+ }
+ else {
+ console.log("Unrecognized attribute definition token:", token);
+ }
+ }
+
+ return defObj;
+}
+
+export default {
+ parse
+};
diff --git a/src/public/app/widgets/attribute_detail.js b/src/public/app/widgets/attribute_detail.js
index 0d3d4d2f1..b5d1860f9 100644
--- a/src/public/app/widgets/attribute_detail.js
+++ b/src/public/app/widgets/attribute_detail.js
@@ -4,6 +4,7 @@ import treeService from "../services/tree.js";
import linkService from "../services/link.js";
import BasicWidget from "./basic_widget.js";
import noteAutocompleteService from "../services/note_autocomplete.js";
+import promotedAttributeDefinitionParser from '../services/promoted_attribute_definition_parser.js';
const TPL = `
@@ -27,15 +28,15 @@ const TPL = `
margin-bottom: 10px;
}
- .attr-edit {
+ .attr-edit-table {
width: 100%;
}
- .attr-edit th {
+ .attr-edit-table th {
text-align: left;
}
- .attr-edit td input {
+ .attr-edit-table td input {
width: 100%;
}
@@ -53,59 +54,70 @@ const TPL = `
-
+
@@ -140,22 +152,37 @@ export default class AttributeDetailWidget extends BasicWidget {
this.$relatedNotesList = this.$relatedNotesContainer.find('.related-notes-list');
this.$relatedNotesMoreNotes = this.$relatedNotesContainer.find('.related-notes-more-notes');
- this.$attrEditName = this.$widget.find('.attr-edit-name');
- this.$attrEditName.on('keyup', () => this.updateParent());
+ this.$attrInputName = this.$widget.find('.attr-input-name');
+ this.$attrInputName.on('keyup', () => this.updateParent());
- this.$attrValueRow = this.$widget.find('.attr-value-row');
- this.$attrEditValue = this.$widget.find('.attr-edit-value');
- this.$attrEditValue.on('keyup', () => this.updateParent());
+ this.$attrRowValue = this.$widget.find('.attr-row-value');
+ this.$attrInputValue = this.$widget.find('.attr-input-value');
+ this.$attrInputValue.on('keyup', () => this.updateParent());
- this.$attrDefinitionPromoted = this.$widget.find('.attr-definition-promoted');
- this.$attrDefinitionMultiplicity = this.$widget.find('.attr-definition-multiplicity');
- this.$attrDefinitionLabelType = this.$widget.find('.attr-definition-label-type');
- this.$attrDefinitionInverseRelation = this.$widget.find('.attr-definition-inverse-relation');
+ this.$attrRowPromoted = this.$widget.find('.attr-row-promoted');
+ this.$attrInputPromoted = this.$widget.find('.attr-input-promoted');
+ this.$attrInputPromoted.on('change', () => this.updateDefinition());
- this.$attrTargetNoteRow = this.$widget.find('.attr-target-note-row');
- this.$attrEditTargetNote = this.$widget.find('.attr-edit-target-note');
+ this.$attrRowMultiplicity = this.$widget.find('.attr-row-multiplicity');
+ this.$attrInputMultiplicity = this.$widget.find('.attr-input-multiplicity');
+ this.$attrInputMultiplicity.on('change', () => this.updateDefinition());
- noteAutocompleteService.initNoteAutocomplete(this.$attrEditTargetNote)
+ this.$attrRowLabelType = this.$widget.find('.attr-row-label-type');
+ this.$attrInputLabelType = this.$widget.find('.attr-input-label-type');
+ this.$attrInputLabelType.on('change', () => this.updateDefinition());
+
+ this.$attrRowNumberPrecision = this.$widget.find('.attr-row-number-precision');
+ this.$attrInputNumberPrecision = this.$widget.find('.attr-input-number-precision');
+ this.$attrInputNumberPrecision.on('change', () => this.updateDefinition());
+
+ this.$attrRowInverseRelation = this.$widget.find('.attr-row-inverse-relation');
+ this.$attrInputInverseRelation = this.$widget.find('.attr-input-inverse-relation');
+ this.$attrInputInverseRelation.on('keyup', () => this.updateDefinition());
+
+ this.$attrRowTargetNote = this.$widget.find('.attr-row-target-note');
+ this.$attrInputTargetNote = this.$widget.find('.attr-input-target-note');
+
+ noteAutocompleteService.initNoteAutocomplete(this.$attrInputTargetNote)
.on('autocomplete:selected', (event, suggestion, dataset) => {
if (!suggestion.notePath) {
return false;
@@ -166,8 +193,8 @@ export default class AttributeDetailWidget extends BasicWidget {
this.triggerCommand('updateAttributeList', { attributes: this.allAttributes });
});
- this.$attrEditInheritable = this.$widget.find('.attr-edit-inheritable');
- this.$attrEditInheritable.on('change', () => this.updateParent());
+ this.$attrInputInheritable = this.$widget.find('.attr-input-inheritable');
+ this.$attrInputInheritable.on('change', () => this.updateParent());
this.$closeAttrDetailButton = this.$widget.find('.close-attr-detail-button');
this.$attrIsOwnedBy = this.$widget.find('.attr-is-owned-by');
@@ -191,9 +218,13 @@ export default class AttributeDetailWidget extends BasicWidget {
return;
}
- const attrType = this.getAttrType(attribute);
+ this.attrType = this.getAttrType(attribute);
- this.$title.text(ATTR_TITLES[attrType]);
+ const definition = this.attrType.endsWith('-definition')
+ ? promotedAttributeDefinitionParser.parse(attribute.value)
+ : {};
+
+ this.$title.text(ATTR_TITLES[this.attrType]);
this.allAttributes = allAttributes;
this.attribute = attribute;
@@ -246,33 +277,53 @@ export default class AttributeDetailWidget extends BasicWidget {
.append(await linkService.createNoteLink(attribute.noteId))
}
- this.$attrEditName
+ this.$attrInputName
.val(attribute.name)
.attr('readonly', () => !isOwned);
- this.$attrValueRow.toggle(attrType === 'label');
- this.$attrTargetNoteRow.toggle(attrType === 'relation');
+ this.$attrRowValue.toggle(this.attrType === 'label');
+ this.$attrRowTargetNote.toggle(this.attrType === 'relation');
- this.$attrDefinitionPromoted.toggle(['label-definition', 'relation-definition'].includes(attrType));
- this.$attrDefinitionMultiplicity.toggle(['label-definition', 'relation-definition'].includes(attrType));
- this.$attrDefinitionLabelType.toggle(attrType === 'label-definition');
- this.$attrDefinitionInverseRelation.toggle(attrType === 'relation-definition');
+ this.$attrRowPromoted.toggle(['label-definition', 'relation-definition'].includes(this.attrType));
+ this.$attrInputPromoted
+ .prop("checked", !!definition.isPromoted)
+ .attr('disabled', () => !isOwned);
+
+ this.$attrRowMultiplicity.toggle(['label-definition', 'relation-definition'].includes(this.attrType));
+ this.$attrInputMultiplicity
+ .val(definition.multiplicity)
+ .attr('disabled', () => !isOwned);
+
+ this.$attrRowLabelType.toggle(this.attrType === 'label-definition');
+ this.$attrInputLabelType
+ .val(definition.labelType)
+ .attr('disabled', () => !isOwned);
+
+ this.$attrRowNumberPrecision.toggle(this.attrType === 'label-definition' && definition.labelType === 'number');
+ this.$attrInputNumberPrecision
+ .val(definition.numberPrecision)
+ .attr('disabled', () => !isOwned);
+
+ this.$attrRowInverseRelation.toggle(this.attrType === 'relation-definition');
+ this.$attrInputInverseRelation
+ .val(definition.inverseRelation)
+ .attr('disabled', () => !isOwned);
if (attribute.type === 'label') {
- this.$attrEditValue
+ this.$attrInputValue
.val(attribute.value)
.attr('readonly', () => !isOwned);
}
else if (attribute.type === 'relation') {
const targetNote = await treeCache.getNote(attribute.value);
- this.$attrEditTargetNote
+ this.$attrInputTargetNote
.attr('readonly', () => !isOwned)
.val(targetNote ? targetNote.title : "")
.setSelectedNotePath(attribute.value);
}
- this.$attrEditInheritable
+ this.$attrInputInheritable
.prop("checked", !!attribute.isInheritable)
.attr('disabled', () => !isOwned);
@@ -301,9 +352,37 @@ export default class AttributeDetailWidget extends BasicWidget {
}
updateParent() {
- this.attribute.name = this.$attrEditName.val();
- this.attribute.value = this.$attrEditValue.val();
- this.attribute.isInheritable = this.$attrEditInheritable.is(":checked");
+ this.attribute.name = this.$attrInputName.val();
+ this.attribute.value = this.$attrInputValue.val();
+ this.attribute.isInheritable = this.$attrInputInheritable.is(":checked");
+
+ this.triggerCommand('updateAttributeList', { attributes: this.allAttributes });
+ }
+
+ updateDefinition() {
+ this.attribute.name = this.$attrInputName.val();
+ this.attribute.isInheritable = this.$attrInputInheritable.is(":checked");
+
+ const props = [];
+
+ if (this.$attrInputPromoted.is(":checked")) {
+ props.push("promoted");
+ }
+
+ props.push(this.$attrInputMultiplicity.val());
+
+ if (this.attrType === 'label-definition') {
+ props.push(this.$attrInputLabelType.val());
+ }
+ else if (this.attrType === 'relation-definition' && this.$attrInputInverseRelation.val().trim().length > 0) {
+ props.push("inverse=" + this.$attrInputInverseRelation.val());
+ }
+
+ this.$attrRowNumberPrecision.toggle(
+ this.attrType === 'label-definition'
+ && this.$attrInputLabelType.val() === 'number');
+
+ this.attribute.value = props.join(",");
this.triggerCommand('updateAttributeList', { attributes: this.allAttributes });
}
diff --git a/src/public/app/widgets/attribute_editor.js b/src/public/app/widgets/attribute_editor.js
index 80c0054be..1b3e5320e 100644
--- a/src/public/app/widgets/attribute_editor.js
+++ b/src/public/app/widgets/attribute_editor.js
@@ -234,20 +234,24 @@ export default class AttributeEditorWidget extends TabAwareWidget {
return;
}
- let type, name;
+ let type, name, value;
if (command === 'addNewLabel') {
type = 'label';
- name = 'fillName';
+ name = 'myLabel';
+ value = '';
} else if (command === 'addNewRelation') {
type = 'relation';
- name = 'fillName';
+ name = 'myRelation';
+ value = '';
} else if (command === 'addNewLabelDefinition') {
type = 'label';
- name = 'label:fillName';
+ name = 'label:myLabel';
+ value = 'promoted,single,text';
} else if (command === 'addNewRelationDefinition') {
type = 'label';
- name = 'relation:fillName';
+ name = 'relation:myRelation';
+ value = 'promoted,single';
} else {
return;
}
@@ -255,7 +259,7 @@ export default class AttributeEditorWidget extends TabAwareWidget {
attrs.push({
type,
name,
- value: '',
+ value,
isInheritable: false
});
@@ -347,7 +351,7 @@ export default class AttributeEditorWidget extends TabAwareWidget {
let matchedAttr = null;
for (const attr of parsedAttrs) {
- if (clickIndex >= attr.startIndex && clickIndex <= attr.endIndex) {
+ if (clickIndex > attr.startIndex && clickIndex <= attr.endIndex) {
matchedAttr = attr;
break;
}