Skip to content

Commit 20d0f26

Browse files
committed
Add ability to copy attrs between entities by ref
- Add option to copyAttributeList to copy attributes as a linked reference - Added EMLUtilities file to store common EML functions, like getParentEML - Made sure that parentModel is passed down the line of sub-models correctly in eml attribute related models/collections - Fixed logic with listeners Issue #2652
1 parent be2fd23 commit 20d0f26

File tree

9 files changed

+358
-92
lines changed

9 files changed

+358
-92
lines changed

src/js/collections/metadata/eml/EMLAttributes.js

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -61,22 +61,34 @@ define([
6161
},
6262

6363
/**
64-
* Add an attribute to the collection. Will try to set the parentModel
65-
* if it is not already set.
66-
* @param {object} attributes - The model attributes of the new EML
67-
* attribute, optional. May include the parentModel
64+
* Add an attribute to the collection. Will try to set the parentModel if
65+
* it is not already set.
66+
* @param {object|EMLAttribute} [attributes] - The model attributes of the
67+
* new EML attribute. Should include the parentModel. Or an instance of
68+
* EMLAttribute.
6869
* @param {object} options - Options to pass to the add method
6970
* @returns {EMLAttribute} The newly added attribute
7071
*/
7172
addAttribute(attributes = {}, options = {}) {
73+
let modifiedAttrs = attributes;
7274
// A parent (entity) model is required for some of Attribute's methods
73-
const modifiedAttrs = { ...attributes };
74-
if (!modifiedAttrs.parentModel) {
75-
modifiedAttrs.parentModel = this.getParentModel();
76-
}
77-
if (!modifiedAttrs.xmlID) {
78-
modifiedAttrs.xmlID = DataONEObject.generateId();
75+
if (attributes instanceof EMLAttribute) {
76+
if (!modifiedAttrs.get("parentModel")) {
77+
modifiedAttrs.set("parentModel", this.getParentModel());
78+
}
79+
if (!modifiedAttrs.get("xmlID")) {
80+
modifiedAttrs.set("xmlID", DataONEObject.generateId());
81+
}
82+
} else {
83+
modifiedAttrs = { ...attributes };
84+
if (!modifiedAttrs.parentModel) {
85+
modifiedAttrs.parentModel = this.getParentModel();
86+
}
87+
if (!modifiedAttrs.xmlID) {
88+
modifiedAttrs.xmlID = DataONEObject.generateId();
89+
}
7990
}
91+
8092
return this.add(modifiedAttrs, options);
8193
},
8294

@@ -138,7 +150,7 @@ define([
138150
* number of attributes in the collection, the extra attributes will be
139151
* removed.
140152
* @param {string[]} names - An array of new attribute names
141-
* @param {EMLEntity} parentModel - The model that contains this
153+
* @param {EMLAttributeList} parentModel - The model that contains this
142154
* collection
143155
* @param {object} options - Options to pass to the add, remove, and set
144156
* methods

src/js/collections/metadata/eml/EMLEntities.js

Lines changed: 62 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ define([
77
"models/metadata/eml211/EMLEntity",
88
"models/metadata/eml211/EMLDataTable",
99
"models/metadata/eml211/EMLOtherEntity",
10-
], (Backbone, EMLEntity, EMLDataTable, EMLOtherEntity) => {
10+
"models/DataONEObject",
11+
], (Backbone, EMLEntity, EMLDataTable, EMLOtherEntity, DataONEObject) => {
1112
// The names of the nodes that are considered entities in EML
1213
const ENTITY_NODE_NAMES = [
1314
"otherEntity",
@@ -378,8 +379,14 @@ define([
378379
* thrown if any attributes from the source entity are invalid. If set to
379380
* false, only valid attributes will be copied over, and invalid
380381
* attributes will be ignored.
382+
* @param {boolean} [byRef] - If true, the attributes will be copied as a
383+
* reference to the source attributes. Meaning that changes to the source
384+
* attributes will affect the target attributes. If false (default), the
385+
* attributes will have the same values as the source attributes, but will
386+
* be a deep copy. This means that changes to the source attributes will
387+
* not affect the target attributes.
381388
*/
382-
copyAttributeList(source, targets, errorIfInvalid = true) {
389+
copyAttributeList(source, targets, errorIfInvalid = true, byRef = false) {
383390
if (!source || !targets) return;
384391

385392
const sourceAttrs = source.get("attributeList");
@@ -397,26 +404,64 @@ define([
397404
errors.join ? errors.join("\n") : "Invalid attribute list",
398405
);
399406
}
400-
const attrsStr = source.get("attributeList").serialize();
407+
401408
targets.forEach((target) => {
402-
const attrList = target.get("attributeList");
403-
const emlAttrs = attrList.get("emlAttributes");
404-
// Use remove rather than reset to trigger events
405-
emlAttrs.remove(emlAttrs.models);
406-
const attrDOM = new DOMParser().parseFromString(attrsStr, "text/xml");
407-
emlAttrs.add(attrDOM, { parse: true });
408-
// remove xmlID from the target attributes
409-
emlAttrs.each((attr) => attr.unset("xmlID"));
410-
// Reference to entity model required for attr & sub-models
411-
emlAttrs.each((attr) => attr.set("parentModel", target));
412-
413-
// Invalid to have both attributes and references
414-
if (attrList.hasReferences()) {
415-
attrList.removeReferences();
409+
if (!target) return;
410+
if (byRef) {
411+
this.copyAttributeListByRef(source, target);
412+
} else {
413+
this.deepCopyAttributeList(source, target);
416414
}
417415
});
418416
},
419417

418+
/**
419+
* Copy the attribute list from a source entity to a target entity by
420+
* reference. This means that changes to the source will be reflected in
421+
* the target.
422+
* @param {EMLEntity} source - The entity to copy attributes from
423+
* @param {EMLEntity} target - The entity to copy attributes to
424+
*/
425+
copyAttributeListByRef(source, target) {
426+
const sourceAttrList = source.get("attributeList");
427+
let sourceId = sourceAttrList.get("xmlID");
428+
429+
if (!sourceId) {
430+
sourceId = DataONEObject.generateId();
431+
sourceAttrList.set("xmlID", sourceId);
432+
}
433+
434+
// empty the target attribute list
435+
const targetAttrList = target.get("attributeList");
436+
targetAttrList.setReferences(sourceId);
437+
},
438+
439+
/**
440+
* Deep copy the attribute list from a source entity to a target entity.
441+
* This means that changes to the source will not be reflected in the
442+
* target.
443+
* @param {EMLEntity} source - The entity to copy attributes from
444+
* @param {EMLEntity} target - The entity to copy attributes to
445+
*/
446+
deepCopyAttributeList(source, target) {
447+
const attrsStr = source.get("attributeList").serialize();
448+
const attrList = target.get("attributeList");
449+
const emlAttrs = attrList.get("emlAttributes");
450+
// Use remove rather than reset to trigger events
451+
emlAttrs.remove(emlAttrs.models);
452+
const attrDOM = new DOMParser().parseFromString(attrsStr, "text/xml");
453+
emlAttrs.add(attrDOM, { parse: true });
454+
// remove xmlID from the target attributes
455+
emlAttrs.each((attr) => attr.unset("xmlID"));
456+
// Reference to entity model required for attr & sub-models
457+
emlAttrs.each((attr) => attr.set("parentModel", target));
458+
459+
// Invalid to have both attributes and references
460+
if (attrList.hasReferences()) {
461+
attrList.removeReferences();
462+
}
463+
},
464+
420465
/**
421466
* Get the attribute lists of all entities in the collection.
422467
* @returns {EMLAttributeList[]} The attribute lists of all entities in the

src/js/common/EMLUtilities.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"use strict";
2+
3+
define([], () => {
4+
/**
5+
* @namespace EMLUtilities
6+
* @description A generic utility object that contains functions used
7+
* throughout MetacatUI to perform useful functions related to EML, but not
8+
* used to store or manipulate any state about the application.
9+
* @type {object}
10+
* @since 0.0.0
11+
*/
12+
const EMLUtilities = /** @lends EMLUtilities.prototype */ {
13+
/**
14+
* Climbs up the model hierarchy until it finds the EML model
15+
* @param {Backbone.Model} model - The starting model
16+
* @param {number} [maxTries] - The maximum number of levels to climb
17+
* @returns {EML211|false} - Returns the EML 211 Model or null if not found
18+
*/
19+
getParentEML(model, maxTries = 6) {
20+
let emlModel = model.get("parentModel");
21+
let tries = 0;
22+
23+
while (emlModel && emlModel.type !== "EML" && tries < maxTries) {
24+
emlModel = emlModel.get("parentModel");
25+
tries += 1;
26+
}
27+
28+
return emlModel && emlModel.type === "EML" ? emlModel : false;
29+
},
30+
};
31+
32+
return EMLUtilities;
33+
});

0 commit comments

Comments
 (0)