Source: src/js/views/metadata/EMLPartyView.js

define([
  "underscore",
  "jquery",
  "backbone",
  "models/metadata/eml211/EMLParty",
  "text!templates/metadata/EMLParty.html",
], function (_, $, Backbone, EMLParty, EMLPartyTemplate) {
  /**
          @class EMLPartyView
          @classdesc  The EMLParty renders the content of an EMLParty model
          @classcategory Views/Metadata
          @extends Backbone.View
        */
  var EMLPartyView = Backbone.View.extend(
    /** @lends EMLPartyView.prototype */ {
      type: "EMLPartyView",

      tagName: "div",

      className: "row-fluid eml-party",

      editTemplate: _.template(EMLPartyTemplate),

      initialize: function (options) {
        if (!options) var options = {};

        this.isNew = options.isNew || (options.model ? false : true);
        this.model = options.model || new EMLParty();
        this.edit = options.edit || false;

        this.$el.data({ model: this.model });
      },

      events: {
        change: "updateModel",
        focusout: "showValidation",
        "keyup .phone": "formatPhone",
        "mouseover .remove": "previewRemove",
        "mouseout .remove": "previewRemove",
      },

      render: function () {
        //Format the given names
        var name = this.model.get("individualName") || {},
          fullGivenName = "";

        //Take multiple given names and combine into one given name.
        //TODO: Support multiple given names as an array
        if (Array.isArray(name.givenName)) {
          fullGivenName = _.map(name.givenName, function (name) {
            if (typeof name != "undefined" && name) return name.trim();
            else return "";
          }).join(" ");
        } else fullGivenName = name.givenName;

        //Get the address object
        var address = Array.isArray(this.model.get("address"))
          ? this.model.get("address")[0] || {}
          : this.model.get("address") || {};

        //Use the template with the editing elements if this view has the "edit" flag on
        if (this.edit) {
          //Send all the EMLParty info to the template
          this.$el.html(
            this.editTemplate({
              uniqueId: this.model.cid,
            }),
          );

          //Populate the form with all the EMLParty values
          this.$("#" + this.model.cid + "-givenName").val(fullGivenName || "");
          this.$("#" + this.model.cid + "-surName").val(name.surName || "");
          this.$("#" + this.model.cid + "-position").val(
            this.model.get("positionName") || "",
          );
          this.$("#" + this.model.cid + "-organizationName").val(
            this.model.get("organizationName") || "",
          );
          this.$("#" + this.model.cid + "-email").val(
            this.model.get("email").length ? this.model.get("email")[0] : "",
          );
          this.$("#" + this.model.cid + "-website").val(
            this.model.get("onlineUrl").length
              ? this.model.get("onlineUrl")[0]
              : "",
          );
          this.$("#" + this.model.cid + "-phone").val(
            this.model.get("phone").length ? this.model.get("phone")[0] : "",
          );
          this.$("#" + this.model.cid + "-fax").val(
            this.model.get("fax").length ? this.model.get("fax")[0] : "",
          );
          this.$("#" + this.model.cid + "-orcid").val(
            Array.isArray(this.model.get("userId"))
              ? this.model.get("userId")[0]
              : this.model.get("userId") || "",
          );
          this.$("#" + this.model.cid + "-address").val(
            address.deliveryPoint && address.deliveryPoint.length
              ? address.deliveryPoint[0]
              : "",
          );
          this.$("#" + this.model.cid + "-address2").val(
            address.deliveryPoint && address.deliveryPoint.length > 1
              ? address.deliveryPoint[1]
              : "",
          );
          this.$("#" + this.model.cid + "-city").val(address.city || "");
          this.$("#" + this.model.cid + "-state").val(
            address.administrativeArea || "",
          );
          this.$("#" + this.model.cid + "-zip").val(address.postalCode || "");
          this.$("#" + this.model.cid + "-country").val(address.country || "");
        }

        //If this EML Party is new/empty, then add the new class
        if (this.isNew) {
          this.$el.addClass("new");
        }

        //Save the view and model on the element
        this.$el.data({
          model: this.model,
          view: this,
        });

        this.$el.attr("data-category", this.model.get("type"));

        return this;
      },

      updateModel: function (e) {
        if (!e) return false;

        //Get the attribute that was changed
        var changedAttr = $(e.target).attr("data-attribute");
        if (!changedAttr) return false;

        //Get the current value
        var currentValue = this.model.get(changedAttr);

        //Addresses and Names have special rules for updating
        switch (changedAttr) {
          case "deliveryPoint":
            this.updateAddress(e);
            return;
          case "city":
            this.updateAddress(e);
            return;
          case "administrativeArea":
            this.updateAddress(e);
            return;
          case "country":
            this.updateAddress(e);
            return;
          case "postalCode":
            this.updateAddress(e);
            return;
          case "surName":
            this.updateName(e);
            return;
          case "givenName":
            this.updateName(e);
            return;
          case "salutation":
            this.updateName(e);
            return;
        }

        //Update the EMLParty model with the new value
        if (Array.isArray(currentValue)) {
          //Get the position that this new value should go in
          var position = this.$("[data-attribute='" + changedAttr + "']").index(
            e.target,
          );

          if ($(e.target).val() == "") {
            //Remove the current value from the array if there is no value in the input field
            currentValue.splice(position, 1);
          } else {
            var emlModel = this.model.getParentEML(),
              value = $(e.target).val();

            if (emlModel) {
              value = emlModel.cleanXMLText(value);
            }

            //Put the new value in the array at the correct position
            currentValue[position] = value;
          }

          this.model.set(changedAttr, currentValue);

          this.model.trigger("change:" + changedAttr);
          this.model.trigger("change");
        } else {
          //If the value of the input field is nothing, then reset the field
          if ($(e.target).val() == "") {
            this.model.set(changedAttr, this.model.defaults()[changedAttr]);
          } else {
            var emlModel = this.model.getParentEML(),
              value = $(e.target).val();

            if (emlModel) {
              value = emlModel.cleanXMLText(value);
            }

            this.model.set(changedAttr, value);
          }
        }

        //If this is a new EML Party, add it to the parent EML211 model
        if (this.isNew) {
          var mergeSuccess = this.model.mergeIntoParent();

          //If the merge was sucessfull, mark this as not new
          if (mergeSuccess) this.notNew();
        }

        //If this EMLParty model has been removed from the parent EML model,
        //then add it back
        if (this.model.get("removed")) {
          var position = this.$el
            .parent()
            .children(".eml-party")
            .index(this.$el);
          this.model.get("parentModel").addParty(this.model);
          this.model.set("removed", false);
        }

        this.model.trickleUpChange();
      },

      updateAddress: function (e) {
        if (!e) return false;

        //Get the address part that was changed
        var changedAttr = $(e.target).attr("data-attribute");
        if (!changedAttr) return false;

        //TODO: Allow multiple addresses - right now we only support editing the first address
        var address = this.model.get("address")[0] || {},
          currentValue = address[changedAttr];

        //Get the parent EML model and the value from the input element
        var emlModel = this.model.getParentEML(),
          value = $(e.target).val();

        //If there is a parent EML model, clean up the text for XML
        if (emlModel) {
          value = emlModel.cleanXMLText(value);
        }

        //Update the address
        if (Array.isArray(currentValue)) {
          //Get the position that this new value should go in
          var position = this.$("[data-attribute='" + changedAttr + "']").index(
            e.target,
          );

          //Put the new value in the array at the correct position
          currentValue[position] = value;
        }
        //Make sure delivery points are saved as arrays
        else if (changedAttr == "deliveryPoint") {
          address[changedAttr] = [value];
        } else address[changedAttr] = value;

        //Update the model
        var allAddresses = this.model.get("address");
        allAddresses[0] = address;
        this.model.set("address", allAddresses);

        //If this is a new EML Party, add it to the parent EML211 model
        if (this.isNew) {
          var mergeSuccess = this.model.mergeIntoParent();

          //If the merge was sucessfull, mark this as not new
          if (mergeSuccess) this.notNew();
        }

        //If this EMLParty model has been removed from the parent EML model,
        //then add it back
        if (this.model.get("removed")) {
          var position = this.$el
            .parent()
            .children(".eml-party")
            .index(this.$el);
          this.model.get("parentModel").addParty(this.model);
          this.model.set("removed", false);
        }

        //Manually trigger the change event since it's an object
        this.model.trigger("change:address");
        this.model.trigger("change");

        this.model.trickleUpChange();
      },

      updateName: function (e) {
        if (!e) return false;

        //Get the address part that was changed
        var changedAttr = $(e.target).attr("data-attribute");
        if (!changedAttr) return false;

        //TODO: Allow multiple given names - right now we only support editing the first given name
        var name = this.model.get("individualName") || {},
          currentValue = String.prototype.trim(name[changedAttr]);

        //Get the parent EML model and the value from the input element
        var emlModel = this.model.getParentEML(),
          value = $(e.target).val().trim();

        //If there is a parent EML model, clean up the text for XML
        if (emlModel) {
          value = emlModel.cleanXMLText(value);
        }

        //Update the name
        if (Array.isArray(currentValue)) {
          //Get the position that this new value should go in
          var position = this.$("[data-attribute='" + changedAttr + "']").index(
            e.target,
          );

          //Put the new value in the array at the correct position
          currentValue[position] = value;
        } else if (changedAttr == "givenName") {
          name.givenName = value;
        } else name[changedAttr] = value;

        //Update the value on the model
        this.model.set("individualName", name);

        //If this is a new EML Party, add it to the parent EML211 model
        if (this.isNew) {
          var mergeSuccess = this.model.mergeIntoParent();

          //If the merge was sucessfull, mark this as not new
          if (mergeSuccess) this.notNew();
        }

        //If this EMLParty model has been removed from the parent EML model,
        //then add it back
        if (this.model.get("removed")) {
          var position = this.$el
            .parent()
            .children(".eml-party")
            .index(this.$el);
          this.model.get("parentModel").addParty(this.model);
          this.model.set("removed", false);
        }

        //Manually trigger a change on the name attribute
        this.model.trigger("change:individualName");
        this.model.trigger("change");

        this.model.trickleUpChange();
      },

      /**
       * Validates and displays error messages for the persons' name, position
       * and organization name.
       *
       */
      showValidation: function () {
        //Remove the error styling
        this.$(".notification").empty();
        this.$(".error").removeClass("error");

        // Check if there are values to validate
        if (this.isEmpty()) {
          //Remove this EMLParty model from it's parent model instead
          //of showing a validation error, since it's completely empty
          this.model.removeFromParent();

          return;
        }
        //If the model is valid, exit
        else if (this.model.isValid()) {
          return;
        } else {
          //Start the full error message string for all the EMLParty errors
          var errorMessages = "";

          //Iterate over each field that has a validation error
          _.mapObject(
            this.model.validationError,
            function (errorMsg, attribute) {
              //Find the input element for this attribute and add the error styling
              this.$("[data-attribute='" + attribute + "']").addClass("error");

              //Add this error message to the full error messages string
              errorMessages += errorMsg + " ";
            },
            this,
          );

          //Add the full error message text to the notification area and add the error styling
          this.$(".notification").text(errorMessages).addClass("error");
        }
      },

      /**
       * Checks if the user has entered any data in the fields.
       *
       * @return {bool} True if the user hasn't entered any party info, otherwise returns false
       */
      isEmpty: function () {
        // If we add any new fields, be sure to add the data-attribute here.
        var attributes = [
          "country",
          "city",
          "administrativeArea",
          "postalCode",
          "deliveryPoint",
          "userId",
          "fax",
          "phone",
          "onlineUrl",
          "email",
          "givenName",
          "surName",
          "positionName",
          "organizationName",
        ];

        for (var i in attributes) {
          var attribute = "[data-attribute='" + attributes[i] + "']";
          if (this.$(attribute).val() != "") return false;
        }

        return true;
      },

      // A function to format text to look like a phone number
      formatPhone: function (e) {
        // Strip all characters from the input except digits
        var input = $(e.target).val().replace(/\D/g, "");

        // Trim the remaining input to ten characters, to preserve phone number format
        input = input.substring(0, 10);

        // Based upon the length of the string, we add formatting as necessary
        var size = input.length;
        if (size == 0) {
          input = input;
        } else if (size < 4) {
          input = "(" + input;
        } else if (size < 7) {
          input = "(" + input.substring(0, 3) + ") " + input.substring(3, 6);
        } else {
          input =
            "(" +
            input.substring(0, 3) +
            ") " +
            input.substring(3, 6) +
            " - " +
            input.substring(6, 10);
        }

        $(e.target).val(input);
      },

      previewRemove: function () {
        this.$("input, img, label").toggleClass("remove-preview");
      },

      /**
       * Changes this view and its model from -new- to -not new-
       * "New" means this EMLParty model is not referenced or stored on a
       * parent model, and this view is being displayed to the user so they can
       * add a new party to their EML (versus edit an existing one).
       */
      notNew: function () {
        this.isNew = false;

        this.$el.removeClass("new");
        this.$el.find(".new").removeClass("new");
      },
    },
  );

  return EMLPartyView;
});