Source: src/js/views/portals/editor/PortEditorImageView.js

define(['underscore',
        'jquery',
        'backbone',
        "models/portals/PortalImage",
        "views/ImageUploaderView",
        "text!templates/imageEdit.html"],
function(_, $, Backbone, PortalImage, ImageUploaderView, Template){

  /**
  * @class PortEditorImageView
  * @classdesc A view that allows the user to upload an image as a DataONEObject
  * @classcategory Views/Portals/Editor
  */
  var PortEditorImageView = Backbone.View.extend(
      /** @lends PortEditorImageView.prototype */{

    /**
    * The type of View this is
    * @type {string}
    */
    type: "PortEditorImage",

    /**
    * The HTML tag name to use for this view's element
    * @type {string}
    */
    tagName: "div",

    /**
    * The HTML classes to use for this view's element
    * @type {string}
    */
    className: "edit-image",

    /**
    * A jQuery selector for the element that the ImageUploaderView should be inserted into
    * @type {string}
    */
    imageUploaderContainer: ".image-uploader-container",

    /**
     * The ImageUploaderView created and used by this ImageEdit view.
     * @type {ImageUploader}
     */
    uploader: undefined,

    /**
    * The PortalImage model that is being edited
    * @type {Image}
    */
    model: undefined,

    /**
    * The Portal model that contains the PortalImage
    * @type {Portal}
    */
    parentModel: undefined,

    /**
    * A reference to the PortalEditorView
    * @type {PortalEditorView}
    */
    editorView: undefined,

    /**
    * The maximum height of the image preview. If set to false,
    * no css width property is set.
    * @type {number}
    */
    imageHeight: 150,

    /**
    * The display width of the image preview. If set to false,
    * no css width property is set.
    * @type {number|boolean}
    */
    imageWidth: 150,

    /**
     * The minimum required height of the image file. If set, the uploader will
     * reject images that are shorter than this. If null, any image height is
     * accepted.
     * @type {number}
     */
    minHeight: null,

    /**
     * The minimum required height of the image file. If set, the uploader will
     * reject images that are shorter than this. If null, any image height is
     * accepted.
     * @type {number}
     */
    minWidth: null,

    /**
     * The maximum height for uploaded files. If a file is taller than this, it
     * will be resized without warning before being uploaded. If set to null,
     * the image won't be resized based on height (but might be depending on
     * maxWidth).
     * @type {number}
     */
    maxHeight: null,

    /**
     * The maximum width for uploaded files. If a file is wider than this, it
     * will be resized without warning before being uploaded. If set to null,
     * the image won't be resized based on width (but might be depending on
     * maxHeight).
     * @type {number}
     */
    maxWidth: null,


    /**
    * Text to instruct the user how to upload an image
    * @type {string[]}
    */
    imageUploadInstructions: ["Drag & drop an image or click here to upload"],

    /**
     * Label for the first text input where the user enters the ImageModel label.
     * If this is set to false, the label input will not be shown.
     * @type {string|boolean}
     */
    nameLabel: "Name",

    /**
     * Label for the second text input where the user enters the ImageModel
     * associated URL. If this is set to false, the URL input will not be shown.
     * @type {string|boolean}
     */
    urlLabel: "URL",

    /**
     * The HTML tag name to insert the uploaded image into. Options are "img",
     * in which case the image is inserted as an HTML <img>, or "div", in which
     * case the image is inserted as the background of a "div".
     * @type {string}
     */
    imageTagName: "div",

    /**
     * Whether or not a remove button should be shown.
     * @type {boolean}
     */
    removeButton: false,

    /**
    * References to templates for this view. HTML files are converted to Underscore.js templates
    */
    template: _.template(Template),

    /**
    * The events this view will listen to and the associated function to call.
    * @type {Object}
    */
    events: {
      "mouseover .toggle-remove-preview"    : "showRemovePreview",
      "mouseout  .toggle-remove-preview"    : "hideRemovePreview",
      "click .remove-image-edit-view"       : "removeSelf",
      "focusout .basic-text"                : "redoValidation"
    },

    /**
    * Creates a new PortEditorImageView
    * @param {Object} options - A literal object with options to pass to the view
    * @property {Portal}  options.parentModel - Gets set as PortEditorImageView.parentModel
    * @property {PortalEditorView}  options.editorView - Gets set as PortEditorImageView.editorView
    * @property {PortalImage}  options.model - Gets set as PortEditorImageView.model
    * @property {string[]}  options.imageUploadInstructions - Gets set as ImageUploaderView.imageUploadInstructions
    * @property {string}  options.nameLabel - Gets set as PortEditorImageView.nameLabel
    * @property {string}  options.urlLabel - Gets set as PortEditorImageView.urlLabel
    * @property {string}  options.imageTagName - Gets set as ImageUploaderView.imageTagName
    * @property {string}  options.removeButton - Gets set as ImageUploaderView.removeButton
    * @property {number}  options.imageWidth - Gets set as ImageUploaderView.width
    * @property {number}  options.imageHeight - Gets set as ImageUploaderView.height
    * @property {number}  options.minWidth - Gets set as ImageUploaderView.minWidth
    * @property {number}  options.minHeight - Gets set as ImageUploaderView.minHeight
    * @property {number}  options.maxWidth - Gets set as ImageUploaderView.maxWidth
    * @property {number}  options.maxHeight - Gets set as ImageUploaderView.maxHeight
    */
    initialize: function(options){

      try {

        if( typeof options == "object" ){
          this.parentModel              = options.parentModel;
          this.editorView               = options.editorView;
          this.model                    = options.model;
          this.imageUploadInstructions  = options.imageUploadInstructions;
          this.imageWidth               = options.imageWidth;
          this.imageHeight              = options.imageHeight;
          this.nameLabel                = options.nameLabel;
          this.urlLabel                 = options.urlLabel;
          this.imageTagName             = options.imageTagName;
          this.removeButton             = options.removeButton;
          this.minHeight                = options.minHeight;
          this.minWidth                 = options.minWidth;
          this.maxHeight                = options.maxHeight;
          this.maxWidth                 = options.maxWidth;
        }

        if(!this.model){

          this.model = new PortalImage();
        }

        //If an alternative repo is configured, upload to that alt repo
        let useAltRepo = MetacatUI.appModel.getActiveAltRepo()? true : false;
        this.model.set("useAltRepo", useAltRepo);

      } catch (e) {
        console.log("PortEditorImageView failed to initialize. Error message: " + e);
      }

    },

    /**
    * Renders this view
    */
    render: function(){

      try {
        // Reference to this view
        var view = this;

        //Insert the template for this view
        this.$el.html(this.template({
          nameLabel:    this.nameLabel,
          urlLabel:     this.urlLabel,
          nameText:     this.model.get("label"),
          urlText:      this.model.get("associatedURL"),
          removeButton: this.removeButton
        }));

        // Create an ImageUploaderView and insert into this view. Allow it to be
        // accessed from parent views.
        this.uploader = new ImageUploaderView({
          model:              this.model,
          url:                this.model.get("imageURL"),
          uploadInstructions: this.imageUploadInstructions,
          imageTagName:       this.imageTagName,
          height:             this.imageHeight,
          width:              this.imageWidth,
          minHeight:          this.minHeight,
          minWidth:           this.minWidth,
          maxHeight:          this.maxHeight,
          maxWidth:           this.maxWidth
        });
        this.$(this.imageUploaderContainer).append(this.uploader.el);
        this.uploader.render();

        // Reset image attributes when user removes image
        this.stopListening(this.uploader, "removedfile");
        this.listenTo(this.uploader, "removedfile", function(){
          var defaults = view.model.defaults();
          view.model.set("identifier", defaults.identifier);
          view.model.set("imageURL", defaults.imageURL);
          view.redoValidation();
        });

        // Try to validate again when image is added but not yet uploaded
        this.stopListening(this.uploader, "addedfile");
        this.listenTo(this.uploader, "addedfile", function(){
          view.redoValidation();
        });

        // Update the PortalImage model when the image is successfully uploaded
        this.stopListening(this.uploader, "successSaving");
        this.listenTo(this.uploader, "successSaving", function(dataONEObject){
          view.model.set("identifier", dataONEObject.get("id"));
          view.model.set("imageURL", dataONEObject.url());
          view.redoValidation();
        });

        this.listenTo(this.model, "change:associatedURL", this.showValidation);

        // Allows model to update when user types in text field
        this.$el.find(".basic-text").data({ model: this.model, view: this });

        //Initialize any tooltips
        this.$(".tooltip-this").tooltip();

        //Save a reference to this view
        this.$el.data("view", this);

      } catch (e) {
        console.log("ImageEdit view not rendered, error message: " + e);
      }

    },

    /**
     * removeSelf - Removes this ImageEdit view and the associated PortalImage
     * model from the parent Portal model.
     */
    removeSelf: function(){

      try {

        var view = this;

        // Remove the model
        this.parentModel.removePortalImage(this.model);
        // Remove the view
        this.$el.animate({width: "0px", overflow: "hidden"}, {
          duration: 250,
          complete: function(){
            view.onClose();
            view.remove();
          }
        });

      } catch (e) {
        console.log("Failed to remove an ImageEdit view. Error message: " + e);
      }

    },

    /**
     * redoValidation - Called when a user focuses out of input fields
     * with the .basic-text class (organization name and associated URL), or
     * when an image is successfully uploaded or removed. This function
     * validates the PortalImage model again and shows errors if there are any.
     */
    redoValidation: function(){
      try {
        view = this;
        // Add a small pause so that the model is updated first.
        setTimeout(function () {
          view.removeValidation();
          view.showValidation();
        }, 1);
      } catch (e) {
        console.log(e);
      }
    },

    /**
     * showValidation - show validation errors for this ImageEdit view
     */
    showValidation: function(){

      try {

        var errors = this.model.validate();

        if(errors){

          _.each(errors, function(errorMsg, category){
            var categoryEls = this.$("[data-category='" + category + "']");
            //Use the showValidationMessage function from the parent view
            if( this.editorView && this.editorView.showValidationMessage ){
              this.editorView.showValidationMessage(categoryEls, errorMsg);
            }

          }, this);

          // add class to dropzone element if error has to do with image
          if(errors.identifier){
            this.$el.find(".dropzone").addClass("error");
          }

        }

      } catch (e) {
        console.log("Failed to validate portalImage, error: " + e);
      }

    },

    /**
     * removeValidation - Remove displayed validation errors, if any
     */
    removeValidation: function(){
      this.$(".notification.error").removeClass("error").empty();
      this.$(".section-link-container.error, input.error, textarea.error").removeClass("error");
      this.$(".validation-error-icon").hide();
      this.$el.find(".dropzone").removeClass("error");
    },

    /**
    * Add the "remove-preview" class which will show a preview for removing this image, via CSS
    */
    showRemovePreview: function(){
      try{
        this.$el.addClass("remove-preview");
      }
      catch (error) {
        console.error("Failed to preview the removal of an image edit view. Error message: " + error);
      }
    },

    /**
    * Removes the "remove-preview" class which will hide the preview for removing this image, via CSS
    */
    hideRemovePreview: function(e){
      try{
        this.$el.removeClass("remove-preview");
      }
      catch (error) {
        console.error("Failed to preview the removal of an image edit view. Error message: " + error);
      }
   },

   /**
   * This function is called whenever this view is about to be removed from the page.
   */
   onClose: function(){
     //Destroy any tooltips in this view that are still open
     this.$(".tooltip-this").tooltip("destroy");
   }


  });

  return PortEditorImageView;

});