# Source: js/views/metadata/EML211EditorView.js

    ﻿/* global define */
    define(['underscore',
            'jquery',
            'backbone',
            'localforage',
            'collections/DataPackage',
            'models/metadata/eml211/EML211',
            'models/metadata/eml211/EMLOtherEntity',
            'models/metadata/ScienceMetadata',
            'views/EditorView',
            'views/CitationView',
            'views/DataPackageView',
            'views/metadata/EML211View',
            'views/metadata/EMLEntityView',
            'views/SignInView',
            'text!templates/editor.html',
            'collections/ObjectFormats',
            'text!templates/editorSubmitMessage.html'],
            function(_, $, Backbone, LocalForage,
                DataPackage, EML, EMLOtherEntity, ScienceMetadata,
                EditorView, CitationView, DataPackageView, EMLView, EMLEntityView, SignInView,
                EditorTemplate, ObjectFormats, EditorSubmitMessageTemplate){

        /**
        * @class EML211EditorView
        * @classdesc A view of a form for creating and editing EML 2.1.1 documents
        * @name EML211EditorView
        * @extends EditorView
        * @constructs
        */
        var EML211EditorView = EditorView.extend(
          /** @lends EML211EditorView.prototype */{

            type: "EML211Editor",

            /* The initial editor layout */
            template: _.template(EditorTemplate),
            editorSubmitMessageTemplate: _.template(EditorSubmitMessageTemplate),

            /**
            * The text to use in the editor submit button
            * @type {string}
            */
            submitButtonText: "Submit Dataset",

            /**
            * The events this view will listen to and the associated function to call.
            * This view will inherit events from the parent class, EditorView.
            * @type {Object}
            */
            events:  _.extend(EditorView.prototype.events, {
              "change" : "saveDraft",
              "click .data-package-item .edit" : "showEntity"
            }),

            /**
            The identifier of the root package EML being rendered
            * @type {string}
            */
            pid: null,

            /**
            * A list of the subviews of the editor
            * @type {Backbone.Views[]}
            */
            subviews: [],

            /**
            * The data package view
            * @type {DataPackageView}
            */
            dataPackageView: null,

            /**
            * Initialize a new EML211EditorView - called post constructor
            */
            initialize: function(options) {

                // Ensure the object formats are cached for the editor's use
                if ( typeof MetacatUI.objectFormats === "undefined" ) {
                    MetacatUI.objectFormats = new ObjectFormats();
                    MetacatUI.objectFormats.fetch();

                }
                return this;
            },

            //Create a new EML model for this view
            createModel: function(){
              //If no pid is given, create a new EML model
              if(!this.pid)
                var model = new EML({'synced' : true});
              //Otherwise create a generic metadata model until we find out the formatId
              else
                var model = new ScienceMetadata({ id: this.pid });

                // Once the ScienceMetadata is populated, populate the associated package
                this.model = model;

                //Listen for the replace event on this model
                var view = this;
                this.listenTo(this.model, "replace", function(newModel){
                  if(view.model.get("id") == newModel.get("id")){
                    view.model = newModel;
                    view.setListeners();
                  }
                });

                this.setListeners();
            },

            /* Render the view */
            render: function() {

              //Execute the superclass render() function, which will add some basic Editor functionality
              EditorView.prototype.render.call(this);

              MetacatUI.appModel.set('headerType', 'default');

              //Empty the view element first
              this.$el.empty();

              //Inert the basic template on the page
              this.$el.html(this.template({
                loading: MetacatUI.appView.loadingTemplate({ msg: "Loading editor..."}),
                submitButtonText: this.submitButtonText
              }));

              //If we don't have a model at this point, create one
              if(!this.model) this.createModel();

              //When the basic Solr metadata are retrieved, get the associated package
              this.listenToOnce(this.model, "sync", this.getDataPackage);

              //If no object is found with this ID, then tell the user
              this.listenToOnce(this.model, "change:notFound", this.showNotFound);

              //If we checked for authentication already
              if(MetacatUI.appUserModel.get("checked")){
                this.fetchModel();
              }
              //If we haven't checked for authentication yet,
              //wait until the user info is loaded before we request the Metadata
              else{
                this.listenToOnce(MetacatUI.appUserModel, "change:checked", this.fetchModel);
              }

              // When the user mistakenly drops a file into an area in the window
              // that isn't a proper drop-target, prevent navigating away from the
              // page. Without this, the user will lose their progress in the
              // editor.
              window.addEventListener("dragover", function(e) {
                e = e || event;
                e.preventDefault();
              }, false);

              window.addEventListener("drop", function(e) {
                e = e || event;
                e.preventDefault();
              }, false);

                return this;
            },

            fetchModel: function(){
              //If we checked for authentication and the user is not logged in
              if(!MetacatUI.appUserModel.get("loggedIn")){
                this.showSignIn();
              }
              else{

                //If the user hasn't provided an id, then don't check the authority and mark as synced already
                if(!this.pid){
                  this.model.set("isAuthorized", true);
                  this.model.trigger("sync");
                }
                else {
                  //Get the data package when we find out the user is authorized to edit it
                  this.listenToOnce(this.model, "change:isAuthorized", this.getDataPackage);
                  //Let a user know when they are not authorized to edit this data set
                  this.listenToOnce(this.model, "change:isAuthorized", this.notAuthorized);

                  //Fetch the model
                  this.model.fetch();

                  //Check the authority of this user
                  this.model.checkAuthority();
                }

              }

            },

            /* Get the data package associated with the EML */
            getDataPackage: function(scimetaModel) {

                if(!this.model.get("synced") || !this.model.get("isAuthorized")) return;

                if(!scimetaModel)
                  var scimetaModel = this.model;

                //Check if this package is obsoleted
                if(this.model.get("obsoletedBy")){
                  this.showLatestVersion();
                  return;
                }

                var resourceMapIds = scimetaModel.get("resourceMap");

                if ( typeof resourceMapIds === "undefined" || resourceMapIds === null || resourceMapIds.length <= 0 ) {
                    console.log("Resource map ids could not be found for " + scimetaModel.id);

                    //Check if the rootDataPackage contains the metadata document the user is trying to edit
                    if( MetacatUI.rootDataPackage && MetacatUI.rootDataPackage.pluck &&
                      _.contains(MetacatUI.rootDataPackage.pluck("id"), this.model.get("id")) ){

                        //Make sure we have the latest version of the resource map before we allow editing
                        this.listenToOnce(MetacatUI.rootDataPackage.packageModel, "latestVersionFound", function(model) {
                          //Create a new data package for the latest version package
                          MetacatUI.rootDataPackage = new DataPackage([this.model], { id: model.get("latestVersion") });

                          //Handle the add of the metadata model
                          MetacatUI.rootDataPackage.saveReference(this.model);

                           //Fetch the data package
                           MetacatUI.rootDataPackage.fetch();

                           //Render the Data Package table
                           this.renderDataPackage();
                       });

                       //Remove the cached system metadata XML so we retrieve it again
                       MetacatUI.rootDataPackage.packageModel.set("sysMetaXML", null);
                       //Find the latest version of the resource map
                       MetacatUI.rootDataPackage.packageModel.findLatestVersion();

                    }
                    else{
                      //Create a new DataPackage collection for this view
                      this.createDataPackage();

                      // Set the listeners
                      this.setListeners();

                      //Render the data package
                      this.renderDataPackage();

                      //Render the metadata
                      this.renderMetadata();
                    }

                } else {
                    // Create a new data package with this id
                    MetacatUI.rootDataPackage = new DataPackage([this.model], {id: resourceMapIds[0]});

                    //Handle the add of the metadata model
                    MetacatUI.rootDataPackage.saveReference(this.model);

                    // If there is more than one resource map, we need to make sure we fetch the most recent one
                    if ( resourceMapIds.length > 1 ) {

                    //Now, find the latest version
                    this.listenToOnce(MetacatUI.rootDataPackage.packageModel, "change:latestVersion", function(model) {
                            //Create a new data package for the latest version package
                            MetacatUI.rootDataPackage = new DataPackage([this.model], { id: model.get("latestVersion") });

                            //Handle the add of the metadata model
                            MetacatUI.rootDataPackage.saveReference(this.model);

                             //Fetch the data package
                             MetacatUI.rootDataPackage.fetch();

                             //Render the Data Package table
                             this.renderDataPackage();
                         });

                         MetacatUI.rootDataPackage.packageModel.findLatestVersion();

                        return;

                    }

                    //Fetch the data package
                    MetacatUI.rootDataPackage.fetch();

                    //Render the Data Package table
                    this.renderDataPackage();
                }

            },

            /*
            * Creates a DataPackage collection for this EML211EditorView and sets it on the MetacatUI
            * global object (as `rootDataPackage`)
            */
            createDataPackage: function(){
              // Create a new Data packages
              MetacatUI.rootDataPackage = new DataPackage([this.model]);
              MetacatUI.rootDataPackage.packageModel.set("synced", true);

              //Handle the add of the metadata model
              MetacatUI.rootDataPackage.handleAdd(this.model);

              // Associate the science metadata with the resource map
              if ( this.model.get && Array.isArray(this.model.get("resourceMap")) ) {
                  this.model.get("resourceMap").push(MetacatUI.rootDataPackage.packageModel.id);

              } else {
                  this.model.set("resourceMap", MetacatUI.rootDataPackage.packageModel.id);

              }

              // Set the sysMetaXML for the packageModel
              MetacatUI.rootDataPackage.packageModel.set("sysMetaXML",
                  MetacatUI.rootDataPackage.packageModel.serializeSysMeta());
            },

            renderChildren: function(model, options) {

            },

            renderDataPackage: function(){
              var view = this;

              // As the root collection is updated with models, render the UI
                this.listenTo(MetacatUI.rootDataPackage, "add", function(model){

                  if(!model.get("synced") && model.get('id'))
                    this.listenTo(model, "sync", view.renderMember);
                  else if(model.get("synced"))
                    view.renderMember(model);

                  //Listen for changes on this member
                    model.on("change:fileName", model.addToUploadQueue);
                });

              //Render the Data Package view
                this.dataPackageView = new DataPackageView({
                  edit: true,
                  dataPackage: MetacatUI.rootDataPackage
                  });

                //Render the view
                var $packageTableContainer = this.$("#data-package-container");
                $packageTableContainer.html(this.dataPackageView.render().el);

                //Make the view resizable on the bottom
                var handle = $(document.createElement("div"))
                        .addClass("ui-resizable-handle ui-resizable-s")
                  .attr("title", "Drag to resize")
                  .append($(document.createElement("i")).addClass("icon icon-caret-down"));
                $packageTableContainer.after(handle);
                $packageTableContainer.resizable({
                    handles: { "s" : handle },
                    minHeight: 100,
                    maxHeight: 900,
                    resize: function(){
                      view.emlView.resizeTOC();
                    }
                  });

                var tableHeight = ($(window).height() - $("#Navbar").height()) * .40;
                $packageTableContainer.css("height", tableHeight + "px");

                var table = this.dataPackageView.$el;
                this.listenTo(this.dataPackageView, "addOne", function(){
                  if(table.outerHeight() > $packageTableContainer.outerHeight() && table.outerHeight() < 220){
                        $packageTableContainer.css("height", table.outerHeight() + handle.outerHeight());
                        if(this.emlView)
                          this.emlView.resizeTOC();
                  }
                });

                if(this.emlView)
                  this.emlView.resizeTOC();

                //Save the view as a subview
                this.subviews.push(this.dataPackageView);

                this.listenTo(MetacatUI.rootDataPackage.packageModel, "change:childPackages", this.renderChildren);
            },

            /* Calls the appropriate render method depending on the model type */
            renderMember: function(model, collection, options) {

                // Render metadata or package information, based on the type
                if ( typeof model.attributes === "undefined") {
                    return;

                } else {
                    switch ( model.get("type")) {
                        case "DataPackage":
                            // Do recursive rendering here for sub packages
                            break;

                        case "Metadata":

                            // this.renderDataPackageItem(model, collection, options);
                            this.renderMetadata(model, collection, options);
                            break;

                        case "Data":
                            //this.renderDataPackageItem(model, collection, options);
                            break;

                        default:
                            console.log("model.type is not set correctly");

                    }
                }
            },

            /* Renders the metadata section of the EML211EditorView */
            renderMetadata: function(model, collection, options){

              if(!model && this.model) var model = this.model;
              if(!model) return;

                var emlView, dataPackageView;

                // render metadata as the collection is updated, but only EML passed from the event
                if ( typeof model.get === "undefined" ||
                            model.get("formatId") !== "eml://ecoinformatics.org/eml-2.1.1" ) {
                    console.log("Not EML. TODO: Render generic ScienceMetadata.");
                    return;

                } else {

                  //Create an EML model
                    if(model.type != "EML"){
                      //Create a new EML model from the ScienceMetadata model
                      var EMLmodel = new EML(model.toJSON());
                      //Replace the old ScienceMetadata model in the collection
                      MetacatUI.rootDataPackage.remove(model);
                      MetacatUI.rootDataPackage.add(EMLmodel, { silent: true });
                      MetacatUI.rootDataPackage.handleAdd(EMLmodel);
                      model.trigger("replace", EMLmodel);

                      //Fetch the EML and render it
                      this.listenToOnce(EMLmodel, "sync", this.renderMetadata);
                      EMLmodel.fetch();

                      return;
                    }

                  //Create an EML211 View and render it
                  emlView = new EMLView({
                    model: model,
                    edit: true
                    });
                  this.subviews.push(emlView);
                  this.emlView = emlView;
                  emlView.render();

                  //Show the required fields for this editor
                  this.renderRequiredIcons(MetacatUI.appModel.get("emlEditorRequiredFields"));

                  // Create a citation view and render it
                  var citationView = new CitationView({
                              model: model,
                              title: "Untitled dataset"
                            });

                  if( model.isNew() ){
                    citationView.createLink = false;
                    citationView.createTitleLink = false;
                  }
                  else{
                    citationView.createLink = false;
                    citationView.createTitleLink = true;
                  }

                  this.subviews.push(citationView);
                  $("#citation-container").html(citationView.render().$el);

                  //Remove the rendering class from the body element
                  $("body").removeClass("rendering");
                }

                // Focus the folder name field once loaded but only if this is a new
                // document
                if (!this.pid) {
                    $("#data-package-table-body td.name").focus();
                }

            },

            /* Renders the data package section of the EML211EditorView */
            renderDataPackageItem: function(model, collection, options) {

                var hasPackageSubView =
                    _.find(this.subviews, function(subview) {
                        return subview.id === "data-package-table";
                    }, model);

                // Only create the package table if it hasn't been created
                if ( ! hasPackageSubView ) {
                    this.dataPackageView = new DataPackageView({
                        dataPackage: MetacatUI.rootDataPackage,
                        edit: true
                        });
                    this.subviews.push(this.dataPackageView);
                    dataPackageView.render();

                }
            },

            /*
             * Set listeners on the view's model for various reasons.
             * This function centralizes all the listeners so that when/if the view's model is replaced, the listeners would be reset.
             */
            setListeners: function() {

                this.listenTo(this.model, "change:uploadStatus", this.showControls);

                // Register a listener for any attribute change
                this.model.on("change", this.model.handleChange, this.model);

                // Register a listener to save drafts on change
                this.model.on("change", this.model.saveDraft, this.model);

                // If any attributes have changed (including nested objects), show the controls
                if ( typeof MetacatUI.rootDataPackage.packageModel !== "undefined" ) {
                    this.stopListening(MetacatUI.rootDataPackage.packageModel, "change:changed");
                    this.listenTo(MetacatUI.rootDataPackage.packageModel, "change:changed", this.toggleControls);
                    this.listenTo(MetacatUI.rootDataPackage.packageModel, "change:changed", function(event) {
                        if (MetacatUI.rootDataPackage.packageModel.get("changed") ) {
                            // Put this metadata model in the queue when the package has been changed
                            // Don't put it in the queue if it's in the process of saving already
                            if( this.model.get("uploadStatus") != "p" )
                              this.model.set("uploadStatus", "q");
                        }
                    });

                }

              if( MetacatUI.rootDataPackage && DataPackage.prototype.isPrototypeOf(MetacatUI.rootDataPackage) ){
                // If the Data Package failed saving, display an error message
                this.listenTo(MetacatUI.rootDataPackage, "errorSaving", this.saveError);

                // Listen for when the package has been successfully saved
                this.listenTo(MetacatUI.rootDataPackage, "successSaving", this.saveSuccess);

                //When the Data Package cancels saving, hide the saving styling
                this.listenTo(MetacatUI.rootDataPackage, "cancelSave", this.hideSaving);
                this.listenTo(MetacatUI.rootDataPackage, "cancelSave", this.handleSaveCancel);
              }

              //When the model is invalid, show the required fields
              this.listenTo(this.model, "invalid", this.showValidation);
              this.listenTo(this.model, "valid",   this.showValidation);

              // When a data package member fails to load, remove it and warn the user
              this.listenTo(MetacatUI.eventDispatcher, "fileLoadError", this.handleFileLoadError);

              // When a data package member fails to be read, remove it and warn the user
              this.listenTo(MetacatUI.eventDispatcher, "fileReadError", this.handleFileReadError);

              //Set a beforeunload event only if there isn't one already
              if( !this.beforeunloadCallback ){
                var view = this;
                //When the Window is about to be closed, show a confirmation message
                this.beforeunloadCallback = function(e){
                  if( !view.canClose() ){
                    //Browsers don't support custom confirmation messages anymore,
                    // so preventDefault() needs to be called or the return value has to be set
                    e.preventDefault();
                    e.returnValue = "";
                  }
                  return;
                }
                window.addEventListener("beforeunload", this.beforeunloadCallback);
              }

            },

            /**
             * Saves all edits in the collection
             */
            save: function(e){
              var btn = (e && e.target)? $(e.target) : this.$("#save-editor");

              //If the save button is disabled, then we don't want to save right now
              if(btn.is(".btn-disabled")) return;

               this.showSaving();

              //Save the package!
              MetacatUI.rootDataPackage.save();
            },

            /*
             * When the data package collection saves successfully, tell the user
             */
            saveSuccess: function(savedObject){

              //We only want to perform these actions after the package saves
              if(savedObject.type != "DataPackage") return;

              //Change the URL to the new id
              MetacatUI.uiRouter.navigate("submit/" + encodeURIComponent(this.model.get("id")), { trigger: false, replace: true });

              this.toggleControls();

              // Construct the save message
              var message = this.editorSubmitMessageTemplate({
                messageText: "Your changes have been submitted.",
                viewURL: MetacatUI.root + "/view/" + encodeURIComponent(this.model.get("id")),
                buttonText: "View your dataset"
              });

              MetacatUI.appView.showAlert(message, "alert-success", this.$el, null, {remove: true});

              //Rerender the CitationView
              var citationView = _.where(this.subviews, { type: "Citation" });
              if(citationView.length){
                citationView[0].createTitleLink = true;
                citationView[0].render();
              }

              // Reset the state to clean
              MetacatUI.rootDataPackage.packageModel.set("changed", false);
              this.model.set("hasContentChanges", false);

              this.setListeners();
            },

            /*
             * When the data package collection fails to save, tell the user
             */
            saveError: function(errorMsg){

              var errorId = "error" + Math.round(Math.random()*100),
                  messageContainer = $(document.createElement("div")).append(document.createElement("p")),
                  messageParagraph = messageContainer.find("p"),
                  messageClasses = "alert-error";

              //Get all the models that have an error
              var failedModels = MetacatUI.rootDataPackage.where({ uploadStatus: "e" });

              //If every failed model is a DataONEObject data file that failed
              // because of a slow network, construct a specific error message that
              // is more informative than the usual message
              if( failedModels.length &&
                   _.every(failedModels, function(m){ return m.get("type") == "Data" &&
                                                             m.get("errorMessage").indexOf("network issue") > -1 }) ){

                //Create a list of file names for the files that failed to upload
                var failedFileList = $(document.createElement("ul"));

                _.each(failedModels, function(failedModel){

                  failedFileList.append( $(document.createElement("li")).text( failedModel.get("fileName") ) );

                }, this);

                //Make the error message
                messageParagraph.text("The following files could not be uploaded due to a network issue. Make sure you are connected to a reliable internet connection. ");
                messageParagraph.after(failedFileList);
              }
              //If one of the failed models is this package's metadata model or the
              // resource map model and it failed to upload due to a network issue,
              // show a more specific error message
              else if( _.find(failedModels, function(m){
                        var errorMsg = m.get("errorMessage") || "";
                        return (m == this.model && errorMsg.indexOf("network issue") > -1)
                      }, this) ||
                      ( MetacatUI.rootDataPackage.packageModel.get("uploadStatus") == "e" &&
                        MetacatUI.rootDataPackage.packageModel.get("errorMessage").indexOf("network issue") > -1) ){

                messageParagraph.text("Your changes could not be submitted due to a network issue. Make sure you are connected to a reliable internet connection. ");

              }
              else{

                if( this.model.get("draftSaved") && MetacatUI.appModel.get("editorSaveErrorMsgWithDraft") ){
                  messageParagraph.text( MetacatUI.appModel.get("editorSaveErrorMsgWithDraft") );
                  messageClasses = "alert-warning"
                }
                else if( MetacatUI.appModel.get("editorSaveErrorMsg") ){
                  messageParagraph.text( MetacatUI.appModel.get("editorSaveErrorMsg") );
                  messageClasses = "alert-error";
                }
                else{
                  messageParagraph.text("Not all of your changes could be submitted.");
                  messageClasses = "alert-error";
                }

                messageParagraph.after($(document.createElement("p")).append($(document.createElement("a"))
                          .text("See technical details")
                          .attr("data-toggle", "collapse")
                          .attr("data-target", "#" + errorId)
                          .addClass("pointer")),
                        $(document.createElement("div"))
                          .addClass("collapse")
                          .attr("id", errorId)
                          .append($(document.createElement("pre")).text(errorMsg)));
              }

              MetacatUI.appView.showAlert(messageContainer, messageClasses, this.$el, null, {
                emailBody: "Error message: Data Package save error: " + errorMsg,
                remove: true
                });

              //Reset the Saving styling
              this.hideSaving();
            },

            showLatestVersion: function(){
              var view = this;

              //When the latest version is found,
              this.listenToOnce(this.model, "change:latestVersion", function(){
                //Make sure it has a newer version, and if so,
                if(view.model.get("latestVersion") != view.model.get("id")){
                  //Get the obsoleted id
                  var oldID = view.model.get("id");

                  //Reset the current model
                  view.pid = view.model.get("latestVersion");
                  view.model = null;

                  //Update the URL
                  MetacatUI.uiRouter.navigate("submit/" + encodeURIComponent(view.pid), { trigger: false, replace: true });

                  //Render the new model
                  view.render();

                  //Show a warning that the user was trying to edit old content
                  MetacatUI.appView.showAlert("You've been forwarded to the newest version of your dataset for editing.",
                      "alert-warning", this.$el, 12000, { remove: true });
                }
                else
                  view.getDataPackage();
              });

              //Find the latest version of this metadata object
              this.model.findLatestVersion();
            },

            /*
             * Show the entity editor
             */
            showEntity: function(e){
                  if(!e || !e.target)
                    return;

                  //For EML metadata docs
                  if(this.model.type == "EML"){
                    //Get the Entity View
                    var row = $(e.target).parents(".data-package-item"),
                      entityView = row.data("entityView"),
                      dataONEObject = row.data("model");

                    if(dataONEObject.get("uploadStatus") == "p" || dataONEObject.get("uploadStatus") == "l" || dataONEObject.get("uploadStatus") == "e")
                      return;

                    //If there isn't a view yet, create one
                    if(!entityView){

                      //Get the entity model for this data package item
                      var entityModel = this.model.getEntity(row.data("model"));

                      //Create a new EMLOtherEntity if it doesn't exist
                      if(!entityModel){
                        entityModel = new EMLOtherEntity({
                          entityName : dataONEObject.get("fileName"),
                          entityType : dataONEObject.get("formatId") || dataONEObject.get("mediaType"),
                          parentModel: this.model,
                          xmlID: dataONEObject.getXMLSafeID()
                        });

                        if(!dataONEObject.get("fileName")){
                          //Listen to changes to required fields on the otherEntity models
                          this.listenTo(entityModel, "change:entityName", function(){
                            if(!entityModel.isValid()) return;

                            //Get the position this entity will be in
                            var position = $(".data-package-item.data").index(row);

                            this.model.addEntity(entityModel, position);
                          });
                        }
                        else{
                          //Get the position this entity will be in
                          var position = $(".data-package-item.data").index(row);

                          this.model.addEntity(entityModel, position);
                        }
                      }

                      //Create a new view for the entity based on the model type
                      if(entityModel.type == "EMLOtherEntity"){
                        entityView = new EMLEntityView({
                          model: entityModel,
                          DataONEObject: dataONEObject,
                          edit: true
                        });
                      }
                      else{
                        entityView = new EMLEntityView({
                          model: entityModel,
                          DataONEObject: dataONEObject,
                          edit: true
                        });
                      }

                      //Attach the view to the edit button so we can access it again
                      row.data("entityView", entityView);

                      //Render the view
                      entityView.render();
                    }

                    //Show the modal window editor for this entity
                    if(entityView)
                      entityView.show();
                  }

            },

            /*
             * Shows a message if the user is not authorized to edit this package
             */
            notAuthorized: function(){
              if(this.model.get("isAuthorized") || this.model.get("notFound")) return;

              this.$("#editor-body").empty();
              MetacatUI.appView.showAlert("You are not authorized to edit this data set.",
                  "alert-error", "#editor-body");

              //Stop listening to any further events
              this.stopListening();
              this.model.off();
            },

            /* Toggle the editor footer controls (Save bar) */
            toggleControls: function() {
                if ( MetacatUI.rootDataPackage &&
                     MetacatUI.rootDataPackage.packageModel &&
                     MetacatUI.rootDataPackage.packageModel.get("changed") ) {
                    this.showControls();

                } else {
                    this.hideControls();

                }
            },

            showValidation: function(){

              //First clear all the error messaging
              this.$(".notification.error").empty();
              this.$(".side-nav-item .icon").hide();
              this.$("#metadata-container .error").removeClass("error");
              $(".alert-container").remove();

              var errors = this.model.validationError;

              _.each(errors, function(errorMsg, category){

                var categoryEls = this.$("[data-category='" + category + "']"),
                    dataItemRow = categoryEls.parents(".data-package-item");

                //If this field is in a DataItemView, then delegate to that view
                if(dataItemRow.length && dataItemRow.data("view")){
                  dataItemRow.data("view").showValidation(category, errorMsg);
                  return;
                }
                else{
                  var elsWithViews = _.filter(categoryEls, function(el){
                      return ( $(el).data("view") &&
                          $(el).data("view").showValidation &&
                          !$(el).data("view").isNew );
                    });

                  if(elsWithViews.length){
                    _.each(elsWithViews, function(el){
                      $(el).data("view").showValidation();
                    });
                  }
                  else{
                    //Show the error message
                    categoryEls.filter(".notification").addClass("error").text(errorMsg);

                    //Add the error message to inputs
                    categoryEls.filter("textarea, input").addClass("error");
                  }
                }

                //Get the link in the table of contents navigation
                var navigationLink = this.$(".side-nav-item[data-category='" + category + "']");

                if(!navigationLink.length){
                  var section = categoryEls.parents("[data-section]");
                  navigationLink = this.$(".side-nav-item." + $(section).attr("data-section"));
                }

                //Show the error icon in the table of contents
                navigationLink.addClass("error")
                  .find(".icon")
                  .addClass("error")
                  .show();

                this.model.off("change:"  + category, this.model.checkValidity);
                this.model.once("change:" + category, this.model.checkValidity);

              }, this);

              if(errors){
                MetacatUI.appView.showAlert("Fix the errors flagged below before submitting.",
                    "alert-error",
                    this.$el,
                    null,
                    {
                          remove: true
                    });
              }

            },

            /**
            * @inheritdoc
            */
            hasUnsavedChanges: function(){
              //If the form hasn't been edited, we can close this view without confirmation
              if( typeof MetacatUI.rootDataPackage.getQueue != "function" || MetacatUI.rootDataPackage.getQueue().length)
                return true;
              else
                return false;
            },

            /**
            *  @inheritdoc
            */
            onClose: function() {

              //Execute the parent class onClose() function
              //EditorView.prototype.onClose.call(this);

              //Remove the listener on the Window
              if( this.beforeunloadCallback ){
                window.removeEventListener("beforeunload", this.beforeunloadCallback);
                delete this.beforeunloadCallback;
              }

              //Stop listening to the "add" event so that new package members aren't rendered.
              //Check first if the DataPackage has been intialized. An easy check is to see is
              // the 'models' attribute is undefined. If the DataPackage collection has been intialized,
              // then it would be an empty array.
              if( typeof MetacatUI.rootDataPackage.models !== "undefined" ){
                this.stopListening(MetacatUI.rootDataPackage, "add");
              }

              //Remove all the other events
              this.off();    // remove callbacks, prevent zombies
              this.model.off();

              $(".Editor").removeClass("Editor");
              this.$el.empty();

              this.model = null;

              // Close each subview
              _.each(this.subviews, function(subview) {
                if(subview.onClose)
                  subview.onClose();
              });

              this.subviews = [];

              this.undelegateEvents();

            },

            /*
                Handle "fileLoadError" events by alerting the user
                and removing the row from the data package table.

                @param item The model item passed by the fileLoadError event
             */
              handleFileLoadError: function(item) {
                  var message;
                  var fileName;
                  /* Remove the data package table row */
                  this.dataPackageView.removeOne(item);
                  /* Then inform the user */
                  if ( item && item.get &&
                      (item.get("fileName") !== "undefined" || item.get("fileName") !== null) ) {
                      fileName = item.get("fileName");
                      message = "The file " + fileName +
                          " is already included in this dataset. The duplicate file has not been added.";
                  } else {
                      message = "The chosen file is already included in this dataset. " +
                          "The duplicate file has not been added.";
                  }
                  MetacatUI.appView.showAlert(message, "alert-info", this.el, 10000, {remove: true});
              },

            /*
                Handle "fileReadError" events by alerting the user
                and removing the row from the data package table.

                @param item The model item passed by the fileReadError event
             */
              handleFileReadError: function(item) {
                  var message;
                  var fileName;
                  /* Remove the data package table row */
                  this.dataPackageView.removeOne(item);
                  /* Then inform the user */
                  if ( item && item.get &&
                      (item.get("fileName") !== "undefined" || item.get("fileName") !== null) ) {
                      fileName = item.get("fileName");
                      message = "The file " + fileName +
                          " could not be read. You may not have permission to read the file," +
                          " or the file was too large for your browser to upload. " +
                          "The file has not been added.";
                  } else {
                      message = "The chosen file " +
                          " could not be read. You may not have permission to read the file," +
                          " or the file was too large for your browser to upload. " +
                          "The file has not been added.";
                  }
                  MetacatUI.appView.showAlert(message, "alert-info", this.el, 10000, {remove: true});
              },
              /**
               * Save a draft of the parent EML model
               */
              saveDraft: function() {
                var view = this;

                try {
                  var title = this.model.get("title") || "No title";

                  LocalForage.setItem(this.model.get("id"), {
                    id: this.model.get("id"),
                    datetime: (new Date()).toISOString(),
                    title: Array.isArray(title) ? title[0] : title,
                    draft: this.model.serialize()
                  }).then(function() {
                    view.clearOldDrafts();
                  });
                } catch (ex) {
                  console.log("Error saving draft:", ex);
                }
              },

              /**
               * Clear older drafts by iterating over the sorted list of drafts
               * stored by LocalForage and removing any beyond a hardcoded limit.
               */
              clearOldDrafts: function() {
                var drafts = [];

                try {
                  LocalForage.iterate(function(value, key, iterationNumber) {
                    // Extract each draft
                    drafts.push({
                        key: key,
                        value: value
                      });
                    }).then(function(){
                      // Sort by datetime
                      drafts = _.sortBy(drafts, function(draft) {
                        return draft.value.datetime.toString();
                      }).reverse();
                    }).then(function() {
                      _.each(drafts, function(draft, i) {
                        var age = (new Date()) - new Date(draft.value.datetime);
                        var isOld = (age / 2678400000) > 1; // ~31days

                        // Delete this draft is not in the most recent 100 or
                        // if older than 31 days
                        var shouldDelete = i > 100 || isOld;

                        if (!shouldDelete) {
                          return;
                        }

                        LocalForage.removeItem(draft.key).then(function() {
                          // Item should be removed
                        });
                      })
                    });
                }
                catch (ex) {
                  console.log("Failed to clear old drafts: ", ex);
                }
              }
        });
        return EML211EditorView;
    });