diff --git a/src/js/models/metadata/eml211/EMLGeoCoverage.js b/src/js/models/metadata/eml211/EMLGeoCoverage.js
index 192694470..c03ad5973 100644
--- a/src/js/models/metadata/eml211/EMLGeoCoverage.js
+++ b/src/js/models/metadata/eml211/EMLGeoCoverage.js
@@ -1,440 +1,560 @@
/* global define */
-define(['jquery', 'underscore', 'backbone', 'models/DataONEObject'],
- function ($, _, Backbone, DataONEObject) {
-
- /**
- * @class EMLGeoCoverage
- * @classdesc A description of geographic coverage of a dataset, per the EML 2.1.1 metadata standard
- * @classcategory Models/Metadata/EML211
- * @extends Backbone.Model
- * @constructor
- */
- var EMLGeoCoverage = Backbone.Model.extend(
- /** @lends EMLGeoCoverage.prototype */{
-
- defaults: {
- objectXML: null,
- objectDOM: null,
- parentModel: null,
- description: null,
- east: null,
- north: null,
- south: null,
- west: null
- },
-
- initialize: function (attributes) {
- if (attributes && attributes.objectDOM) this.set(this.parse(attributes.objectDOM));
-
- //specific attributes to listen to
- this.on("change:description " +
- "change:east " +
- "change:west " +
- "change:south " +
- "change:north",
- this.trickleUpChange);
- },
-
- /*
- * Maps the lower-case EML node names (valid in HTML DOM) to the camel-cased EML node names (valid in EML).
- * Used during parse() and serialize()
- */
- nodeNameMap: function () {
- return {
- "altitudemaximum": "altitudeMaximum",
- "altitudeminimum": "altitudeMinimum",
- "altitudeunits": "altitudeUnits",
- "boundingaltitudes": "boundingAltitudes",
- "boundingcoordinates": "boundingCoordinates",
- "eastboundingcoordinate": "eastBoundingCoordinate",
- "geographiccoverage": "geographicCoverage",
- "geographicdescription": "geographicDescription",
- "northboundingcoordinate": "northBoundingCoordinate",
- "southboundingcoordinate": "southBoundingCoordinate",
- "westboundingcoordinate": "westBoundingCoordinate"
- }
- },
-
- /** Based on this example serialization
-
- Rhine-Main-Observatory
-
- 9.0005
- 9.0005
- 50.1600
- 50.1600
-
-
- **/
- parse: function (objectDOM) {
-
- var modelJSON = {};
-
- if (!objectDOM) {
- if (this.get("objectDOM"))
- var objectDOM = this.get("objectDOM");
- else
- return {};
- }
-
- //Create a jQuery object of the DOM
- var $objectDOM = $(objectDOM);
-
- //Get the geographic description
- modelJSON.description = $objectDOM.children('geographicdescription').text();
-
- //Get the bounding coordinates
- var boundingCoordinates = $objectDOM.children('boundingcoordinates');
- if (boundingCoordinates) {
- modelJSON.east = boundingCoordinates.children('eastboundingcoordinate').text().replace("+", "");
- modelJSON.north = boundingCoordinates.children('northboundingcoordinate').text().replace("+", "");
- modelJSON.south = boundingCoordinates.children('southboundingcoordinate').text().replace("+", "");
- modelJSON.west = boundingCoordinates.children('westboundingcoordinate').text().replace("+", "");
- }
-
- return modelJSON;
- },
-
- serialize: function () {
- var objectDOM = this.updateDOM(),
- xmlString = objectDOM.outerHTML;
-
- //Camel-case the XML
- xmlString = this.formatXML(xmlString);
-
- return xmlString;
- },
-
- /*
- * Makes a copy of the original XML DOM and updates it with the new values from the model.
- */
- updateDOM: function () {
- var objectDOM;
-
- if (!this.isValid()) {
- return "";
- }
-
- if (this.get("objectDOM")) {
- objectDOM = $(this.get("objectDOM").cloneNode(true));
- } else {
- objectDOM = $(document.createElement("geographiccoverage"));
- }
-
- //If only one point is given, make sure both points are the same
- if ((this.get("north") && this.get("west")) && (!this.get("south") && !this.get("east"))) {
- this.set("south", this.get("north"));
- this.set("east", this.get("west"));
- }
- else if ((this.get("south") && this.get("east")) && (!this.get("north") && !this.get("west"))) {
- this.set("north", this.get("south"));
- this.set("west", this.get("east"));
- }
-
- // Description
- if (!objectDOM.children("geographicdescription").length)
- objectDOM.append($(document.createElement("geographicdescription")).text(this.get("description")));
- else
- objectDOM.children("geographicdescription").text(this.get("description"));
-
- // Create the bounding coordinates element
- var boundingCoordinates = objectDOM.find("boundingcoordinates");
- if (!boundingCoordinates.length) {
- boundingCoordinates = document.createElement("boundingcoordinates");
- objectDOM.append(boundingCoordinates);
- }
-
- //Empty out the coordinates first
- $(boundingCoordinates).empty();
-
- //Add the four coordinate values
- $(boundingCoordinates).append($(document.createElement("westboundingcoordinate")).text(this.get("west")),
- $(document.createElement("eastboundingcoordinate")).text(this.get("east")),
- $(document.createElement("northboundingcoordinate")).text(this.get("north")),
- $(document.createElement("southboundingcoordinate")).text(this.get("south")));
-
- return objectDOM;
- },
-
- /**
- * Sometimes we'll need to add a space between error messages, but only if an
- * error has already been triggered. Use addSpace to accomplish this.
- *
- * @param {string} msg The string that will be appended
- * @param {bool} front A flag that when set will append the whitespace to the front of 'msg'
- * @return {string} The string that was passed in, 'msg', with whitespace appended
- */
- addSpace: function (msg, front) {
- if (typeof front === "undefined") {
- front = false;
- }
- if (msg) {
- if (front) {
- return (" " + msg);
- }
- return msg += " ";
- }
- return msg;
- },
-
- /**
- * Because the same error messages are used in a couple of different places, we centralize the strings
- * and access here.
- *
- * @param {string} area Specifies the area that the error message belongs to.
- * Browse through the switch statement to find the one you need.
- * @return {string} The error message
- */
- getErrorMessage: function (area) {
- switch (area) {
- case "north":
- return "The Northwest latitude must be between -90 and 90.";
- break;
- case "east":
- return "The Southeast longitude must be between -180 and 180.";
- break;
- case "south":
- return "The Southeast latitude must be between -90 and 90.";
- break;
- case "west":
- return "The Northwest longitude must be between -180 and 180.";
- break;
- case "missing":
- return "Each coordinate must include a latitude AND longitude.";
- break;
- case "description":
- return "Each location must have a description.";
- break;
- case "needPair":
- return "Each location description must have at least one coordinate pair.";
- break;
- default:
- return "";
- break;
- }
- },
-
- /**
- * Generates an object that describes the current state of each latitude
- * and longitude box. The status includes whether there is a value and
- * if the value is valid.
- *
- * @return {array} An array containing the current state of each coordinate box
- */
- getCoordinateStatus: function () {
- var north = this.get("north"),
- east = this.get("east"),
- south = this.get("south"),
- west = this.get("west");
-
- return {
- 'north': {
- isSet: typeof north !== "undefined" && north != null && north !== "",
- isValid: this.validateCoordinate(north, -90, 90)
- },
- 'east': {
- isSet: typeof east !== "undefined" && east != null && east !== "",
- isValid: this.validateCoordinate(east, -180, 180)
- },
- 'south': {
- isSet: typeof south !== "undefined" && south != null && south !== "",
- isValid: this.validateCoordinate(south, -90, 90)
- },
- 'west': {
- isSet: typeof west !== "undefined" && west != null && west !== "",
- isValid: this.validateCoordinate(west, -180, 180)
- },
- }
- },
-
- /**
- * Checks the status object for conditions that warrant an error message to the user. This is called
- * during the validation processes (validate() and updateModel()) after the status object has been
- * created by getCoordinateStatus().
- *
- * @param status The status object, holding the state of the coordinates
- * @return {string} Any errors that need to be displayed to the user
- */
- generateStatusErrors: function (status) {
- var errorMsg = "";
-
- // Northwest Latitude
- if (status.north.isSet && !status.north.isValid) {
- errorMsg = this.addSpace(errorMsg);
- errorMsg += this.getErrorMessage("north");
- }
- // Northwest Longitude
- if (status.west.isSet && !status.west.isValid) {
- errorMsg = this.addSpace(errorMsg);
- errorMsg += this.getErrorMessage("west");
- }
- // Southeast Latitude
- if (status.south.isSet && !status.south.isValid) {
- errorMsg = this.addSpace(errorMsg);
- errorMsg += this.getErrorMessage("south");
- }
- // Southeast Longitude
- if (status.east.isSet && !status.east.isValid) {
- errorMsg = this.addSpace(errorMsg);
- errorMsg += this.getErrorMessage("east");
- }
- return errorMsg;
-
- },
-
- /**
- * This grabs the various location elements and validates the user input. In the case of an error,
- * we append an error string (errMsg) so that we display all of the messages at the same time. This
- * validates the entire location row by adding extra checks for a description and for coordinate pairs
- *
- * @return {string} The error messages that the user will see
- */
- validate: function () {
- var errors = {};
-
- if (!this.get("description")) {
- errors.description = this.getErrorMessage("description");
- }
-
- var pointStatuses = this.getCoordinateStatus();
-/*
- if (!this.checkForPairs(pointStatuses)) {
- errorMsg = this.addSpace(errorMsg);
- errorMsg += this.getErrorMessage("needPair");
- }
-
- if( this.hasMissingPoint(pointStatuses) ) {
- //errorMsg = this.addSpace(errorMsg);
- errors += this.getErrorMessage("missing");
- }
-*/
- // errorMsg += this.addSpace(this.generateStatusErrors(pointStatuses), true);
-
- if( !pointStatuses.north.isSet && !pointStatuses.south.isSet &&
- !pointStatuses.east.isSet && !pointStatuses.west.isSet){
- errors.north = this.getErrorMessage("needPair");
- errors.west = "";
- }
-
- //Check that all the values are correct
- if( pointStatuses.north.isSet && !pointStatuses.north.isValid )
- errors.north = this.getErrorMessage("north");
- if( pointStatuses.south.isSet && !pointStatuses.south.isValid )
- errors.south = this.getErrorMessage("south");
- if( pointStatuses.east.isSet && !pointStatuses.east.isValid )
- errors.east = this.getErrorMessage("east");
- if( pointStatuses.west.isSet && !pointStatuses.west.isValid )
- errors.west = this.getErrorMessage("west");
-
- if( pointStatuses.north.isSet && !pointStatuses.west.isSet )
- errors.west = this.getErrorMessage("missing");
- else if( !pointStatuses.north.isSet && pointStatuses.west.isSet )
- errors.north = this.getErrorMessage("missing");
- else if( pointStatuses.south.isSet && !pointStatuses.east.isSet )
- errors.east = this.getErrorMessage("missing");
- else if( !pointStatuses.south.isSet && pointStatuses.east.isSet )
- errors.south = this.getErrorMessage("missing");
-
- if( Object.keys(errors).length )
- return errors;
- else
- return false;
- },
-
- /**
- * Checks for any coordinates with missing counterparts.
- *
- * @param status The status of the coordinates
- * @return {bool} True if there are missing coordinates, false otherwise
- */
- hasMissingPoint: function (status) {
- if ((status.north.isSet && !status.west.isSet) ||
- (!status.north.isSet && status.west.isSet)) {
- return true
- } else if ((status.south.isSet && !status.east.isSet) ||
- (!status.south.isSet && status.east.isSet)) {
- return true;
- }
-
- return false;
-
- },
-
- /**
- * Checks that there are either two or four coordinate values. If there aren't,
- * it means that the user still needs to enter coordinates.
- *
- * @param status The current state of the coordinates
- * @return {bool} True if there are pairs, false otherwise
- */
- checkForPairs: function (status) {
- var isSet = _.filter(status, function (coord) { return coord.isSet == true; });
-
- if (isSet.length == 0) {
- return false;
- }
- return true;
- },
-
- /**
- * Validate a coordinate String by making sure it can be coerced into a number and
- * is within the given bounds.
- * Note: Min and max are inclusive
- *
- * @param value {string} The value of the edit area that will be validated
- * @param min The minimum value that 'value' can be
- * @param max The maximum value that 'value' can be
- * @return {bool} True if the validation passed, otherwise false
- */
- validateCoordinate: function (value, min, max) {
-
- if (typeof value === "undefined" || value === null || value === "" && isNaN(value)) {
- return false;
- }
-
- var parsed = Number(value);
-
- if (isNaN(parsed)) {
- return false;
- }
-
- if (parsed < min || parsed > max) {
- return false;
- }
-
- return true;
- },
-
- /*
- * Climbs up the model heirarchy until it finds the EML model
- *
- * @return {EML211 or false} - Returns the EML 211 Model or false if not found
- */
- getParentEML: function(){
- var emlModel = this.get("parentModel"),
- tries = 0;
-
- while (emlModel.type !== "EML" && tries < 6){
- emlModel = emlModel.get("parentModel");
- tries++;
- }
-
- if( emlModel && emlModel.type == "EML")
- return emlModel;
- else
- return false;
-
- },
-
- trickleUpChange: function () {
- this.get("parentModel").trigger("change");
- this.get("parentModel").trigger("change:geoCoverage");
- },
-
- formatXML: function (xmlString) {
- return DataONEObject.prototype.formatXML.call(this, xmlString);
- }
+define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
+ $,
+ _,
+ Backbone,
+ DataONEObject
+) {
+ /**
+ * @class EMLGeoCoverage
+ * @classdesc A description of geographic coverage of a dataset, per the EML
+ * 2.1.1 metadata standard
+ * @classcategory Models/Metadata/EML211
+ * @extends Backbone.Model
+ * @constructor
+ */
+ var EMLGeoCoverage = Backbone.Model.extend(
+ /** @lends EMLGeoCoverage.prototype */ {
+ defaults: {
+ objectXML: null,
+ objectDOM: null,
+ parentModel: null,
+ description: null,
+ east: null,
+ north: null,
+ south: null,
+ west: null,
+ },
+
+ initialize: function (attributes) {
+ if (attributes && attributes.objectDOM)
+ this.set(this.parse(attributes.objectDOM));
+
+ //specific attributes to listen to
+ this.on(
+ "change:description " +
+ "change:east " +
+ "change:west " +
+ "change:south " +
+ "change:north",
+ this.trickleUpChange
+ );
+ },
+
+ /*
+ * Maps the lower-case EML node names (valid in HTML DOM) to the
+ * camel-cased EML node names (valid in EML). Used during parse() and
+ * serialize()
+ */
+ nodeNameMap: function () {
+ return {
+ altitudemaximum: "altitudeMaximum",
+ altitudeminimum: "altitudeMinimum",
+ altitudeunits: "altitudeUnits",
+ boundingaltitudes: "boundingAltitudes",
+ boundingcoordinates: "boundingCoordinates",
+ eastboundingcoordinate: "eastBoundingCoordinate",
+ geographiccoverage: "geographicCoverage",
+ geographicdescription: "geographicDescription",
+ northboundingcoordinate: "northBoundingCoordinate",
+ southboundingcoordinate: "southBoundingCoordinate",
+ westboundingcoordinate: "westBoundingCoordinate",
+ };
+ },
+
+ /**
+ * Parses the objectDOM to populate this model with data.
+ * @param {Element} objectDOM - The EML object element
+ * @returns {Object} The EMLGeoCoverage data
+ *
+ * @example - Example input XML
+ *
+ * Rhine-Main-Observatory
+ *
+ * 9.0005
+ * 9.0005
+ * 50.1600
+ * 50.1600
+ *
+ *
+ */
+ parse: function (objectDOM) {
+ var modelJSON = {};
+
+ if (!objectDOM) {
+ if (this.get("objectDOM")) var objectDOM = this.get("objectDOM");
+ else return {};
+ }
+
+ //Create a jQuery object of the DOM
+ var $objectDOM = $(objectDOM);
+
+ //Get the geographic description
+ modelJSON.description = $objectDOM
+ .children("geographicdescription")
+ .text();
+
+ //Get the bounding coordinates
+ var boundingCoordinates = $objectDOM.children("boundingcoordinates");
+ if (boundingCoordinates) {
+ modelJSON.east = boundingCoordinates
+ .children("eastboundingcoordinate")
+ .text()
+ .replace("+", "");
+ modelJSON.north = boundingCoordinates
+ .children("northboundingcoordinate")
+ .text()
+ .replace("+", "");
+ modelJSON.south = boundingCoordinates
+ .children("southboundingcoordinate")
+ .text()
+ .replace("+", "");
+ modelJSON.west = boundingCoordinates
+ .children("westboundingcoordinate")
+ .text()
+ .replace("+", "");
+ }
+
+ return modelJSON;
+ },
+
+ /**
+ * Converts this EMLGeoCoverage to XML
+ * @returns {string} The XML string
+ */
+ serialize: function () {
+
+ const objectDOM = this.updateDOM();
+ let xmlString = objectDOM?.outerHTML;
+ if (!xmlString) xmlString = objectDOM?.[0]?.outerHTML;
+
+ //Camel-case the XML
+ xmlString = this.formatXML(xmlString);
+
+ return xmlString;
+ },
+
+ /*
+ * Makes a copy of the original XML DOM and updates it with the new values
+ * from the model.
+ */
+ updateDOM: function () {
+ var objectDOM;
+
+ if (!this.isValid()) {
+ return "";
+ }
+
+ if (this.get("objectDOM")) {
+ objectDOM = $(this.get("objectDOM").cloneNode(true));
+ } else {
+ objectDOM = $(document.createElement("geographiccoverage"));
+ }
+
+ //If only one point is given, make sure both points are the same
+ if (
+ this.get("north") &&
+ this.get("west") &&
+ !this.get("south") &&
+ !this.get("east")
+ ) {
+ this.set("south", this.get("north"));
+ this.set("east", this.get("west"));
+ } else if (
+ this.get("south") &&
+ this.get("east") &&
+ !this.get("north") &&
+ !this.get("west")
+ ) {
+ this.set("north", this.get("south"));
+ this.set("west", this.get("east"));
+ }
+
+ // Description
+ if (!objectDOM.children("geographicdescription").length)
+ objectDOM.append(
+ $(document.createElement("geographicdescription")).text(
+ this.get("description")
+ )
+ );
+ else
+ objectDOM
+ .children("geographicdescription")
+ .text(this.get("description"));
+
+ // Create the bounding coordinates element
+ var boundingCoordinates = objectDOM.find("boundingcoordinates");
+ if (!boundingCoordinates.length) {
+ boundingCoordinates = document.createElement("boundingcoordinates");
+ objectDOM.append(boundingCoordinates);
+ }
+
+ //Empty out the coordinates first
+ $(boundingCoordinates).empty();
+
+ //Add the four coordinate values
+ $(boundingCoordinates).append(
+ $(document.createElement("westboundingcoordinate")).text(
+ this.get("west")
+ ),
+ $(document.createElement("eastboundingcoordinate")).text(
+ this.get("east")
+ ),
+ $(document.createElement("northboundingcoordinate")).text(
+ this.get("north")
+ ),
+ $(document.createElement("southboundingcoordinate")).text(
+ this.get("south")
+ )
+ );
+
+ return objectDOM;
+ },
+
+ /**
+ * Sometimes we'll need to add a space between error messages, but only if
+ * an error has already been triggered. Use addSpace to accomplish this.
+ *
+ * @param {string} msg The string that will be appended
+ * @param {bool} front A flag that when set will append the whitespace to
+ * the front of 'msg'
+ * @return {string} The string that was passed in, 'msg', with whitespace
+ * appended
+ */
+ addSpace: function (msg, front) {
+ if (typeof front === "undefined") {
+ front = false;
+ }
+ if (msg) {
+ if (front) {
+ return " " + msg;
+ }
+ return (msg += " ");
+ }
+ return msg;
+ },
+
+ /**
+ * Because the same error messages are used in a couple of different
+ * places, we centralize the strings and access here.
+ *
+ * @param {string} area Specifies the area that the error message belongs
+ * to. Browse through the switch statement to find the one you need.
+ * @return {string} The error message
+ */
+ getErrorMessage: function (area) {
+ switch (area) {
+ case "north":
+ return "The Northwest latitude must be between -90 and 90.";
+ break;
+ case "east":
+ return "The Southeast longitude must be between -180 and 180.";
+ break;
+ case "south":
+ return "The Southeast latitude must be between -90 and 90.";
+ break;
+ case "west":
+ return "The Northwest longitude must be between -180 and 180.";
+ break;
+ case "missing":
+ return "Each coordinate must include a latitude AND longitude.";
+ break;
+ case "description":
+ return "Each location must have a description.";
+ break;
+ case "needPair":
+ return "Each location description must have at least one coordinate pair.";
+ break;
+ case "northSouthReversed":
+ return "The North latitude must be greater than the South latitude.";
+ break;
+ case "crossesAntiMeridian":
+ return "The bounding box cannot cross the anti-meridian.";
+ break;
+ case "containsPole":
+ return "The bounding box cannot contain the North or South pole.";
+ default:
+ return "";
+ break;
+ }
+ },
+
+ /**
+ * Generates an object that describes the current state of each latitude
+ * and longitude box. The status includes whether there is a value and if
+ * the value is valid.
+ *
+ * @return {array} An array containing the current state of each
+ * coordinate box, including: value (the value of the coordinate converted
+ * to a number), isSet (whether the coordinate has a value), and isValid
+ * (whether the value is in the correct range)
+ */
+ getCoordinateStatus: function () {
+ var north = this.get("north"),
+ east = this.get("east"),
+ south = this.get("south"),
+ west = this.get("west");
+
+ const isDefined = (value) =>
+ typeof value !== "undefined" && value != null && value !== "";
+
+ return {
+ north: {
+ value: Number(north),
+ isSet: isDefined(north),
+ isValid: this.validateCoordinate(north, -90, 90),
+ },
+ east: {
+ value: Number(east),
+ isSet: isDefined(east),
+ isValid: this.validateCoordinate(east, -180, 180),
+ },
+ south: {
+ value: Number(south),
+ isSet: isDefined(south),
+ isValid: this.validateCoordinate(south, -90, 90),
+ },
+ west: {
+ value: Number(west),
+ isSet: isDefined(west),
+ isValid: this.validateCoordinate(west, -180, 180),
+ },
+ };
+ },
+
+ /**
+ * Checks the status object for conditions that warrant an error message
+ * to the user. This is called during the validation processes (validate()
+ * and updateModel()) after the status object has been created by
+ * getCoordinateStatus().
+ *
+ * @param status The status object, holding the state of the coordinates
+ * @return {string} Any errors that need to be displayed to the user
+ */
+ generateStatusErrors: function (status) {
+ var errorMsg = "";
+
+ // Northwest Latitude
+ if (status.north.isSet && !status.north.isValid) {
+ errorMsg = this.addSpace(errorMsg);
+ errorMsg += this.getErrorMessage("north");
+ }
+ // Northwest Longitude
+ if (status.west.isSet && !status.west.isValid) {
+ errorMsg = this.addSpace(errorMsg);
+ errorMsg += this.getErrorMessage("west");
+ }
+ // Southeast Latitude
+ if (status.south.isSet && !status.south.isValid) {
+ errorMsg = this.addSpace(errorMsg);
+ errorMsg += this.getErrorMessage("south");
+ }
+ // Southeast Longitude
+ if (status.east.isSet && !status.east.isValid) {
+ errorMsg = this.addSpace(errorMsg);
+ errorMsg += this.getErrorMessage("east");
+ }
+ return errorMsg;
+ },
+
+ /**
+ * This grabs the various location elements and validates the user input.
+ * In the case of an error, we append an error string (errMsg) so that we
+ * display all of the messages at the same time. This validates the entire
+ * location row by adding extra checks for a description and for
+ * coordinate pairs
+ *
+ * @return {string} The error messages that the user will see
+ */
+ validate: function () {
+ var errors = {};
+
+ if (!this.get("description")) {
+ errors.description = this.getErrorMessage("description");
+ }
+
+ var pointStatuses = this.getCoordinateStatus();
+
+ if (
+ !pointStatuses.north.isSet &&
+ !pointStatuses.south.isSet &&
+ !pointStatuses.east.isSet &&
+ !pointStatuses.west.isSet
+ ) {
+ errors.north = this.getErrorMessage("needPair");
+ errors.west = "";
+ }
+
+ //Check that all the values are correct
+ if (pointStatuses.north.isSet && !pointStatuses.north.isValid)
+ errors.north = this.getErrorMessage("north");
+ if (pointStatuses.south.isSet && !pointStatuses.south.isValid)
+ errors.south = this.getErrorMessage("south");
+ if (pointStatuses.east.isSet && !pointStatuses.east.isValid)
+ errors.east = this.getErrorMessage("east");
+ if (pointStatuses.west.isSet && !pointStatuses.west.isValid)
+ errors.west = this.getErrorMessage("west");
+
+ if (pointStatuses.north.isSet && !pointStatuses.west.isSet)
+ errors.west = this.getErrorMessage("missing");
+ else if (!pointStatuses.north.isSet && pointStatuses.west.isSet)
+ errors.north = this.getErrorMessage("missing");
+ else if (pointStatuses.south.isSet && !pointStatuses.east.isSet)
+ errors.east = this.getErrorMessage("missing");
+ else if (!pointStatuses.south.isSet && pointStatuses.east.isSet)
+ errors.south = this.getErrorMessage("missing");
+
+ // Verify latitudes: north should be > south. Don't allow bounding boxes
+ // to contain the north or south poles (doesn't really work)
+ if (
+ pointStatuses.north.isSet &&
+ pointStatuses.south.isSet &&
+ pointStatuses.north.isValid &&
+ pointStatuses.south.isValid
+ ) {
+ if (pointStatuses.north.value < pointStatuses.south.value) {
+ const msg = this.getErrorMessage("northSouthReversed");
+ errors.north = msg;
+ errors.south = msg;
+ }
+ if (
+ pointStatuses.north.value == 90 ||
+ pointStatuses.south.value == -90
+ ) {
+ const msg = this.getErrorMessage("containsPole");
+ errors.north = msg;
+ errors.south = msg;
+ }
+ }
+
+ // For longitudes, don't allow bounding boxes that attempt to traverse
+ // the anti-meridian
+ if (
+ pointStatuses.east.isSet &&
+ pointStatuses.west.isSet &&
+ pointStatuses.east.isValid &&
+ pointStatuses.west.isValid
+ ) {
+ if (pointStatuses.east.value < pointStatuses.west.value) {
+ const msg = this.getErrorMessage("crossesAntiMeridian");
+ errors.east = msg;
+ errors.west = msg;
+ }
+ }
+
+
+ if (Object.keys(errors).length) return errors;
+ else return false;
+ },
+
+ /**
+ * Checks for any coordinates with missing counterparts.
+ *
+ * @param status The status of the coordinates
+ * @return {bool} True if there are missing coordinates, false otherwise
+ */
+ hasMissingPoint: function (status) {
+ if (
+ (status.north.isSet && !status.west.isSet) ||
+ (!status.north.isSet && status.west.isSet)
+ ) {
+ return true;
+ } else if (
+ (status.south.isSet && !status.east.isSet) ||
+ (!status.south.isSet && status.east.isSet)
+ ) {
+ return true;
+ }
+
+ return false;
+ },
+
+ /**
+ * Checks that there are either two or four coordinate values. If there
+ * aren't, it means that the user still needs to enter coordinates.
+ *
+ * @param status The current state of the coordinates
+ * @return {bool} True if there are pairs, false otherwise
+ */
+ checkForPairs: function (status) {
+ var isSet = _.filter(status, function (coord) {
+ return coord.isSet == true;
});
- return EMLGeoCoverage;
- });
+ if (isSet.length == 0) {
+ return false;
+ }
+ return true;
+ },
+
+ /**
+ * Validate a coordinate String by making sure it can be coerced into a
+ * number and is within the given bounds. Note: Min and max are inclusive
+ *
+ * @param value {string} The value of the edit area that will be validated
+ * @param min The minimum value that 'value' can be
+ * @param max The maximum value that 'value' can be
+ * @return {bool} True if the validation passed, otherwise false
+ */
+ validateCoordinate: function (value, min, max) {
+ if (
+ typeof value === "undefined" ||
+ value === null ||
+ (value === "" && isNaN(value))
+ ) {
+ return false;
+ }
+
+ var parsed = Number(value);
+
+ if (isNaN(parsed)) {
+ return false;
+ }
+
+ if (parsed < min || parsed > max) {
+ return false;
+ }
+
+ return true;
+ },
+
+ /**
+ * Climbs up the model hierarchy until it finds the EML model
+ *
+ * @return {EML211 or false} - Returns the EML 211 Model or false if not
+ * found
+ */
+ getParentEML: function () {
+ var emlModel = this.get("parentModel"),
+ tries = 0;
+
+ while (emlModel.type !== "EML" && tries < 6) {
+ emlModel = emlModel.get("parentModel");
+ tries++;
+ }
+
+ if (emlModel && emlModel.type == "EML") return emlModel;
+ else return false;
+ },
+
+ /**
+ * Apply the change event on the parent EML model
+ */
+ trickleUpChange: function () {
+ const parentModel = this.get("parentModel");
+ if (!parentModel) return;
+ parentModel.trigger("change");
+ parentModel.trigger("change:geoCoverage");
+ },
+
+ /**
+ * See DataONEObject.formatXML()
+ */
+ formatXML: function (xmlString) {
+ return DataONEObject.prototype.formatXML.call(this, xmlString);
+ },
+ }
+ );
+
+ return EMLGeoCoverage;
+});
diff --git a/src/js/templates/metadata/EMLGeoCoverage.html b/src/js/templates/metadata/EMLGeoCoverage.html
index cec7cdb9c..dcfa0f9bd 100644
--- a/src/js/templates/metadata/EMLGeoCoverage.html
+++ b/src/js/templates/metadata/EMLGeoCoverage.html
@@ -9,13 +9,13 @@
diff --git a/src/js/views/metadata/EMLGeoCoverageView.js b/src/js/views/metadata/EMLGeoCoverageView.js
index 6b404231a..8b73c610e 100644
--- a/src/js/views/metadata/EMLGeoCoverageView.js
+++ b/src/js/views/metadata/EMLGeoCoverageView.js
@@ -1,253 +1,327 @@
/* global define */
-define(['underscore', 'jquery', 'backbone',
- 'models/metadata/eml211/EMLGeoCoverage',
- 'text!templates/metadata/EMLGeoCoverage.html'],
- function (_, $, Backbone, EMLGeoCoverage, EMLGeoCoverageTemplate) {
-
- /**
- * @class EMlGeoCoverageView
- * @classdesc The EMLGeoCoverage renders the content of an EMLGeoCoverage model
- * @classcategory Views/Metadata
- * @extends Backbone.View
- */
- var EMLGeoCoverageView = Backbone.View.extend(
- /** @lends EMLGeoCoverageView.prototype */{
-
- type: "EMLGeoCoverageView",
-
- tagName: "div",
-
- className: "row-fluid eml-geocoverage",
-
- attributes: {
- "data-category": "geoCoverage"
- },
-
- editTemplate: _.template(EMLGeoCoverageTemplate),
-
- initialize: function (options) {
- if (!options)
- var options = {};
-
- this.isNew = options.isNew || (options.model ? false : true);
- this.model = options.model || new EMLGeoCoverage();
- this.edit = options.edit || false;
- },
-
- events: {
- "change": "updateModel",
- "mouseover .remove": "toggleRemoveClass",
- "mouseout .remove": "toggleRemoveClass"
- },
-
- render: function (e) {
- //Save the view and model on the element
- this.$el.data({
- model: this.model,
- view: this
- });
-
- this.$el.html(this.editTemplate({
- edit: this.edit,
- model: this.model.toJSON()
- }));
-
- if (this.isNew) {
- this.$el.addClass("new");
- }
-
- return this;
- },
-
- /**
- * Updates the model.
- * If this is called from the user switching between latitude and longitude boxes,
- * we check to see if the input was valid and display any errors if we need to.
- *
- * @param e The event
+define([
+ "underscore",
+ "jquery",
+ "backbone",
+ "models/metadata/eml211/EMLGeoCoverage",
+ "text!templates/metadata/EMLGeoCoverage.html",
+], function (_, $, Backbone, EMLGeoCoverage, EMLGeoCoverageTemplate) {
+ /**
+ * @class EMlGeoCoverageView
+ * @classdesc The EMLGeoCoverage renders the content of an EMLGeoCoverage
+ * model
+ * @classcategory Views/Metadata
+ * @extends Backbone.View
+ */
+ var EMLGeoCoverageView = Backbone.View.extend(
+ /** @lends EMLGeoCoverageView.prototype */ {
+ type: "EMLGeoCoverageView",
+
+ /**
+ * The HTML tag name for this view element
+ * @type {string}
+ * @default "div"
+ */
+ tagName: "div",
+
+ /**
+ * The class names to add to this view's HTML element
+ */
+ className: "row-fluid eml-geocoverage",
+
+ /**
+ * Attributes for the HTML element.
+ */
+ attributes: {
+ "data-category": "geoCoverage",
+ },
+
+ /**
+ * Events applied to this view's HTML elements by Backbone.
+ */
+ events: {
+ change: "updateModel", // <- TODO: does this work?
+ "mouseover .remove": "toggleRemoveClass",
+ "mouseout .remove": "toggleRemoveClass",
+ },
+
+ /**
+ * The template to use for this view in edit mode
+ */
+ editTemplate: _.template(EMLGeoCoverageTemplate),
+
+ /**
+ * Initializes the EMLGeoCoverageView
+ * @param {Object} options - A literal object with options to pass to the
+ * view
+ * @param {EMLGeoCoverage} options.model - The EMLGeoCoverage model to
+ * render
+ * @param {boolean} options.edit - Flag to toggle whether this view is in
+ * edit mode
+ * @param {boolean} options.isNew - Flag to toggle whether this view is
+ * new
+ */
+ initialize: function (options) {
+ if (!options) var options = {};
+
+ this.isNew = options.isNew || (options.model ? false : true);
+ this.model = options.model || new EMLGeoCoverage();
+ this.edit = options.edit || false;
+ },
+
+ /**
+ * Renders the EMLGeoCoverageView
+ * @returns {EMLGeoCoverageView} Returns the view
+ */
+ render: function () {
+ try {
+ // Save the view and model on the element
+ this.$el.data({
+ model: this.model,
+ view: this,
+ });
+
+ this.$el.html(
+ this.editTemplate({
+ edit: this.edit,
+ model: this.model.toJSON(),
+ })
+ );
+
+ if (this.isNew) {
+ this.$el.addClass("new");
+ }
+
+ return this;
+ } catch (e) {
+ console.log("Error rendering EMLGeoCoverageView: ", e);
+ return this;
+ }
+ },
+
+ /**
+ * Updates the model. If this is called from the user switching between
+ * latitude and longitude boxes, we check to see if the input was valid
+ * and display any errors if we need to.
+ *
+ * @param {Event} e - The event that triggered this function
+ */
+ updateModel: function (e) {
+ if (!e) return false;
+
+ e.preventDefault();
+
+ //Get the attribute and value
+ var element = $(e.target),
+ value = element.val(),
+ attribute = element.attr("data-attribute");
+
+ //Get the attribute that was changed
+ if (!attribute) return false;
+
+ var emlModel = this.model.getParentEML();
+ if (emlModel) {
+ value = emlModel.cleanXMLText(value);
+ }
+
+ //Are the NW and SE points the same? i.e. is this a single point and not
+ //a box?
+ var isSinglePoint =
+ this.model.get("north") != null &&
+ this.model.get("north") == this.model.get("south") &&
+ this.model.get("west") != null &&
+ this.model.get("west") == this.model.get("east"),
+ hasEmptyInputs =
+ this.$("[data-attribute='north']").val() == "" ||
+ this.$("[data-attribute='south']").val() == "" ||
+ this.$("[data-attribute='west']").val() == "" ||
+ this.$("[data-attribute='east']").val() == "";
+
+ //Update the model
+ if (value == "") this.model.set(attribute, null);
+ else this.model.set(attribute, value);
+
+ //If the NW and SE points are the same point...
+ if (isSinglePoint && hasEmptyInputs) {
+ /* If the user updates one of the empty number inputs, then we can
+ * assume they do not want a single point and are attempting to
+ * enter a second point. So we should empty the value from the model
+ * for the corresponding coordinate For example, if the UI shows a
+ * lat,long pair of NW: [10] [30] SE: [ ] [ ] then the model values
+ * would be N: 10, W: 30, S: 10, E: 30 if the user updates that to:
+ * NW: [10] [30] SE: [5] [ ] then we want to remove the "east" value
+ * of "30", so the model would be: N: 10, W: 30, S: 5, E: null
+ */
+ if (
+ attribute == "north" &&
+ this.$("[data-attribute='west']").val() == ""
+ )
+ this.model.set("west", null);
+ else if (
+ attribute == "south" &&
+ this.$("[data-attribute='east']").val() == ""
+ )
+ this.model.set("east", null);
+ else if (
+ attribute == "east" &&
+ this.$("[data-attribute='south']").val() == ""
+ )
+ this.model.set("south", null);
+ else if (
+ attribute == "west" &&
+ this.$("[data-attribute='north']").val() == ""
+ )
+ this.model.set("north", null);
+ /*
+ * If the user removes one of the latitude or longitude values, reset
+ * the opposite point
+ */ else if (
+ ((attribute == "north" && this.model.get("north") == null) ||
+ (attribute == "west" && this.model.get("west") == null)) &&
+ this.$("[data-attribute='south']").val() == "" &&
+ this.$("[data-attribute='east']").val() == ""
+ ) {
+ this.model.set("south", null);
+ this.model.set("east", null);
+ } else if (
+ ((attribute == "south" && this.model.get("south") == null) ||
+ (attribute == "east" && this.model.get("east") == null)) &&
+ this.$("[data-attribute='north']").val() == "" &&
+ this.$("[data-attribute='west']").val() == ""
+ ) {
+ this.model.set("north", null);
+ this.model.set("west", null);
+ } else if (attribute == "north" && this.model.get("north") != null)
+ /* Otherwise, if the non-empty number inputs are updated, we simply
+ * update the corresponding value in the other point
*/
- updateModel: function (e) {
- if (!e) return false;
-
- e.preventDefault();
-
- //Get the attribute and value
- var element = $(e.target),
- value = element.val(),
- attribute = element.attr("data-attribute");
-
- //Get the attribute that was changed
- if (!attribute) return false;
-
- var emlModel = this.model.getParentEML();
- if(emlModel){
- value = emlModel.cleanXMLText(value);
- }
-
- //Are the NW and SE points the same? i.e. is this a single point and not a box?
- var isSinglePoint = (this.model.get("north") != null && this.model.get("north") == this.model.get("south")) &&
- (this.model.get("west") != null && this.model.get("west") == this.model.get("east")),
- hasEmptyInputs = this.$("[data-attribute='north']").val() == "" ||
- this.$("[data-attribute='south']").val() == "" ||
- this.$("[data-attribute='west']").val() == "" ||
- this.$("[data-attribute='east']").val() == "";
-
- //Update the model
- if (value == "")
- this.model.set(attribute, null);
- else
- this.model.set(attribute, value);
-
- //If the NW and SE points are the same point...
- if (isSinglePoint && hasEmptyInputs) {
- /* If the user updates one of the empty number inputs, then we can assume they do not
- * want a single point and are attempting to enter a second point. So we should empty the
- * value from the model for the corresponding coordinate
- * For example, if the UI shows a lat,long pair of NW: [10] [30] SE: [ ] [ ] then the model
- * values would be N: 10, W: 30, S: 10, E: 30
- * if the user updates that to: NW: [10] [30] SE: [5] [ ]
- * then we want to remove the "east" value of "30", so the model would be: N: 10, W: 30, S: 5, E: null
- */
- if (attribute == "north" && this.$("[data-attribute='west']").val() == "")
- this.model.set("west", null);
- else if (attribute == "south" && this.$("[data-attribute='east']").val() == "")
- this.model.set("east", null);
- else if (attribute == "east" && this.$("[data-attribute='south']").val() == "")
- this.model.set("south", null);
- else if (attribute == "west" && this.$("[data-attribute='north']").val() == "")
- this.model.set("north", null);
-
- /*
- * If the user removes one of the latitude or longitude values, reset the opposite point
- */
- else if (((attribute == "north" && this.model.get("north") == null) ||
- (attribute == "west" && this.model.get("west") == null)) &&
- (this.$("[data-attribute='south']").val() == "" &&
- this.$("[data-attribute='east']").val() == "")) {
- this.model.set("south", null);
- this.model.set("east", null);
- } else if (((attribute == "south" && this.model.get("south") == null) ||
- (attribute == "east" && this.model.get("east") == null)) &&
- (this.$("[data-attribute='north']").val() == "" && this.$("[data-attribute='west']").val() == "")) {
- this.model.set("north", null);
- this.model.set("west", null);
- }
- /* Otherwise, if the non-empty number inputs are updated,
- * we simply update the corresponding value in the other point
- */
- else if (attribute == "north" && this.model.get("north") != null)
- this.model.set("south", value);
- else if (attribute == "south" && this.model.get("south") != null)
- this.model.set("north", value);
- else if (attribute == "west" && this.model.get("west") != null)
- this.model.set("east", value);
- else if (attribute == "east" && this.model.get("east") != null)
- this.model.set("west", value);
- }
- else {
-
- //Find out if we are missing a complete NW or SE point
- var isMissingNWPoint = (this.model.get("north") == null && this.model.get("west") == null),
- isMissingSEPoint = (this.model.get("south") == null && this.model.get("east") == null);
-
- // If there is a full NW point but no SE point, we can assume the user wants a single point and
- // so we will copy the NW values to the SE
- if (this.model.get("north") != null && this.model.get("west") != null && isMissingSEPoint) {
- this.model.set("south", this.model.get("north"));
- this.model.set("east", this.model.get("west"));
- }
- // Same for when there is a SE point but no NW point
- else if (this.model.get("south") != null && this.model.get("east") != null && isMissingNWPoint) {
- this.model.set("north", this.model.get("south"));
- this.model.set("west", this.model.get("east"));
- }
- }
-
-
- // Validate the coordinate boxes
- //this.validateCoordinates(e);
-
- //If this model is part of the EML inside the root data package, mark the package as changed
- if (this.model.get("parentModel")) {
- if (this.model.get("parentModel").type == "EML" && _.contains(MetacatUI.rootDataPackage.models, this.model.get("parentModel"))) {
- MetacatUI.rootDataPackage.packageModel.set("changed", true);
- }
- }
-
- this.validate();
- },
-
- /**
- * Checks to see if any error messages need to be removed. If not, then it performs validation
- * across the row and displays any errors. This id called when the user clicks out of an edit box
- * on to the page.
- *
- * @param e The event
- * @param options
- */
- validate: function (e, options) {
-
- //Query for the EMlGeoCoverageView element that the user is actively interacting with
- var activeGeoCovEl = $(document.activeElement).parents(".eml-geocoverage");
-
- //If the user is not actively in this view, then exit
- if (activeGeoCovEl.length && activeGeoCovEl[0] == this.el)
- return;
-
- //If the model is valid, then remove error styling and exit
- if( this.model.isValid() ) {
- this.$(".error").removeClass("error");
- this.$el.removeClass("error");
- this.$(".notification").empty();
- this.model.trigger("valid");
-
- return;
- }
- else{
-
- this.showValidation();
-
- }
-
- },
-
- /*
- * Resets the error messaging and displays the current error messages for this model
- * This function is used by the EML211EditorView during the package validation process
- */
- showValidation: function(){
- this.$(".error").removeClass("error");
- this.$el.removeClass("error");
- this.$(".notification").empty();
-
- var errorMessages = "";
-
- for( field in this.model.validationError ){
- this.$("[data-attribute='" + field + "']").addClass("error");
-
- errorMessages += this.model.validationError[field] + " ";
- }
-
- this.$(".notification").text(errorMessages).addClass("error");
- },
-
- /**
- * Highlight what will be removed when the remove icon is hovered over
- *
- */
- toggleRemoveClass: function () {
- this.$el.toggleClass("remove-preview");
- },
-
- /**
- * Unmarks this view as new
- *
- */
- notNew: function () {
- this.$el.removeClass("new");
- this.isNew = false;
- }
- });
-
- return EMLGeoCoverageView;
- });
+ this.model.set("south", value);
+ else if (attribute == "south" && this.model.get("south") != null)
+ this.model.set("north", value);
+ else if (attribute == "west" && this.model.get("west") != null)
+ this.model.set("east", value);
+ else if (attribute == "east" && this.model.get("east") != null)
+ this.model.set("west", value);
+ } else {
+ //Find out if we are missing a complete NW or SE point
+ var isMissingNWPoint =
+ this.model.get("north") == null && this.model.get("west") == null,
+ isMissingSEPoint =
+ this.model.get("south") == null && this.model.get("east") == null;
+
+ // If there is a full NW point but no SE point, we can assume the user
+ // wants a single point and so we will copy the NW values to the SE
+ if (
+ this.model.get("north") != null &&
+ this.model.get("west") != null &&
+ isMissingSEPoint
+ ) {
+ this.model.set("south", this.model.get("north"));
+ this.model.set("east", this.model.get("west"));
+ }
+ // Same for when there is a SE point but no NW point
+ else if (
+ this.model.get("south") != null &&
+ this.model.get("east") != null &&
+ isMissingNWPoint
+ ) {
+ this.model.set("north", this.model.get("south"));
+ this.model.set("west", this.model.get("east"));
+ }
+ }
+
+ // Validate the coordinate boxes this.validateCoordinates(e);
+
+ //If this model is part of the EML inside the root data package, mark
+ //the package as changed
+ if (this.model.get("parentModel")) {
+ if (
+ this.model.get("parentModel").type == "EML" &&
+ _.contains(
+ MetacatUI.rootDataPackage.models,
+ this.model.get("parentModel")
+ )
+ ) {
+ MetacatUI.rootDataPackage.packageModel.set("changed", true);
+ }
+ }
+
+ this.validate();
+ },
+
+ /**
+ * Checks to see if any error messages need to be removed. If not, then it
+ * performs validation across the row and displays any errors. This id
+ * called when the user clicks out of an edit box on to the page.
+ *
+ * @param {Event} e - The event that triggered this function
+ * @param {Object} options - Validation options
+ */
+ validate: function (e, options) {
+ //Query for the EMlGeoCoverageView element that the user is actively
+ //interacting with
+ var activeGeoCovEl = $(document.activeElement).parents(
+ ".eml-geocoverage"
+ );
+
+ //If the user is not actively in this view, then exit
+ if (activeGeoCovEl.length && activeGeoCovEl[0] == this.el) return;
+
+ //If the model is valid, then remove error styling and exit
+ if (this.model.isValid()) {
+ this.$(".error").removeClass("error");
+ this.$el.removeClass("error");
+ this.$(".notification").empty();
+ this.model.trigger("valid");
+
+ return;
+ } else {
+ this.showValidation();
+ }
+ },
+
+ /*
+ * Resets the error messaging and displays the current error messages for
+ * this model This function is used by the EML211EditorView during the
+ * package validation process
+ */
+ showValidation: function () {
+ this.$(".error").removeClass("error");
+ this.$el.removeClass("error");
+ this.$(".notification").empty();
+
+ const errorObj = this.model.validationError;
+ // Get all of the field keys
+ const fields = Object.keys(errorObj);
+ // Get all of the error messages (values). Remove duplicates.
+ let errorMessages = [...new Set(Object.values(errorObj))];
+ // Join the error messages into a single string
+ errorMessages = errorMessages.join(" ");
+
+ // Highlight the fields that need to be fixed
+ fields.forEach((field) => {
+ this.$("[data-attribute='" + field + "']").addClass("error");
+ })
+ // Show the combined error message
+ this.$(".notification").text(errorMessages).addClass("error");
+ },
+
+ /**
+ * Highlight what will be removed when the remove icon is hovered over.
+ */
+ toggleRemoveClass: function () {
+ this.$el.toggleClass("remove-preview");
+ },
+
+ /**
+ * Unmark this view as news
+ */
+ notNew: function () {
+ this.$el.removeClass("new");
+ this.isNew = false;
+ },
+ }
+ );
+
+ return EMLGeoCoverageView;
+});
diff --git a/test/config/tests.json b/test/config/tests.json
index a558e0dec..842aa0e71 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/models/metadata/eml211/EMLGeoCoverage.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/models/metadata/eml211/EMLGeoCoverage.spec.js b/test/js/specs/unit/models/metadata/eml211/EMLGeoCoverage.spec.js
new file mode 100644
index 000000000..de3717d30
--- /dev/null
+++ b/test/js/specs/unit/models/metadata/eml211/EMLGeoCoverage.spec.js
@@ -0,0 +1,162 @@
+define([
+ "../../../../../../../../src/js/models/metadata/eml211/EMLGeoCoverage",
+], function (EMLGeoCoverage) {
+ // Configure the Chai assertion library
+ var should = chai.should();
+ var expect = chai.expect;
+
+ describe("EMLGeoCoverage Test Suite", function () {
+ /* Set up */
+ beforeEach(function () {
+ let testEML = `
+ Rhine-Main-Observatory
+
+ 9.0005
+ 9.0005
+ 50.1600
+ 50.1600
+
+ `;
+ // remove ALL whitespace
+ testEML = testEML.replace(/\s/g, "");
+ this.testEML = testEML;
+ });
+
+ /* Tear down */
+ afterEach(function () {
+ delete this.testEML;
+ });
+
+ describe("Initialization", function () {
+ it("should create a EMLGeoCoverage instance", function () {
+ new EMLGeoCoverage().should.be.instanceof(EMLGeoCoverage);
+ });
+ });
+
+ describe("parse()", function () {
+ it("should parse EML", function () {
+
+ var emlGeoCoverage = new EMLGeoCoverage(
+ { objectDOM: this.testEML },
+ { parse: true }
+ );
+
+ emlGeoCoverage
+ .get("description")
+ .should.equal("Rhine-Main-Observatory");
+ emlGeoCoverage.get("east").should.equal("9.0005");
+ emlGeoCoverage.get("north").should.equal("50.1600");
+ emlGeoCoverage.get("south").should.equal("50.1600");
+ emlGeoCoverage.get("west").should.equal("9.0005");
+ });
+ });
+
+ describe("serialize()", function () {
+
+ it("should serialize to XML", function () {
+ var emlGeoCoverage = new EMLGeoCoverage(
+ { objectDOM: this.testEML },
+ { parse: true }
+ );
+ var xmlString = emlGeoCoverage.serialize();
+ xmlString.should.equal(this.testEML);
+ });
+ });
+
+ describe("validation", function () {
+ it("should get the status of the coordinates", function () {
+ var emlGeoCoverage = new EMLGeoCoverage(
+ { objectDOM: this.testEML },
+ { parse: true }
+ );
+ var status = emlGeoCoverage.getCoordinateStatus();
+ status.north.isSet.should.equal(true);
+ status.north.isValid.should.equal(true);
+ status.east.isSet.should.equal(true);
+ status.east.isValid.should.equal(true);
+ status.south.isSet.should.equal(true);
+ status.south.isValid.should.equal(true);
+ status.west.isSet.should.equal(true);
+ status.west.isValid.should.equal(true);
+ });
+
+ it("should validate the coordinates", function () {
+ var emlGeoCoverage = new EMLGeoCoverage(
+ { objectDOM: this.testEML },
+ { parse: true }
+ );
+ var status = emlGeoCoverage.getCoordinateStatus();
+ var errors = emlGeoCoverage.generateStatusErrors(status);
+ expect(errors).to.be.empty;
+ });
+
+ it("should give an error if the coordinates are invalid", function () {
+ var emlGeoCoverage = new EMLGeoCoverage(
+ { objectDOM: this.testEML },
+ { parse: true }
+ );
+ emlGeoCoverage.set("north", "100");
+ var errors = emlGeoCoverage.validate();
+ errors.north.should.equal(
+ "The Northwest latitude must be between -90 and 90."
+ );
+ });
+
+ it("should give an error if the coordinates are missing", function () {
+ var emlGeoCoverage = new EMLGeoCoverage(
+ { objectDOM: this.testEML },
+ { parse: true }
+ );
+ emlGeoCoverage.set("north", "");
+ var errors = emlGeoCoverage.validate();
+ errors.north.should.equal("Each coordinate must include a latitude AND longitude.");
+ });
+
+ it("should give an error if the north and south coordinates are reversed", function () {
+ var emlGeoCoverage = new EMLGeoCoverage(
+ { objectDOM: this.testEML },
+ { parse: true }
+ );
+ emlGeoCoverage.set("north", "40");
+ emlGeoCoverage.set("south", "50");
+ var errors = emlGeoCoverage.validate();
+ const msg = "The North latitude must be greater than the South latitude.";
+ errors.north.should.equal(msg);
+ errors.south.should.equal(msg);
+ });
+
+ it("should give an error if the bounds cross the anti-meridian", function () {
+ var emlGeoCoverage = new EMLGeoCoverage(
+ { objectDOM: this.testEML },
+ { parse: true }
+ );
+ emlGeoCoverage.set("west", "170");
+ emlGeoCoverage.set("east", "-170");
+ var errors = emlGeoCoverage.validate();
+ errors.west.should.equal("The bounding box cannot cross the anti-meridian.");
+ errors.east.should.equal("The bounding box cannot cross the anti-meridian.");
+ });
+
+ it("should give an error if the bounds contain the north pole", function () {
+ var emlGeoCoverage = new EMLGeoCoverage(
+ { objectDOM: this.testEML },
+ { parse: true }
+ );
+ emlGeoCoverage.set("north", "90");
+ var errors = emlGeoCoverage.validate();
+ errors.north.should.equal("The bounding box cannot contain the North or South pole.");
+ });
+
+ it("should give an error if the bounds contain the south pole", function () {
+ var emlGeoCoverage = new EMLGeoCoverage(
+ { objectDOM: this.testEML },
+ { parse: true }
+ );
+ emlGeoCoverage.set("south", "-90");
+ var errors = emlGeoCoverage.validate();
+ errors.south.should.equal("The bounding box cannot contain the North or South pole.");
+ });
+
+ });
+ });
+});
\ No newline at end of file