diff --git a/src/js/collections/metadata/eml/EMLDistributions.js b/src/js/collections/metadata/eml/EMLDistributions.js
new file mode 100644
index 000000000..8ba696865
--- /dev/null
+++ b/src/js/collections/metadata/eml/EMLDistributions.js
@@ -0,0 +1,112 @@
+"use strict";
+
+define(["backbone", "models/metadata/eml211/EMLDistribution"], function (
+ Backbone,
+ EMLDistribution
+) {
+ /**
+ * @class EMLDistributions
+ * @classdesc A collection of EMLDistributions.
+ * @classcategory Collections/Metadata/EML
+ * @since x.x.x
+ */
+ var EMLDistributions = Backbone.Collection.extend(
+ /** @lends EMLDistributions.prototype */
+ {
+ /**
+ * The reference to the model class that this collection is made of.
+ * @type EMLDistribution
+ */
+ model: EMLDistribution,
+
+ /**
+ * Find the distribution that has all of the matching attributes. This
+ * will return true if the distribution has all of the attributes, even if
+ * it has more attributes than the ones passed in. Only the first matching
+ * distribution will be returned.
+ * @param {object} attributes - The attributes to match
+ * @param {boolean} partialMatch - If true, then the attribute values in
+ * the distribution models only need to partially match the attribute
+ * values given. If false, then the attributes must match exactly.
+ * @return {EMLDistribution|undefined} The matching distribution, or
+ * undefined if there is no match.
+ */
+ findByAttributes: function (attributes, partialMatch = false) {
+ return this.find((d) => {
+ return Object.keys(attributes).every((key) => {
+ const val = d.get(key);
+ if (partialMatch) {
+ return val.includes(attributes[key]);
+ }
+ return val === attributes[key];
+ });
+ });
+ },
+
+ /**
+ * Remove the distribution that has all of the matching attributes. This
+ * will remove the first distribution that has all of the attributes, even
+ * if it has more attributes than the ones passed in.
+ * @param {object} attributes - The attributes to match
+ * @param {boolean} partialMatch - If true, then the attribute values in
+ * the distribution models only need to partially match the attribute
+ * values given. If false, then the attributes must match exactly.
+ * @return {EMLDistribution|undefined} The matching distribution, or
+ * undefined if there is no match.
+ */
+ removeByAttributes: function (attributes, partialMatch = false) {
+ const dist = this.findByAttributes(attributes, partialMatch);
+ if (dist) {
+ return this.remove(dist);
+ }
+ },
+
+ /**
+ * Make sure that the EML dataset element has a distribution node with the
+ * location where the data package can be viewed. This will be either the
+ * view URL for the member node being used or the DOI.org URL if the
+ * dataset has one. This method will look for the old distribution URL and
+ * update it if it exists, or add a new distribution node if it doesn't.
+ * @param {string} url - The URL to add to the dataset distribution
+ * @param {string[]} oldIDs - The old PIDs, seriesIds, or current PID to
+ * remove from the dataset distribution
+ * @return {EMLDistribution} The distribution that was added or updated
+ */
+ addDatasetDistributionURL: function (url, oldIDs = []) {
+ if (!url) {
+ console.warn("No URL given to addDatasetDistributionURL");
+ return;
+ }
+
+ // Reference to this collection
+ const dists = this;
+ // The URL function used for dataset distribution URLs
+ const func = "information";
+
+ // Remove any distribution models with the old PID, seriesId, or current
+ // PID in the URL (only if the URL function is "information")
+ if (dists.length && oldIDs.length) {
+ oldIDs.forEach((url) => {
+ dists.removeByAttributes({ url: id, urlFunction: func }, true);
+ });
+ }
+
+ // Add a new distribution with the view URL
+ return dists.add({ url: url, urlFunction: urlFunction });
+ },
+
+ /**
+ * Update the DOM for each distribution in this collection with the
+ * current model state.
+ * @return {object[]} An array of jQuery DOM objects for each distribution
+ * in this collection.
+ */
+ updateDOMs: function (doms) {
+ const objectDOMs = this.map((model) => model.updateDOM());
+ return objectDOMs;
+ },
+ }
+ );
+
+ return EMLDistributions;
+});
diff --git a/src/js/models/metadata/eml211/EML211.js b/src/js/models/metadata/eml211/EML211.js
index 35dab46df..c83c08055 100644
--- a/src/js/models/metadata/eml211/EML211.js
+++ b/src/js/models/metadata/eml211/EML211.js
@@ -7,7 +7,7 @@ define(['jquery', 'underscore', 'backbone', 'uuid',
'models/metadata/eml211/EMLKeywordSet',
'models/metadata/eml211/EMLTaxonCoverage',
'models/metadata/eml211/EMLTemporalCoverage',
- 'models/metadata/eml211/EMLDistribution',
+ 'collections/metadata/eml/EMLDistributions',
'models/metadata/eml211/EMLEntity',
'models/metadata/eml211/EMLDataTable',
'models/metadata/eml211/EMLOtherEntity',
@@ -19,7 +19,7 @@ define(['jquery', 'underscore', 'backbone', 'uuid',
'models/metadata/eml211/EMLAnnotation'],
function($, _, Backbone, uuid, Units, ScienceMetadata, DataONEObject,
EMLGeoCoverage, EMLKeywordSet, EMLTaxonCoverage, EMLTemporalCoverage,
- EMLDistribution, EMLEntity, EMLDataTable, EMLOtherEntity, EMLParty,
+ EMLDistributions, EMLEntity, EMLDataTable, EMLOtherEntity, EMLParty,
EMLProject, EMLText, EMLMethods, EMLAnnotations, EMLAnnotation) {
/**
@@ -55,7 +55,7 @@ define(['jquery', 'underscore', 'backbone', 'uuid',
keywordSets: [], //array of EMLKeywordSet objects
additionalInfo: [],
intellectualRights: "This work is dedicated to the public domain under the Creative Commons Universal 1.0 Public Domain Dedication. To view a copy of this dedication, visit https://creativecommons.org/publicdomain/zero/1.0/.",
- distribution: [], // array of EMLDistribution objects
+ distributions: new EMLDistributions(), // EMLDistribution collection
geoCoverage : [], //an array for EMLGeoCoverages
temporalCoverage : [], //an array of EMLTempCoverage models
taxonCoverage : [], //an array of EMLTaxonCoverages
@@ -513,14 +513,18 @@ define(['jquery', 'underscore', 'backbone', 'uuid',
type: attributeName
}));
}
- //EML Distribution modules are stored in EMLDistribution models
- else if(_.contains(emlDistribution, thisNode.localName)) {
- if(typeof modelJSON[thisNode.localName] == "undefined") modelJSON[thisNode.localName] = [];
-
- modelJSON[thisNode.localName].push(new EMLDistribution({
- objectDOM: thisNode,
- parentModel: model
- }, { parse: true }));
+ //EML Distribution info is stored in an EMLDistribution collection
+ else if (_.contains(emlDistribution, thisNode.localName)) {
+ // Create the collection if it doesn't exist
+ const distName = thisNode.localName
+ let distCollection = modelJSON[distName]
+ if (!distCollection) {
+ modelJSON[distName] = distCollection = new EMLDistributions();
+ }
+ // Add the distribution to the collection
+ const distAttrs = { objectDOM: thisNode, parentModel: model }
+ const distOpts = { parse: true }
+ distCollection.add(distAttrs, distOpts);
}
//The EML Project is stored in the EMLProject model
else if(thisNode.localName == "project"){
@@ -1029,21 +1033,15 @@ define(['jquery', 'underscore', 'backbone', 'uuid',
}
// Serialize the distribution
- const distributions = this.get('distribution');
- if (distributions && distributions.length > 0) {
- // Remove existing nodes
- datasetNode.children('distribution').remove();
- // Get the updated DOMs
- const distributionDOMs = distributions.map(d => d.updateDOM());
- // Insert the updated DOMs in their correct positions
- distributionDOMs.forEach((dom, i) => {
- const insertAfter = this.getEMLPosition(eml, 'distribution');
- if (insertAfter) {
- insertAfter.after(dom);
- } else {
- datasetNode.append(dom);
- }
- });
+ datasetNode.children('distribution').remove();
+ const distributionDOMs = this.getDistributions().updateDOMs();
+ if (distributionDOMs.length) {
+ const insertAfter = this.getEMLPosition(eml, 'distribution');
+ if (insertAfter) {
+ insertAfter.after(distributionDOMs);
+ } else {
+ datasetNode.append(distributionDOMs);
+ }
}
//Detach the project elements from the DOM
@@ -1438,44 +1436,24 @@ define(['jquery', 'underscore', 'backbone', 'uuid',
},
/**
- * Adds a new EMLDistribution model to the distribution array
- * @param {object} attributes - The attributes to set on the new
- * EMLDistribution model
- * @param {object} options - Options to pass to the new EMLDistribution
- * model
+ * Get the distribution model collection on this EML model. If there is no
+ * distribution collection, then one is created, set on the EML model, and
+ * returned.
+ * @return {EMLDistributions} The distribution collection on this EML model
*/
- addDistribution: function (attributes, options) {
+ getDistributions: function () {
try {
- const distributions = this.get('distribution') || [];
- const newDistribution = new EMLDistribution(attributes, options);
- distributions.push(newDistribution);
- this.set('distribution', distributions);
+ let distributions = this.get('distribution');
+ if (!distributions) {
+ distributions = new EMLDistributions();
+ this.set('distribution', distributions);
+ }
+ return distributions;
} catch (e) {
- console.log("Couldn't add a distribution to the EML model", e);
+ console.log("Couldn't get the distributions from the EML model", e);
}
},
- /**
- * Find the distribution that has all of the matching attributes. This will
- * return true if the distribution has all of the attributes, even if it
- * has more attributes than the ones passed in.
- * @param {object} attributes - The attributes to match
- * @param {boolean} partialMatch - If true, then the attributes only need
- * to partially match. If false, then the attributes must match exactly.
- */
- findDistribution: function (attributes, partialMatch = false) {
- const distributions = this.get('distribution') || [];
- return distributions.find(d => {
- return Object.keys(attributes).every(key => {
- const val = d.get(key);
- if (partialMatch) {
- return val.includes(attributes[key]);
- }
- return val === attributes[key];
- });
- });
- },
-
/**
* Make sure that the EML dataset element has a distribution node with the
* location where the data package can be viewed. This will be either the
@@ -1484,38 +1462,16 @@ define(['jquery', 'underscore', 'backbone', 'uuid',
* if it exists, or add a new distribution node if it doesn't.
*/
addDatasetDistributionURL: function () {
- const model = this;
- const oldPid = this.get('oldPid');
- const newPid = this.get('id');
- const seriesId = this.get('seriesId');
- const IDs = [oldPid, newPid, seriesId]
-
- // Remove any distribution models with the old PID, seriesId, or current
- // PID in the URL (only if the URL function is "information")
- const distributions = this.get('distribution') || [];
- IDs.forEach(id => {
- const distributions = model.get('distribution');
- if(!distributions || !distributions.length) return;
- const dist = this.findDistribution(
- { url: id, urlFunction: 'information' }, true);
- if (dist) {
- // Remove the distribution model from the array
- distributions.splice(distributions.indexOf(dist), 1);
- }
- });
-
-
- // Add a new distribution node with the view URL
- const viewURL = this.getCanonicalDOIIRI() || this.createViewURL();
- if (viewURL) {
- this.addDistribution({
- url: viewURL,
- urlFunction: 'information'
- });
- } else {
- console.log('Could not add a distribution node with the view URL');
+ try {
+ // Old distribution URLs could exist for any of the old or current
+ const IDs = [ this.get('oldPid'), this.get('id'), this.get('seriesId')]
+ // The new distribution URL will be the view URL or DOI URL
+ const viewURL = this.getCanonicalDOIIRI() || this.createViewURL();
+ // Add the new distribution URL to the collection
+ this.getDistributions().addDatasetDistributionURL(viewURL, IDs);
+ } catch (e) {
+ console.log("Couldn't add the distribution URL to the EML model", e);
}
-
},
diff --git a/src/js/models/metadata/eml211/EMLDistribution.js b/src/js/models/metadata/eml211/EMLDistribution.js
index 2f49165c0..1cc1ff6ed 100644
--- a/src/js/models/metadata/eml211/EMLDistribution.js
+++ b/src/js/models/metadata/eml211/EMLDistribution.js
@@ -228,6 +228,7 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
/*
* Makes a copy of the original XML DOM and updates it with the new values
* from the model.
+ * @return {Element} The updated XML DOM
*/
updateDOM: function () {
const objectDOM =
@@ -357,10 +358,18 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
else return false;
},
+ /*
+ * Trigger a change event on the parent EML model
+ */
trickleUpChange: function () {
MetacatUI.rootDataPackage?.packageModel?.set("changed", true);
},
+ /*
+ * Formats the given XML string to be human-readable
+ * @param {string} xmlString - The XML string to format
+ * @return {string} - The formatted XML string
+ */
formatXML: function (xmlString) {
return DataONEObject.prototype.formatXML.call(this, xmlString);
},
diff --git a/test/config/tests.json b/test/config/tests.json
index a558e0dec..5585a8f20 100644
--- a/test/config/tests.json
+++ b/test/config/tests.json
@@ -21,6 +21,7 @@
"./js/specs/unit/collections/metadata/eml/EMLMissingValueCodes.spec.js",
"./js/specs/unit/models/metadata/eml211/EMLMissingValueCode.spec.js",
"./js/specs/unit/models/metadata/eml211/EMLDistribution.spec.js",
+ "./js/specs/unit/collections/metadata/eml/EMLDistributions.spec.js",
"./js/specs/unit/models/maps/assets/CesiumImagery.spec.js",
"./js/specs/unit/collections/maps/Geohashes.spec.js",
"./js/specs/unit/models/connectors/Filters-Map.spec.js",
diff --git a/test/js/specs/unit/collections/metadata/eml/EMLDistributions.spec.js b/test/js/specs/unit/collections/metadata/eml/EMLDistributions.spec.js
new file mode 100644
index 000000000..8ff730f08
--- /dev/null
+++ b/test/js/specs/unit/collections/metadata/eml/EMLDistributions.spec.js
@@ -0,0 +1,115 @@
+define([
+ "../../../../../../../../src/js/collections/metadata/eml/EMLDistributions",
+], function (EMLDistributions) {
+ // Configure the Chai assertion library
+ var should = chai.should();
+ var expect = chai.expect;
+
+ describe("EMLDistributions Test Suite", function () {
+ /* Set up */
+ beforeEach(function () {
+ this.EMLDistributions = new EMLDistributions();
+ });
+
+ /* Tear down */
+ afterEach(function () {
+ delete this.EMLDistributions;
+ });
+
+ describe("Initialization", function () {
+ it("should create a EMLDistributions instance", function () {
+ new EMLDistributions().should.be.instanceof(EMLDistributions);
+ });
+ });
+
+ describe("Finding distributions", function () {
+ it("should find a distribution by attributes", function () {
+ let dist = this.EMLDistributions.add({ url: "http://example.com" });
+ this.EMLDistributions.findByAttributes({ url: "http://example.com" }).should.equal(dist);
+ });
+
+ it("should find a distribution by partial attributes", function () {
+ let dist = this.EMLDistributions.add({ url: "http://example.com" });
+ this.EMLDistributions.findByAttributes({ url: "example.com" }, true).should.equal(dist);
+ });
+
+ it("should not find a distribution by attributes", function () {
+ let dist = this.EMLDistributions.add({ url: "http://example.com" });
+ expect(this.EMLDistributions.findByAttributes({ url: "http://example.org" })).to.be.undefined;
+ });
+
+ it("should not find a distribution by partial attributes", function () {
+ let dist = this.EMLDistributions.add({ url: "http://example.com" });
+ expect(this.EMLDistributions.findByAttributes({ url: "example.org" }, true)).to.be.undefined;
+ });
+
+ it("should find a distribution by attributes with multiple matches", function () {
+ let dist1 = this.EMLDistributions.add({ url: "http://example.com" });
+ let dist2 = this.EMLDistributions.add({ url: "http://example.com" });
+ this.EMLDistributions.findByAttributes({ url: "http://example.com" }).should.equal(dist1);
+ });
+
+ it("should find a distribution by partial attributes with multiple matches", function () {
+ let dist1 = this.EMLDistributions.add({ url: "http://example.com" });
+ let dist2 = this.EMLDistributions.add({ url: "http://example.com" });
+ this.EMLDistributions.findByAttributes({ url: "example.com" }, true).should.equal(dist1);
+ });
+
+ });
+
+ describe("Adding and removing distributions", function () {
+ it("should add a distribution", function () {
+ let dist = this.EMLDistributions.add({ url: "http://example.com" });
+ this.EMLDistributions.length.should.equal(1);
+ this.EMLDistributions.at(0).should.equal(dist);
+ });
+
+ it("should parse a distribution that is added with a DOM", function () {
+ let dom = jQuery("http://example.com");
+ let dist = this.EMLDistributions.add({ objectDOM: dom }, { parse: true });
+ this.EMLDistributions.length.should.equal(1);
+ this.EMLDistributions.at(0).should.equal(dist);
+ this.EMLDistributions.at(0).get("url").should.equal("http://example.com");
+ });
+
+ it("should remove a distribution", function () {
+ let dist = this.EMLDistributions.add({ url: "http://example.com" });
+ this.EMLDistributions.remove(dist);
+ this.EMLDistributions.length.should.equal(0);
+ });
+
+ it("should remove a distribution by attributes", function () {
+ let dist = this.EMLDistributions.add({ url: "http://example.com" });
+ this.EMLDistributions.removeByAttributes({ url: "http://example.com" });
+ this.EMLDistributions.length.should.equal(0);
+ });
+
+ it("should remove a distribution by partial attributes", function () {
+ let dist = this.EMLDistributions.add({ url: "http://example.com" });
+ this.EMLDistributions.removeByAttributes({ url: "example.com" }, true);
+ this.EMLDistributions.length.should.equal(0);
+ });
+
+ });
+
+ describe("Updating DOMs", function () {
+ it("should update DOMs", function () {
+ let dist = this.EMLDistributions.add({ url: "http://example.com", urlFunction: "information" });
+ let doms = this.EMLDistributions.updateDOMs();
+ doms.length.should.equal(1);
+ doms[0].tagName.should.equal("DISTRIBUTION");
+ // Immediate child should be
+ doms[0].children[0].tagName.should.equal("ONLINE");
+ // should have 1 child:
+ doms[0].children[0].children.length.should.equal(1);
+ // should have the correct text
+ doms[0].children[0].children[0].textContent.should.equal("http://example.com");
+ // should have the correct attribute: function="information"
+ doms[0].children[0].children[0].getAttribute("function").should.equal("information");
+ });
+ });
+
+
+
+ });
+});