Source: src/js/views/DataPackageView.js

/* 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
         */
        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: {},

            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();
            	}
            	//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();
                	});
                }

                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});
                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.log("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);
            },

            /**
             * 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;
});