Source: src/js/models/portals/PortalImage.js

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

      /**
       * @class PortalImage
       * @classdesc A Portal Image model represents a single image used in a Portal
       * @classcategory Models/Portals
       * @extends Backbone.Model
       */
      var PortalImageModel = DataONEObject.extend(
        /** @lends PortalImage.prototype */{

        /**
        * @inheritdoc
        */
        type: "PortalImage",

        defaults: function(){
          return _.extend(DataONEObject.prototype.defaults(), {
            identifier: "",
            imageURL: "",
            label: "",
            associatedURL: "",
            objectDOM: null,
            nodeName: "image",
            portalModel: null
          });
        },

        initialize: function(attrs){

          // Call the super class initialize function
          DataONEObject.prototype.initialize.call(this, attrs);

          // If the image model is initialized with an identifier but no image URL,
          // create the full image URL
          if(this.get("identifier") && !this.get("imageURL")){

            var baseURL = this.getBaseURL(),
                imageURL = baseURL + this.get("identifier");
                this.set("imageURL", imageURL);
          }

        },

        /**
         * Parses an ImageType XML element from a portal document
         *
         *  @param {XMLElement} objectDOM - An ImageType XML element from a portal document
         *  @return {JSON} The result of the parsed XML, in JSON. To be set directly on the model.
        */
        parse: function(objectDOM){

          if(!objectDOM){
            objectDOM = this.get("objectDOM");

            if(!objectDOM){
              return {};
            }
          }

          var portalModel = this.get("portalModel"),
              $objectDOM = $(objectDOM),
              modelJSON = {};

          if( portalModel ){
            modelJSON.datasource = portalModel.get("datasource");
            modelJSON.submitter  = portalModel.get("submitter");
            modelJSON.rightsHolder = portalModel.get("rightsHolder");
            modelJSON.originMemberNode = portalModel.get("originMemberNode");
            modelJSON.authoritativeMemberNode = portalModel.get("authoritativeMemberNode");
          }

          modelJSON.nodeName = objectDOM.nodeName;

          //Parse all the simple string elements
          modelJSON.label = $objectDOM.children("label").text();
          modelJSON.associatedURL = $objectDOM.children("associatedURL").text();

          // Parse the image URL or identifier
          modelJSON.identifier = $objectDOM.children("identifier").text();
          if( modelJSON.identifier ){
            if( modelJSON.identifier.substring(0, 4) !== "http" ){

              var baseURL = this.getBaseURL();
              modelJSON.imageURL = baseURL + modelJSON.identifier;

            }
            else{
              modelJSON.imageURL = modelJSON.identifier;
            }
          }

          return modelJSON;

        },

        /**
         * imageExists - Check if an image exists with the given
         * url, or if no url provided, with the baseURL + identifier
         *
         * @param  {string} imageURL  The image URL to check
         * @return {boolean}          Returns true if an HTTP request returns anything but 404
         */
        imageExists: function (imageURL){

          if(!imageURL){
            this.get("imageURL")
          }

          if(!imageURL && this.get("identifier")){
            var baseURL = this.getBaseURL(),
                imageURL = baseURL + this.get("identifier");
          }

          if(!imageURL){
            return false
          }

          var http = new XMLHttpRequest();
          http.open('HEAD', imageURL, false);
          http.send();

          return http.status != 404;

        },

        /**
         * getBaseURL - Get the base URL to use with an image identifier
         *
         * @return {string}  The image base URL, or an empty string if not found
         */
        getBaseURL: function(){

          var url = "",
              portalModel = this.get("portalModel"),
              // datasource = portalModel ? portalModel.get("datasource") : false;
              datasource = this.get("datasource"),
              datasource = (portalModel && !datasource) ? portalModel.get("datasource") : datasource;

          if( MetacatUI.appModel.get("isCN") ){
            var sourceRepo;

            //Use the object service URL from the origin MN/datasource
            if( datasource ){
              sourceRepo = MetacatUI.nodeModel.getMember(datasource);
            }
            // Use the object service URL from the alt repo
            if( !sourceRepo ){
              sourceRepo = MetacatUI.appModel.getActiveAltRepo();
            }
            if( sourceRepo ){
              url = sourceRepo.objectServiceUrl;
            }
          }

          if(!url && datasource){
            var imageMN = _.findWhere(MetacatUI.appModel.get("alternateRepositories"), { identifier: datasource });
            if(imageMN){
              url = imageMN.objectServiceUrl;
            }
          }

          //If this MetacatUI deployment is pointing to a MN, use the meta service URL from the AppModel
          if( !url ){
            url = MetacatUI.appModel.get("objectServiceUrl") || MetacatUI.appModel.get("resolveServiceUrl");
          }
          return url;
        },

        /**
    		 * Makes a copy of the original XML DOM and updates it with the new values from the model
         *
         *  @return {XMLElement} An updated ImageType XML element from a portal document
    		 */
        updateDOM: function() {

          //If there is no identifier, don't serialize anything
          if( !this.get("identifier") ){
            return "";
          }

          var objectDOM = this.get("objectDOM");

          if (objectDOM) {
            objectDOM = objectDOM.cloneNode(true);
            $(objectDOM).empty();
          } else {
            // create an XML image element from scratch
            var xmlText = "<" + this.get("nodeName") + "></" + this.get("nodeName") + ">",
                objectDOM = new DOMParser().parseFromString(xmlText, "text/xml"),
                objectDOM = $(objectDOM).children()[0];
         }

          // get new image data
          var imageData = {
            identifier: this.get("identifier"),
            label: this.get("label"),
            associatedURL: this.get("associatedURL")
          }

          _.map(imageData, function(value, nodeName){

            // Don't serialize falsey values
            if(value){
              // Make new sub-node
              var imageSubnodeSerialized = objectDOM.ownerDocument.createElement(nodeName);
              $(imageSubnodeSerialized).text(value);
              // Append new sub-node to objectDOM
              $(objectDOM).append(imageSubnodeSerialized);

            }

          });

          return objectDOM;
        },

        /**
         * Overrides the default Backbone.Model.validate.function() to
         * check if this PortalImage model has all the required values necessary
         * to save to the server.
         *
         * @return {Object} If there are errors, an object comprising error
         *                   messages. If no errors, returns nothing.
        */
        validate: function(){
          try {

            var errors          = {},
                requiredFields = MetacatUI.appModel.get("portalEditorRequiredFields"),
                label           = this.get("label"),
                url             = this.get("associatedURL"),
                id              = this.get("identifier"),
                genericLabels   = ["logo", "image"], // not set by the user
                hasLabel        = (label && typeof label == "string" && !genericLabels.includes(label)) ? true : false,
                hasURL          = (url && typeof url == "string") ? true : false,
                hasId           = (id && typeof id == "string") ? true : false,
                urlRegex        = new RegExp(/^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/);

            // If it's a logo, check whether it's a required image
            if(this.get("nodeName") === "logo" && requiredFields.logo && !hasId){
              errors["identifier"] = "An image is required."
              return errors
            }
            // If it's a section image, check whether it's a required image
            else if(this.get("nodeName") === "image" && requiredFields.sectionImage && !hasId){
              errors["identifier"] = "An image is required."
              return errors
            }
            // If none of the fields have values, the portalImage won't be serialized
            else if(!hasId && !hasURL && !hasLabel){
              return
            }

            // If the URL isn't a valid format, add an error message
            if(hasURL && !urlRegex.test(url)){
              errors["associatedURL"] = "Enter a valid URL."
            }
            //If the URL is valid, check if there is an http or https protocol
            else if(hasURL && url.substring(0,4) != "http"){
              //If not, add the https protocol
              this.set("associatedURL", "https://" + url);
            }

            return errors;

          }
          catch(e){
            console.error("Error validating a portal image. Error message:" + e);
            return;
          }
        },


        /**
         * isEmpty - Returns true if the PortalImage model has no label, no associatedURL, and no identifier
         *
         * @return {boolean}  true if the model is empty, false if it has at least a label, url, or id
         */
        isEmpty: function(){
          return (
            !this.get("label")          &&
            !this.get("associatedURL")  &&
            !this.get("identifier")
          ) ;
        },

        /**
        * Returns true if this PortalImage hasn't been saved to a Portal yet, so it is a new object.
        * For now, all PortalImages will be considered new objects since we will not be performing updates on them.
        * @return {boolean}
        */
        isNew: function(){
          if( this.get("identifier") ){
            return false;
          }
          else{
            return true;
          }
        }

      });

      return PortalImageModel;
});