/* 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 * @classcategory Views/Metadata * @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: MetacatUI.appModel.get("editorSaveButtonText"), /** * 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" || model.get("formatId") === "https://eml.ecoinformatics.org/eml-2.2.0" ) ) { 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:not(:has(.temporary-message))").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; });