define(["jquery", "underscore", "backbone",
"models/DataONEObject"],
function($, _, Backbone, DataONEObject) {
/**
* @class EMLNumericDomain
* @classdesc EMLNumericDomain represents the measurement scale of an interval
* or ratio measurement scale attribute, and is an extension of
* EMLMeasurementScale.
* @classcategory Models/Metadata/EML211
* @see https://eml.ecoinformatics.org/schema/eml-attribute_xsd.html#AttributeType_measurementScale
*/
var EMLNumericDomain = Backbone.Model.extend(
/** @lends EMLNumericDomain.prototype */{
type: "EMLNumericDomain",
/* Attributes of an EMLNonNumericDomain object */
defaults: function(){
return {
/* Attributes from EML, extends attributes from EMLMeasurementScale */
measurementScale: null, // the required name of this measurement scale
unit: null, // the required standard or custom unit definition
precision: null, // the precision of the observed number
numericDomain: {} // a required numeric domain object or its reference
}
},
/**
* The map of lower case to camel case node names
* needed to deal with parsing issues with $.parseHTML().
* Use this until we can figure out issues with $.parseXML().
*/
nodeNameMap: {
"standardunit": "standardUnit",
"customunit": "customUnit",
"numericdomain": "numericDomain",
"numbertype": "numberType"
},
/* Initialize an EMLNonNumericDomain object */
initialize: function(attributes, options) {
this.on("change:numericDomain", this.trickleUpChange);
},
/**
* Parse the incoming measurementScale's XML elements
*/
parse: function(attributes, options) {
var $objectDOM;
var measurementScale;
var rootNodeName;
if ( attributes.objectDOM ) {
rootNodeName = $(attributes.objectDOM)[0].localName;
$objectDOM = $(attributes.objectDOM);
} else if ( attributes.objectXML ) {
rootNodeName = $(attributes.objectXML)[0].localName;
$objectDOM = $($(attributes.objectXML)[0]);
} else {
return {};
}
// do we have an appropriate measurementScale tree?
var index = _.indexOf(["measurementscale","interval", "ratio"], rootNodeName);
if ( index == -1 ) {
throw new Error("The measurement scale XML does not have a root " +
"node of 'measurementScale', 'interval', or 'ratio'.");
}
// If measurementScale is present, add it
if ( rootNodeName == "measurementscale" ) {
attributes.measurementScale = $objectDOM.children().first()[0].localName;
$objectDOM = $objectDOM.children().first();
} else {
attributes.measurementScale = $objectDOM.localName;
}
// Add the unit
var unitObject = {};
var unit = $objectDOM.children("unit");
var standardUnitNodes = unit.children("standardunit"),
customUnitNodes = unit.children("customunit"),
standardUnit,
customUnit;
if ( standardUnitNodes.length ) {
standardUnit = standardUnitNodes.text();
if( standardUnit )
unitObject.standardUnit = standardUnit;
}
else if( customUnitNodes.length ){
customUnit = customUnitNodes.text();
if( customUnit )
unitObject.customUnit = customUnit;
}
attributes.unit = unitObject;
// Add the precision
var precision = $objectDOM.children("precision").text();
if ( precision ) {
attributes.precision = precision;
}
// Add the numericDomain
var numericDomainObject = {};
var numericDomain = $objectDOM.children("numericdomain");
var numberType;
var boundsArray = [];
var boundsObject;
var bounds;
var minimum;
var maximum;
var references;
if ( numericDomain ) {
// Add the XML id of the numeric domain
if ( $(numericDomain).attr("id") ) {
numericDomainObject.xmlID = $(numericDomain).attr("id");
}
// Add the numberType
numberType = $(numericDomain).children("numbertype");
if ( numberType ) {
numericDomainObject.numberType = numberType.text();
// Add optional bounds
bounds = $(numericDomain).children("bounds");
if ( bounds.length ) {
_.each(bounds, function(bound) {
boundsObject = {}; // initialize on each
minimum = $(bound).children("minimum").text();
maximum = $(bound).children("maximum").text();
if ( minimum && maximum ) {
boundsObject.minimum = minimum;
boundsObject.maximum = maximum;
} else if ( minimum ) {
boundsObject.minimum = minimum;
} else if ( maximum ) {
boundsObject.maximum = maximum;
}
// If one of min or max is defined, add to the bounds array
if ( boundsObject.minimum || boundsObject.maximum ) {
boundsArray.push(boundsObject);
}
});
}
numericDomainObject.bounds = boundsArray;
} else {
// Otherwise look for references
references = $(numericDomain).children("references");
if ( references ) {
numericDomainObject.references = references.text();
}
}
attributes.numericDomain = numericDomainObject;
}
attributes.objectDOM = $objectDOM[0];
return attributes;
},
/* Serialize the model to XML */
serialize: function() {
var objectDOM = this.updateDOM();
var xmlString = objectDOM.outerHTML;
// Camel-case the XML
xmlString = this.formatXML(xmlString);
return xmlString;
},
/* Copy the original XML DOM and update it with new values from the model */
updateDOM: function(objectDOM) {
var nodeToInsertAfter;
var type = this.get("measurementScale");
if ( typeof type === "undefined") {
console.warn("Defaulting to an interval measurementScale.");
type = "interval";
}
if ( ! objectDOM ) {
objectDOM = this.get("objectDOM");
}
var objectXML = this.get("objectXML");
// If present, use the cached DOM
if ( objectDOM ) {
objectDOM = objectDOM.cloneNode(true);
// otherwise, use the cached XML
} else if ( objectXML ){
objectDOM = $(objectXML)[0].cloneNode(true);
// This is new, create it
} else {
objectDOM = document.createElement(type);
}
// Update the unit
var unit = this.get("unit");
var unitNode;
var unitTypeNode;
if ( unit ) {
// Remove any existing unit
$(objectDOM).find("unit").remove();
// Build a unit element, and populate a standard or custom child
unitNode = document.createElement("unit");
if ( typeof unit.standardUnit !== "undefined") {
unitTypeNode = document.createElement("standardUnit");
$(unitTypeNode).text(unit.standardUnit);
} else if ( typeof unit.customUnit !== "undefined" ) {
unitTypeNode = document.createElement("customUnit");
$(unitTypeNode).text(unit.customUnit);
} else {
// Hmm, unit isn't an object?
// Default to a standard unit
unitTypeNode = document.createElement("standardUnit");
if ( typeof unit === "string") {
$(unitTypeNode).text(unit);
console.warn("EMLNumericDomain.unit should be an object.");
} else {
// We're really striking out. Default to dimensionless.
$(unitTypeNode).text("dimensionless");
console.warn("Defaulting EMLNumericDomain.unit to dimensionless.");
}
}
$(unitNode).append(unitTypeNode);
// Add the unit to the DOM
nodeToInsertAfter = this.getEMLPosition(objectDOM, "unit");
if( ! nodeToInsertAfter ) {
$(objectDOM).prepend(unitNode);
} else {
$(nodeToInsertAfter).after(unitNode);
}
}
// Update the precision
if ( this.get("precision") ) {
if ( $(objectDOM).find("precision").length ) {
$(objectDOM).find("precision").text(this.get("precision"));
} else {
nodeToInsertAfter = this.getEMLPosition(objectDOM, "precision");
if( ! nodeToInsertAfter ) {
$(objectDOM).append($(document.createElement("precision"))
.text(this.get("precision"))[0]);
} else {
$(nodeToInsertAfter).after(
$(document.createElement("precision"))
.text(this.get("precision"))[0]
);
}
}
}
// Update the numericDomain
var numericDomain = this.get("numericDomain");
var numericDomainNode = $(objectDOM).find("numericdomain")[0];
var numberType;
var numberTypeNode;
var minBound;
var maxBound;
var boundsNode;
var minBoundNode;
var maxBoundNode;
if ( numericDomain ) {
var oldNumericDomainNode = $(numericDomainNode).clone();
// Remove the existing numericDomainNode node
if ( typeof numericDomainNode !== "undefined" ) {
numericDomainNode.remove();
}
// Build the new numericDomain node
numericDomainNode = document.createElement("numericdomain");
// Do we have numberType?
if ( typeof numericDomain.numberType !== "undefined" ) {
numberTypeNode = document.createElement("numbertype");
$(numberTypeNode).text(numericDomain.numberType);
$(numericDomainNode).append(numberTypeNode);
}
// Do we have bounds?
if ( typeof numericDomain.bounds !== "undefined" &&
numericDomain.bounds.length ) {
_.each(numericDomain.bounds, function(bound) {
minBound = bound.minimum;
maxBound = bound.maximum;
boundsNode = document.createElement("bounds");
var hasBounds = typeof minBound !== "undefined" || typeof maxBound !== "undefined";
if ( hasBounds ) {
// Populate the minimum element
if ( typeof minBound !== "undefined" ) {
minBoundNode = $(document.createElement("minimum"));
minBoundNode.text(minBound);
var existingExclusive = oldNumericDomainNode.find("minimum").attr("exclusive");
if( !existingExclusive || existingExclusive === "false" )
minBoundNode.attr("exclusive", "false");
else
minBoundNode.attr("exclusive", "true");
}
// Populate the maximum element
if ( typeof maxBound !== "undefined" ) {
maxBoundNode = $(document.createElement("maximum"));
maxBoundNode.text(maxBound);
var existingExclusive = oldNumericDomainNode.find("maximum").attr("exclusive");
if( !existingExclusive || existingExclusive === "false" )
maxBoundNode.attr("exclusive", "false");
else
maxBoundNode.attr("exclusive", "true");
}
$(boundsNode).append(minBoundNode, maxBoundNode);
$(numericDomainNode).append(boundsNode);
} else {
// Do nothing. Content is missing, don't append the node
}
});
} else {
// Basically do nothing. Don't append the numericDomain element
// TODO: handle numericDomain.references
}
nodeToInsertAfter = this.getEMLPosition(objectDOM, "numericDomain");
if( ! nodeToInsertAfter ) {
$(objectDOM).append(numericDomainNode);
} else {
$(nodeToInsertAfter).after(numericDomainNode);
}
}
return objectDOM;
},
formatXML: function(xmlString){
return DataONEObject.prototype.formatXML.call(this, xmlString);
},
/**/
getEMLPosition: function(objectDOM, nodeName) {
// TODO: set the node order
var nodeOrder = ["unit", "precision", "numericDomain"];
var position = _.indexOf(nodeOrder, nodeName);
// Append to the bottom if not found
if ( position == -1 ) {
return $(objectDOM).children().last()[0];
}
// Otherwise, go through each node in the node list and find the
// position where this node will be inserted after
for ( var i = position - 1; i >= 0; i-- ) {
if ( $(objectDOM).find(nodeOrder[i]).length ) {
return $(objectDOM).find(nodeOrder[i]).last()[0];
}
}
},
validate: function(){
var errors = {};
if(!this.get("unit"))
errors.unit = "Choose a unit.";
if( Object.keys(errors).length )
return errors;
else{
this.trigger("valid");
return;
}
},
/*
* 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;
},
/* Let the top level package know of attribute changes from this object */
trickleUpChange: function(){
MetacatUI.rootDataPackage.packageModel.set("changed", true);
}
});
return EMLNumericDomain;
}
);