# Source: src/js/models/metadata/eml211/EMLNonNumericDomain.js

    define(["jquery", "underscore", "backbone",
            "models/DataONEObject"],
        function($, _, Backbone, DataONEObject) {

            /**
             * @class EMLNonNumericDomain
             * @classdesc EMLNonNumericDomain represents the measurement scale of a nominal
             * or ordinal measurement scale attribute, and is an extension of
             * EMLMeasurementScale.
             * @classcategory Models/Metadata/EML211
             * @see https://eml.ecoinformatics.org/schema/eml-attribute_xsd.html#AttributeType_AttributeType_measurementScale_AttributeType_AttributeType_measurementScale_nominal_nonNumericDomain
             * @extends Backbone.Model
             * @constructor
             */
            var EMLNonNumericDomain = Backbone.Model.extend(
                /** @lends EMLNonNumericDomain.prototype */{

            	type: "EMLNonNumericDomain",

                /* Attributes of an EMLNonNumericDomain object */
                defaults: function(){
                    return {
                      /* Attributes from EML, extends attributes from EMLMeasurementScale */
                      measurementScale: null, // the name of this measurement scale
                      nonNumericDomain: [] // One or more of enumeratedDomain, textDomain, references
                    }
                },

                /**
                 * 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().
                 * @type {object}
                 */
                nodeNameMap: {
                    "nonnumericdomain": "nonNumericDomain",
                    "enumerateddomain": "enumeratedDomain",
                    "textdomain": "textDomain",
                    "externalcodeset": "externalCodeSet",
                    "codesetname": "codesetName",
                    "codeseturl": "codesetURL",
                    "entityCodeList": "entityCodeList",
                    "entityreference": "entityReference",
                    "valueattributereference": "valueAttributeReference",
                    "definitionattributereference": "definitionAttributeReference",
                    "orderattributereference": "orderAttributeReference",
                    "sourced": "source"
                },

                /* Initialize an EMLNonNumericDomain object */
                initialize: function(attributes, options) {

                    this.on("change:nonNumericDomain", this.trickleUpChange);
                },

                /**
                 * Parse the incoming measurementScale's XML elements
                 */
                parse: function(attributes, options) {

                    var $objectDOM;
                    var nonNumericDomainNodeList;
                    var domainNodeList; // the list of domain elements
                    var domain; // the text or enumerated domain to parse
                    var domainObject; // The parsed domain object to be added to attributes.nonNumericDomain
                    var rootNodeName; // Name of the fragment root elements

                    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", "nominal", "ordinal"], rootNodeName);
                    if ( index == -1 ) {
                        throw new Error("The measurement scale XML does not have a root " +
                            "node of 'measurementScale', 'nominal', or 'ordinal'.");
                    }

                    // 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;
                    }

                    nonNumericDomainNodeList = $objectDOM.find("nonnumericdomain");

                    if ( nonNumericDomainNodeList && nonNumericDomainNodeList.length > 0 ) {
                        domainNodeList = nonNumericDomainNodeList[0].children;

                    } else {
                        // No content is available, return
                        return attributes;
                    }

                    // Initialize an array of nonNumericDomain objects
                    attributes.nonNumericDomain = [];

                    // Set each domain if we have it
                    if ( domainNodeList && domainNodeList.length > 0 ) {

                        _.each(domainNodeList, function(domain) {
                            if ( domain ) {
                                // match the camelCase name since DOMParser() is XML-aware
                                switch ( domain.localName ) {
                                    case "textdomain":
                                        domainObject = this.parseTextDomain(domain);
                                        break;
                                    case "enumerateddomain":
                                        domainObject = this.parseEnumeratedDomain(domain);
                                        break;
                                    case "references":
                                        // TODO: Support references
                                        console.log("In EMLNonNumericDomain.parse()" +
                                            "We don't support references yet ");
                                    default:
                                        console.log("Unrecognized nonNumericDomain: " + domain.nodeName);
                                }
                            }
                            attributes.nonNumericDomain.push(domainObject);
                        }, this);

                    }

                    // Add in the textDomain content if present
                    // TODO

                    attributes.objectDOM = $objectDOM[0];

                    return attributes;
                },

                /* Parse the nonNumericDomain/textDomain fragment
                 * returning an object with a textDomain attribute, like:
                 * {
                 *     textDomain: {
                 *         definition: "Some definition",
                 *         pattern: ["*", "\w", "[0-9]"],
                 *         source: "Some source reference"
                 *     }
                 * }
                 */
                parseTextDomain: function(domain) {
                    var domainObject = {};
                    domainObject.textDomain = {};
                    var xmlID;
                    var definition;
                    let patterns = [];
                    var source;

                    // Add the XML id attribute
                    if ( $(domain).attr("id") ) {
                        xmlID = $(domain).attr("id");
                    } else {
                        // Generate an id if it's not found
                        xmlID = DataONEObject.generateId();
                    }
                    domainObject.textDomain.xmlID = xmlID;

                    // Add the definition
                    definition = $(domain).children("definition").text();
                    domainObject.textDomain.definition = definition;

                    // Add the pattern
                    _.each($(domain).children("pattern"), function(pattern) {
                         patterns.push(pattern.textContent);
                    });
                    domainObject.textDomain.pattern = patterns;

                    // Add the source
                    source = $(domain).children("sourced").text();
                    domainObject.textDomain.source = source;

                     return domainObject;
                },

                /* Parse the nonNumericDomain/enumeratedDomain fragment
                 * returning an object with an enumeratedDomain attribute, like:
                 * var emlCitation = {};
                 * var nonNumericDomain = [
                 *     {
                 *         enumeratedDomain: {
                 *             codeDefinition: [
                 *                 {
                 *                     code: "Some code", // required
                 *                     definition: "Some definition", // required
                 *                     source: "Some source"
                 *                 } // repeatable
                 *             ]
                 *         }
                 *     }, // or
                 *     {
                 *         enumeratedDomain: {
                 *             externalCodeSet: [
                 *                 {
                 *                     codesetName: "Some code", // required
                 *                     citation: [emlCitation], // one of citation or codesetURL
                 *                     codesetURL: ["Some URL"] // is required, both repeatable
                 *                 } // repeatable
                 *             ]
                 *         }
                 *     }, // or
                 *     {
                 *         enumeratedDomain: {
                 *             entityCodeList: {
                 *                 entityReference: "Some reference", // required
                 *                 valueAttributeReference: "Some attr reference", // required
                 *                 definitionAttributeReference: "Some definition attr reference", // required
                 *                 orderAttributeReference: "Some order attr reference"
                 *             }
                 *         }
                 *     }
                 * ]
                 */
                parseEnumeratedDomain: function(domain) {
                    var domainObject = {};
                    domainObject.enumeratedDomain = {};
                    var codeDefinition = {};
                    var externalCodeSet = {};
                    var entityCodeList = {};
                    var xmlID;

                    // Add the XML id attribute
                    if ( $(domain).attr("id") ) {
                        xmlID = $(domain).attr("id");
                    } else {
                        // Generate an id if it's not found
                        xmlID = DataONEObject.generateId();
                    }
                    domainObject.enumeratedDomain.xmlID = xmlID;

                    // Add the codeDefinitions if present
                    var codeDefinitions = $(domain).children("codedefinition");

                    if ( codeDefinitions.length ) {
                        domainObject.enumeratedDomain.codeDefinition = [];
                        _.each(codeDefinitions, function(codeDef) {
                            var code = $(codeDef).children("code").text();
                            var definition = $(codeDef).children("definition").text();
                            var source = $(codeDef).children("sourced").text() || undefined;
                            domainObject.enumeratedDomain.codeDefinition.push({
                                code: code,
                                definition: definition,
                                source: source
                            });
                        })
                    }
                    return domainObject;
                },

                /* 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 objectDOM;
                    var xmlID; // The id of the textDomain or enumeratedDomain fragment
                    var nonNumericDomainNode;
                    var domainType; // Either textDomain or enumeratedDomain
                    var $domainInDOM; // The jQuery object of the text or enumerated domain from the DOM
                    var nodeToInsertAfter;
                    var domainNode; // Either a textDomain or enumeratedDomain node
                    var definitionNode;
                    var patternNode;
                    var sourceNode;
                    var enumeratedDomainNode;
                    var codeDefinitions;
                    var codeDefinitionNode;
                    var codeNode;

                    var type = this.get("measurementScale");
                    if ( typeof type === "undefined") {
                        console.warn("Defaulting to an nominal measurementScale.");
                        type = "nominal";
                    }
                    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);

                    }

                    if ( this.get("nonNumericDomain").length ) {

                        // Update each nonNumericDomain in the DOM
                        _.each(this.get("nonNumericDomain"), function(domain, i) {

                            // Is this a textDomain or enumeratedDomain?
                            if ( typeof domain.textDomain === "object" ) {
                                domainType = "textDomain";
                                xmlID = domain.textDomain.xmlID;

                            } else if ( typeof domain.enumeratedDomain === "object" ) {
                                domainType = "enumeratedDomain";
                                xmlID = domain.enumeratedDomain.xmlID;
                            } else {
                                console.log("Unrecognized NonNumericDomain type. Skipping.");
                                // TODO: Handle references here
                            }

                            // Update the existing DOM node by id
                            if ( xmlID && $(objectDOM).find("#" + xmlID).length ) {

                                if ( domainType === "textDomain" ) {

                                    let originalTextDomain = $(objectDOM).find("#" + xmlID).find("textdomain");

                                    //If there are existing textDomain nodes in the DOM, update them
                                    if( originalTextDomain.length ){
                                        let updatedTextDomain = this.updateTextDomain(domain.textDomain, originalTextDomain);
                                        originalTextDomain.replaceWith(updatedTextDomain);
                                    }
                                    //If there are no textDomain nodes in the DOM, create new ones
                                    else{
                                      //Create new textDomain nodes
                                      let newTextDomain = this.createTextDomain(domain.textDomain);

                                      //Insert the new textDomain nodes into the nonNumericDomain node
                                      $( $(objectDOM).children("nonnumericdomain")[i] ).html( newTextDomain );
                                    }

                                } else if ( domainType === "enumeratedDomain") {

                                  this.updateEnumeratedDomainDOM(domain.enumeratedDomain, $domainInDOM);
                                }

                            //If there is no XML ID but there are the same number of nonNumericDomains in the model and DOM
                          } else if( this.get("nonNumericDomain").length == $(objectDOM).children("nonnumericdomain").length
                              && $(objectDOM).children("nonnumericdomain").length >= i){

                                //If this is a text domain,
                                if( typeof domain.textDomain === "object" ){

                                    let originalTextDomain = $($(objectDOM).children("nonnumericdomain")[i]).find("textdomain");

                                    //If there are existing textDomain nodes in the DOM, update them
                                    if( originalTextDomain.length ){
                                        let updatedTextDomain = this.updateTextDomain(domain.textDomain, originalTextDomain);
                                        originalTextDomain.replaceWith(updatedTextDomain);
                                    }
                                    //If there are no textDomain nodes in the DOM, create new ones
                                    else{
                                      //Create new textDomain nodes
                                      var newTextDomain = this.createTextDomain(domain.textDomain);

                                      //Insert the new textDomain nodes into the nonNumericDomain node
                                      $( $(objectDOM).children("nonnumericdomain")[i] ).html( newTextDomain );
                                    }

                                }
                                else if(typeof domain.enumeratedDomain === "object"){
                                  //Get the nonNumericDomain node from the DOM
                                  var nonNumericDomainNode = $(objectDOM).children("nonnumericdomain")[i],
                                      enumeratedDomain     = $(nonNumericDomainNode).children("enumerateddomain");

                                  if( enumeratedDomain.length ){
                                    this.updateEnumeratedDomainDOM(domain.enumeratedDomain, enumeratedDomain);
                                  }
                                  else{
                                    //Remove the textDomain node and replace it with an enumeratedDomain node
                                    var textDomainToReplace = $(objectDOM).find("textdomain");

                                    if( textDomainToReplace.length > 0 ){
                                      $(textDomainToReplace[i]).replaceWith(this.createEnumeratedDomainDOM(domain.enumeratedDomain));
                                    }
                                    else{
                                      nonNumericDomainNode.html(this.createEnumeratedDomainDOM(domain.enumeratedDomain, document.createElement("enumerateddomain")));
                                    }

                                  }
                                }

                            // Otherwise append to the DOM
                            } else {

                                // Add the nonNumericDomain element
                                nonNumericDomainNode = document.createElement("nonnumericdomain");

                                if ( domainType === "textDomain" ) {

                                    // Add the definiton element
                                    domainNode = document.createElement("textdomain");
                                    if ( domain.textDomain.definition ) {
                                        definitionNode = document.createElement("definition");
                                        $(definitionNode).text(domain.textDomain.definition);
                                        $(domainNode).append(definitionNode);
                                    }

                                    // Add the pattern element(s)
                                    if ( domain.textDomain.pattern.length ) {
                                        _.each(domain.textDomain.pattern, function(pattern) {
                                            patternNode = document.createElement("pattern");
                                            $(patternNode).text(pattern);
                                            $(domainNode).append(patternNode);
                                        }, this);
                                    }

                                    // Add the source element
                                    if ( domain.textDomain.source ) {
                                        sourceNode = document.createElement("sourced"); // Accommodate parseHTML() with "d"
                                        $(sourceNode).text(domain.textDomain.source);
                                        $(domainNode).append(sourceNode);
                                    }

                                } else if ( domainType === "enumeratedDomain" ) {

                                  nonNumericDomainNode.append(this.createEnumeratedDomainDOM(domain.enumeratedDomain));

                                } else {
                                    console.log("The domainType: " + domainType + " is not recognized.");
                                }
                                $(nonNumericDomainNode).append(domainNode);
                                $(objectDOM).append(nonNumericDomainNode);
                            }
                        }, this);

                    } else {
                        // We have no content, so can't create a valid domain
                        console.log("In EMLNonNumericDomain.updateDOM(),\n" +
                            "references are not handled yet. Returning undefined.");
                        // TODO: handle references here
                        return undefined;
                    }
                    return objectDOM;
                },

                /*
                 * Update the codeDefinitionList in the  first enumeratedDomain
                 * found in the nonNumericDomain array.
                 * TODO: Refactor this to support externalCodeSet and entityCodeList
                 * TODO: Support the source field
                 * TODO: Support repeatable enumeratedDomains
                 * var nonNumericDomain = [
                 *     {
                 *         enumeratedDomain: {
                 *             codeDefinition: [
                 *                 {
                 *                     code: "Some code", // required
                 *                     definition: "Some definition", // required
                 *                     source: "Some source"
                 *                 } // repeatable
                 *             ]
                 *         }
                 *     }
                 * ]
                 */
                updateEnumeratedDomain: function(code, definition, index) {
                    var nonNumericDomain = this.get("nonNumericDomain");
                    var enumeratedDomain = {};
                    var codeDefinitions;

                    if( typeof code == "string" && !code.trim().length ){
                      code = "";
                    }

                    if( typeof definition == "string" && !definition.trim().length ){
                      definition = "";
                    }

                    // Create from scratch
                    if ( !nonNumericDomain.length || !nonNumericDomain[0] || !nonNumericDomain[0].enumeratedDomain) {
                        nonNumericDomain[0] = {
                            enumeratedDomain: {
                                codeDefinition: [
                                    {
                                        code: code,
                                        definition: definition
                                    }
                                ]
                            }
                        }
                    }
                    // Update existing
                    else {
                        enumeratedDomain = this.get("nonNumericDomain")[0].enumeratedDomain;

                        if ( typeof enumeratedDomain !== "undefined" ) {

                          //If there is no code or definition, then remove it from the code list
                          if( !code && code !== 0 && !definition && definition !== 0 ){
                            this.removeCode(index);
                          }
                          else if ( enumeratedDomain.codeDefinition.length >= index ) {
                            //Create a new code object and insert it into the array
                            enumeratedDomain.codeDefinition[index] = {
                                code: code,
                                definition: definition
                            }
                          }
                          else {
                            //Create a new code object and append it to the end of the array
                            enumeratedDomain.codeDefinition.push({
                                code: code,
                                definition: definition
                            });
                          }
                        }
                    }

                    //Manually trigger the change event since we're updating an array on the model
                    this.trigger("change:nonNumericDomain");
                },

                /*
                 * Given a `codeDefinition` HTML node and an enumeratedDomain list,
                 *   this function will update the HTML node code definitions with
                 *   all the code definitions listed in the enumeratedDomain
                 *
                 * @param {object} enumeratedDomain - A literal object with an array of codeDefinitions
                 * @param {DOM Element or jQuery Object} - A DOM Element or jQuery selection that represents the <enumeratedDomain> node
                 */
                updateEnumeratedDomainDOM: function( enumeratedDomain, enumeratedDomainNode ){

                  if ( enumeratedDomain.codeDefinition.length ) {

                      // Update each codeDefinition
                      _.each(enumeratedDomain.codeDefinition, function(codeDef, i) {

                          var codeDefNode = $($(enumeratedDomainNode).children("codedefinition")[i]);

                          if( !codeDefNode.length ){
                            codeDefNode = $(document.createElement("codedefinition"));
                            $(enumeratedDomainNode).append(codeDefNode);
                          }

                          // Update the required code element
                          if ( codeDef.code ) {
                            var codeNode = codeDefNode.children("code");

                            //If there is no <code> XML node, make one
                            if( !codeNode.length ){
                              codeNode = $(document.createElement("code"));
                              codeDefNode.append(codeNode);
                            }

                            //Add the code text to the <code> node
                            codeNode.text(codeDef.code);
                          }
                          else{
                            codeDefNode.children("code").remove();
                          }

                          // Update the required definition element
                          if ( codeDef.definition ) {
                            var defNode = codeDefNode.children("definition");

                            //If there is no <definition> XML node, make one
                            if( !defNode.length ){
                              defNode = $(document.createElement("definition"));
                              codeDefNode.append(defNode);
                            }

                            //Add the definition text to the <definition> node
                            defNode.text(codeDef.definition);
                          }
                          else{
                            codeDefNode.children("definition").remove();
                          }

                          // Update the optional source element
                          if ( codeDef.source ) {
                            // Accommodate parseHTML() with source"d"
                            var sourceNode = codeDefNode.children("sourced");

                            //If there is no <source> XML node, make one
                            if( !sourceNode.length ){
                              sourceNode = $(document.createElement("sourced"));
                              codeDefNode.append(sourceNode);
                            }

                            sourceNode.text(codeDef.source);
                          }
                          else{
                            codeDefNode.children("sourced").remove();
                          }

                      }, this);

                      // If there are more codeDefinition nodes than there are codeDefinitions
                      // in the model, then we need to remove the extraneous nodes
                      var numNodes = $(enumeratedDomainNode).children("codedefinition").length,
                          numCodes = enumeratedDomain.codeDefinition.length;

                      if( numNodes > numCodes ){
                        //Get the extraneous nodes by selecting the last X child elements
                        var nodesToRemove = $(enumeratedDomainNode).children("codedefinition").slice( (numNodes - numCodes) * -1 );
                        //Remove them from the DOM
                        nodesToRemove.remove();
                      }

                  } else if ( domain.enumeratedDomain.externalCodeSet ) {
                      // TODO Handle externalCodeSet

                  } else if ( domain.enumeratedDomain.entityCodeList ) {
                      // TODO Handle entityCodeList
                  }

                  return enumeratedDomainNode;

                },

                /*
                 * Given an enumeratedDomain list, this function will create an
                 *   <enumeratedDomain> HTML element with all the code definitions
                 *   listed in the enumeratedDomain object
                 *
                 * @param {object} enumeratedDomain - A literal object with an array of codeDefinitions
                 * @return {DOM Element} - An <enumerateddomain> DOM element tree with code definitions
                 */
                createEnumeratedDomainDOM: function( enumeratedDomain ){

                  var enumeratedDomainNode = document.createElement("enumerateddomain");

                  if ( enumeratedDomain.codeDefinition.length ) {

                      // Add each codeDefinition
                      _.each(enumeratedDomain.codeDefinition, function(codeDef) {

                          var codeDefinitionNode = document.createElement("codedefinition");

                          // Add the required code element
                          if ( codeDef.code ) {
                              var codeNode = document.createElement("code");
                              $(codeNode).text(codeDef.code);
                              $(codeDefinitionNode).append(codeNode);
                          }

                          // Add the required definition element
                          if ( codeDef.definition ) {
                              var definitionNode = document.createElement("definition");
                              $(definitionNode).text(codeDef.definition);
                              $(codeDefinitionNode).append(definitionNode);
                          }

                          // Add the optional source element
                          if ( codeDef.source ) {
                              var sourceNode = document.createElement("sourced"); // Accommodate parseHTML() with "d"
                              $(sourceNode).text(codeDef.source);
                              $(codeDefinitionNode).append(sourceNode);
                          }
                          $(enumeratedDomainNode).append(codeDefinitionNode);

                      }, this);

                  } else if ( domain.enumeratedDomain.externalCodeSet ) {
                      // TODO Handle externalCodeSet

                  } else if ( domain.enumeratedDomain.entityCodeList ) {
                      // TODO Handle entityCodeList
                  }

                  return enumeratedDomainNode;

                },

                /*
                 * Given a textDomain object, and textDomain DOM object, this function
                 *  will update all the DOM elements with the textDomain object values
                 *
                 * @param {object} textDomain - A literal object representing an EML text domain
                 * @param {DOM Element} textDomainEl - The <textDomain> DOM Element to update
                 * @return {DOM Element} - An <textdomain> DOM element tree to update
                 */
                updateTextDomain: function(textDomain, textDomainEl){

                  if( typeof textDomainEl === "undefined" || (typeof textDomainEl == "object" && textDomainEl.length == 0) )
                    var textDomainEl = document.createElement("textdomain");

                  //Create a shortcut to the jQuery object of the text domain element
                  var $textDomainEl = $(textDomainEl);

                  var definitionEl = $textDomainEl.find("definition");

                  //Update the definition element text
                  if( definitionEl.length > 0 )
                    definitionEl.text(textDomain.definition);
                  else {
                    $textDomainEl.prepend( $(document.createElement("definition")).text(textDomain.definition) );
                  }

                  // Remove existing patterns
                  $textDomainEl.find("pattern").remove();

                  // Add any new patterns
                  if ( textDomain.pattern && textDomain.pattern.length ) {

                      let patterns = Array.from(textDomain.pattern).reverse();

                      _.each(patterns, function(pattern) {

                        //Don't serialize strings with only empty characters
                        if( typeof pattern == "string" && !pattern.trim().length )
                          return;

                        var patternNode = document.createElement("pattern");

                        $(patternNode).text(pattern);

                        // Prepend before the sourced element if present
                        if ( $textDomainEl.find("sourced").length ) {
                            $textDomainEl.find("sourced").before(patternNode);
                        } else {
                            $textDomainEl.append(patternNode);
                        }
                      });
                  }

                  // Update any new source
                  if ( textDomain.source ) {
                      if ( $textDomainEl.find("sourced").length ) {
                          $textDomainEl.find("sourced").text(textDomain.source);
                      } else {
                          //
                          var src = document.createElement("sourced");
                          src.textContent = textDomain.source;
                          $textDomainEl.find("textDomain").append(src);
                      }
                  } else {
                      // Remove the source in the DOM not present in the textDomain
                      // TODO: Uncomment this when we support "source" in the UI
                      // $domainInDOM.children("source").remove();

                  }

                  return textDomainEl;
                },

                /*
                 * Creates a textDomain DOM object with the textDomain object values
                 *
                 * @param {object} textDomain - A literal object representing an EML text domain
                 * @return {DOM Element} - An <textdomain> DOM element tree to update
                 */
                createTextDomain: function(textDomain){
                  var textDomainEl = document.createElement("textdomain");

                  this.updateTextDomain(textDomain, textDomainEl);

                  return textDomainEl;

                },

                /*
                 * Get the DOM node preceding the given nodeName
                 * to find what position in the EML document
                 * the named node should be appended
                 */
                getEMLPosition: function(objectDOM, nodeName) {
                    // TODO: set the node order
                    var nodeOrder = ["enumerateddomain", "textdomain"];

                    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];
                        }
                    }
                },

                /* Let the top level package know of attribute changes from this object */
                trickleUpChange: function(){
                    MetacatUI.rootDataPackage.packageModel.set("changed", true);
                },

                validate: function(){
                	var errors = {};

                	if( !this.get("nonNumericDomain").length )
                		errors.nonNumericDomain = "Choose a possible value type.";
                	else{
                		var domain = this.get("nonNumericDomain")[0];

                		_.each(Object.keys(domain), function(key){

                			//For enumerated domain types
                			if(key == "enumeratedDomain" && domain[key].codeDefinition){

                				var isEmpty = (domain[key].codeDefinition.length == 0) ? true : false;

                				//Validate the list of codes
                				for(var i=0; i < domain[key].codeDefinition.length; i++){

                					var codeDef = domain[key].codeDefinition[i];

                					//If either the code or definition is missing in at least one codeDefinition set,
                					//then this model is invalid
                					if((codeDef.code && !codeDef.definition) || (!codeDef.code && codeDef.definition)){
                						errors.enumeratedDomain = "Provide both a code and definition in each row.";
                						i = domain[key].codeDefinition.length;
                					}
                					else if(domain[key].codeDefinition.length == 1 && !codeDef.code && !codeDef.definition)
                						isEmpty = true;

                				}

                				if(isEmpty)
                					errors.enumeratedDomain = "Define at least one code and definition.";

                			}
                			else if(key == "textDomain" && (typeof domain[key] != "object" || !domain[key].definition)){
                				errors.definition = "Provide a description of the kind of text allowed.";
                			}

                		}, this);

                	}

                	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;

                },

                removeCode: function(index){
                	var codeToRemove = this.get("nonNumericDomain")[0].enumeratedDomain.codeDefinition[index];

                	var newCodeList = _.without(this.get("nonNumericDomain")[0].enumeratedDomain.codeDefinition, codeToRemove);

                	this.get("nonNumericDomain")[0].enumeratedDomain.codeDefinition = newCodeList;

                	this.trigger("change:nonNumericDomain");
                }

            });

            return EMLNonNumericDomain;
        }
    );