Source: src/js/models/metadata/eml211/EMLMissingValueCode.js

define(["backbone"], function (Backbone) {
  /**
   * @class EMLMissingValueCode
   * @classdesc A missing value code is a code that is used to indicate
   * that a value is missing from the data.
   * @see https://eml.ecoinformatics.org/schema/eml-attribute_xsd.html
   * @classcategory Models/Metadata/EML211
   * @since 2.26.0
   * @extends Backbone.Model
   */
  var EMLMissingValueCode = Backbone.Model.extend(
    /** @lends EMLMissingValueCode.prototype */ {
      /**
       * The default attributes for an EMLMissingValueCode model.
       * @returns {object} The default attributes
       * @property {string} type - The element type in the DOM
       * @property {string} code - The missing value code
       * @property {string} codeExplanation - The explanation for the missing value code
       * @property {string[]} nodeOrder - The order of the EML nodes in this object
       */
      defaults: function () {
        return {
          type: "missingValueCode",
          code: "",
          codeExplanation: "",
          nodeOrder: ["code", "codeExplanation"],
        };
      },

      /*
       * Parse the incoming attribute's XML elements.
       */
      parse: function (attributes, options) {
        if (!attributes) return {};
        const objectDOM = attributes.objectDOM || attributes.objectXML;
        if (!objectDOM) return {};
        const $objectDOM = $(objectDOM);

        this.defaults().nodeOrder.forEach((node) => {
          attributes[node] = $objectDOM.children(node.toLowerCase()).text();
        });

        attributes.objectDOM = $objectDOM[0];
        return attributes;
      },

      /**
       * Create an XML string from the model's attributes.
       * @return {string} The XML string
       */
      serialize: function () {
        let xml = this.updateDOM().outerHTML;
        const elNames = this.get("nodeOrder");
        elNames.push(this.get("type"));
        // replace lowercase node names with camelCase
        elNames.forEach((elName) => {
          let elNameLower = elName.toLowerCase();
          xml = xml.replace(`<${elNameLower}>`, `<${elName}>`);
          xml = xml.replace(`</${elNameLower}>`, `</${elName}>`);
        });
        return xml;
      },

      /**
       * Copy the original XML and update fields in a DOM object
       * @param {object} objectDOM - The DOM object to update
       */
      updateDOM: function (objectDOM) {
        const type = this.get("type") || "missingValueCode";
        if (!objectDOM) {
          objectDOM = this.get("objectDOM") || this.get("objectXML");
        }
        if (!objectDOM) {
          objectDOM = document.createElement(type);
        }
        const $objectDOM = $(objectDOM);

        this.get("nodeOrder").forEach((nodeName) => {
          // Remove any existing nodes
          $objectDOM.children(nodeName.toLowerCase()).remove();

          const newValue = this.get(nodeName)?.trim();

          // Add the new node
          if (newValue) {
            const node = document.createElement(nodeName);
            $(node).text(newValue);
            $objectDOM.append(node);
          }
        });

        if (this.isEmpty()) {
          return null;
        } else {
          return $objectDOM[0];
        }
      },

      /**
       * Return true if all of the model's attributes are empty
       * @return {boolean}
       */
      isEmpty: function () {
        return !this.get("code") && !this.get("codeExplanation");
      },

      /**
       * Validate the model attributes
       * @return {object|undefined} The validation errors, if any
       */
      validate() {
        if (this.isEmpty()) return undefined;

        const errors = {};

        // Need a code and an explanation. Both must be non-empty strings.
        let code = this.get("code")?.trim();
        let codeExplanation = this.get("codeExplanation")?.trim();

        this.set("code", code);
        this.set("codeExplanation", codeExplanation);

        if (!code || !codeExplanation) {
          errors.missingValueCode =
            "Both a missing value code and explanation are required.";
          return errors;
        }

        return Object.keys(errors).length > 0 ? errors : undefined;
      },
    },
  );

  return EMLMissingValueCode;
});