/* global define */ define([ 'jquery', 'underscore', 'backbone', 'localforage', 'collections/DataPackage', 'models/DataONEObject', 'models/metadata/ScienceMetadata', 'models/metadata/eml211/EML211', 'views/DataItemView', 'text!templates/dataPackage.html', 'text!templates/dataPackageStart.html'], function($, _, Backbone, LocalForage, DataPackage, DataONEObject, ScienceMetadata, EML211, DataItemView, DataPackageTemplate, DataPackageStartTemplate) { 'use strict'; /** * @class DataPackageView * @classdesc The main view of a Data Package in the editor. The view is * a file/folder browser * @classcategory Views * @screenshot views/DataPackageView.png */ var DataPackageView = Backbone.View.extend( /** @lends DataPackageView.prototype */{ tagName: "table", className: "table table-striped table-hover", id: "data-package-table", events: { "click .toggle-rows" : "toggleRows", // Show/hide rows associated with event's metadata row "click .message-row .addFiles" : "handleAddFiles" }, subviews: {}, /** * A reference to the parent EditorView that contains this view * @type EditorView * @since 2.15.0 */ parentEditorView: null, template: _.template(DataPackageTemplate), startMessageTemplate: _.template(DataPackageStartTemplate), // Models waiting for their parent folder to be rendered, hashed by parent id: // {'parentid': [model1, model2, ...]} delayedModels: {}, /* Flag indicating the open or closed state of the package rows */ isOpen: true, initialize: function(options) { //Get the options sent to this view if(typeof options == "object"){ //The edit option will allow the user to edit the table this.edit = options.edit || false; //The data package to render this.dataPackage = options.dataPackage || new DataPackage(); this.parentEditorView = options.parentEditorView || null; } //Create a new DataPackage collection if one wasn't sent else if(!this.dataPackage){ this.dataPackage = new DataPackage(); } return this; }, /** * Render the DataPackage HTML */ render: function() { this.$el.append(this.template({ loading: MetacatUI.appView.loadingTemplate({msg: "Loading files table... "}), id: this.dataPackage.get("id") })); // Listen for add events because models are being merged this.listenTo(this.dataPackage, 'add', this.addOne); this.listenTo(this.dataPackage, "fileAdded", this.addOne); // Render the current set of models in the DataPackage this.addAll(); //If this is a new data package, then display a message and button if((this.dataPackage.length == 1 && this.dataPackage.models[0].isNew()) || !this.dataPackage.length){ var messageRow = this.startMessageTemplate(); this.$("tbody").append(messageRow); this.listenTo(this.dataPackage, "add", function(){ this.$(".message-row").remove(); }); } //Render the Share control(s) this.renderShareControl(); return this; }, /** * Add a single DataItemView row to the DataPackageView */ addOne: function(item) { if(!item) return false; //Don't add duplicate rows if(this.$(".data-package-item[data-id='" + item.id + "']").length) return; var dataItemView, scimetaParent, parentRow, delayed_models; if ( _.contains(Object.keys(this.subviews), item.id) ) { return false; // Don't double render } dataItemView = new DataItemView({ model: item, parentEditorView: this.parentEditorView }); this.subviews[item.id] = dataItemView; // keep track of all views //Get the science metadata that documents this item scimetaParent = item.get("isDocumentedBy"); //If this item is not documented by a science metadata object, // and there is only one science metadata doc in the package, then assume it is // documented by that science metadata doc if( typeof scimetaParent == "undefined" || !scimetaParent ){ //Get the science metadata models var metadataIds = this.dataPackage.sciMetaPids; //If there is only one science metadata model in the package, then use it if( metadataIds.length == 1 ) scimetaParent = metadataIds[0]; } //Otherwise, get the first science metadata doc that documents this object else{ scimetaParent = scimetaParent[0]; } if((scimetaParent == item.get("id")) || (!scimetaParent && item.get("type") == "Metadata")) { // This is a metadata folder row, append it to the table this.$el.append(dataItemView.render().el); // Render any delayed models if this is the parent if ( _.contains(Object.keys(this.delayedModels), dataItemView.id) ) { delayed_models = this.delayedModels[dataItemView.id]; _.each(delayed_models, this.addOne, this); } } else{ // Find the parent row by it's id, stored in a custom attribute if(scimetaParent) parentRow = this.$("[data-id='" + scimetaParent + "']"); if ( typeof parentRow !== "undefined" && parentRow.length ) { // This is a data row, insert below it's metadata parent folder parentRow.after(dataItemView.render().el); // Remove it from the delayedModels list if necessary if ( _.contains(Object.keys(this.delayedModels), scimetaParent) ) { delayed_models = this.delayedModels[scimetaParent]; var index = _.indexOf(delayed_models, item); delayed_models = delayed_models.splice(index, 1); // Put the shortened array back if delayed models remains if ( delayed_models.length > 0 ) { this.delayedModels[scimetaParent] = delayed_models; } else { this.delayedModels[scimetaParent] = undefined; } } this.trigger("addOne"); } else { console.warn("Couldn't render " + item.id + ". Delayed until parent is rendered."); // Postpone the data row until the parent is rendered delayed_models = this.delayedModels[scimetaParent]; // Delay the model rendering if it isn't already delayed if ( typeof delayed_models !== "undefined" ) { if ( ! _.contains(delayed_models, item) ) { delayed_models.push(item); this.delayedModels[scimetaParent] = delayed_models; } } else { delayed_models = []; delayed_models.push(item); this.delayedModels[scimetaParent] = delayed_models; } } } }, /** * Add all rows to the DataPackageView */ addAll: function() { this.$el.find('#data-package-table-body').html(''); // clear the table first this.dataPackage.sort(); this.dataPackage.each(this.addOne, this); }, /** Remove the subview represented by the given model item. @param item The model representing the sub view to be removed */ removeOne: function(item) { if (_.contains(Object.keys(this.subviews), item.id)) { // Remove the view and the its reference in the subviews list this.subviews[item.id].remove(); delete this.subviews[item.id]; } }, handleAddFiles: function(e){ //Pass this on to the DataItemView for the root data package this.$(".data-package-item.folder").first().data("view").handleAddFiles(e); }, /** * Renders a control that opens the AccessPolicyView for editing permissions on this package * @since 2.15.0 */ renderShareControl: function(){ if( this.parentEditorView && !this.parentEditorView.isAccessPolicyEditEnabled() ){ this.$("#data-package-table-share").remove(); } }, /** * Close subviews as needed */ onClose: function() { // Close each subview _.each(Object.keys(this.subviews), function(id) { var subview = this.subviews[id]; subview.onClose(); }, this); //Reset the subviews from the view completely (by removing it from the prototype) this.__proto__.subviews = {}; }, /** Show or hide the data rows associated with the event row science metadata */ toggleRows: function(event) { if ( this.isOpen ) { // Get the DataItemView associated with each id _.each(Object.keys(this.subviews), function(id) { var subview = this.subviews[id]; if ( subview.model.get("type") === "Data" && subview.remove ) { // Remove the view from the DOM subview.remove(); // And from the subviews list delete this.subviews[id]; } }, this); // And then close the folder this.$el.find(".open") .removeClass("open") .addClass("closed") .removeClass("icon-chevron-down") .addClass("icon-chevron-right"); this.$el.find(".icon-folder-open") .removeClass("icon-folder-open") .addClass("icon-folder-close"); this.isOpen = false; } else { // Add sub rows to the view var dataModels = this.dataPackage.where({type: "Data"}); _.each(dataModels, function(model) { this.addOne(model); }, this); // And then open the folder this.$el.find(".closed") .removeClass("closed") .addClass("open") .removeClass("icon-folder-close") .addClass("icon-chevron-down"); this.$el.find(".icon-folder-close") .removeClass("icon-folder-close") .addClass("icon-folder-open"); this.isOpen = true; } event.stopPropagation(); event.preventDefault(); }, }); return DataPackageView; });