Plato on Github
Report Home
collectionview.js
Maintainability
76.83
Lines of code
257
Difficulty
35.73
Estimated Errors
1.59
Function weight
By Complexity
By SLOC
// Collection View // --------------- // A view that iterates over a Backbone.Collection // and renders an individual ItemView for each model. Marionette.CollectionView = Marionette.View.extend({ // used as the prefix for item view events // that are forwarded through the collectionview itemViewEventPrefix: "itemview", // constructor constructor: function(options){ this._initChildViewStorage(); Marionette.View.prototype.constructor.apply(this, slice(arguments)); this._initialEvents(); }, // Configured the initial events that the collection view // binds to. Override this method to prevent the initial // events, or to add your own initial events. _initialEvents: function(){ if (this.collection){ this.listenTo(this.collection, "add", this.addChildView, this); this.listenTo(this.collection, "remove", this.removeItemView, this); this.listenTo(this.collection, "reset", this.render, this); } }, // Handle a child item added to the collection addChildView: function(item, collection, options){ this.closeEmptyView(); var ItemView = this.getItemView(item); var index = this.collection.indexOf(item); this.addItemView(item, ItemView, index); }, // Override from `Marionette.View` to guarantee the `onShow` method // of child views is called. onShowCalled: function(){ this.children.each(function(child){ Marionette.triggerMethod.call(child, "show"); }); }, // Internal method to trigger the before render callbacks // and events triggerBeforeRender: function(){ this.triggerMethod("before:render", this); this.triggerMethod("collection:before:render", this); }, // Internal method to trigger the rendered callbacks and // events triggerRendered: function(){ this.triggerMethod("render", this); this.triggerMethod("collection:rendered", this); }, // Render the collection of items. Override this method to // provide your own implementation of a render function for // the collection view. render: function(){ this.isClosed = false; this.triggerBeforeRender(); this._renderChildren(); this.triggerRendered(); return this; }, // Internal method. Separated so that CompositeView can have // more control over events being triggered, around the rendering // process _renderChildren: function(){ this.closeEmptyView(); this.closeChildren(); if (this.collection && this.collection.length > 0) { this.showCollection(); } else { this.showEmptyView(); } }, // Internal method to loop through each item in the // collection view and show it showCollection: function(){ var ItemView; this.collection.each(function(item, index){ ItemView = this.getItemView(item); this.addItemView(item, ItemView, index); }, this); }, // Internal method to show an empty view in place of // a collection of item views, when the collection is // empty showEmptyView: function(){ var EmptyView = Marionette.getOption(this, "emptyView"); if (EmptyView && !this._showingEmptyView){ this._showingEmptyView = true; var model = new Backbone.Model(); this.addItemView(model, EmptyView, 0); } }, // Internal method to close an existing emptyView instance // if one exists. Called when a collection view has been // rendered empty, and then an item is added to the collection. closeEmptyView: function(){ if (this._showingEmptyView){ this.closeChildren(); delete this._showingEmptyView; } }, // Retrieve the itemView type, either from `this.options.itemView` // or from the `itemView` in the object definition. The "options" // takes precedence. getItemView: function(item){ var itemView = Marionette.getOption(this, "itemView"); if (!itemView){ throwError("An `itemView` must be specified", "NoItemViewError"); } return itemView; }, // Render the child item's view and add it to the // HTML for the collection view. addItemView: function(item, ItemView, index){ // get the itemViewOptions if any were specified var itemViewOptions = Marionette.getOption(this, "itemViewOptions"); if (_.isFunction(itemViewOptions)){ itemViewOptions = itemViewOptions.call(this, item, index); } // build the view var view = this.buildItemView(item, ItemView, itemViewOptions); // set up the child view event forwarding this.addChildViewEventForwarding(view); // this view is about to be added this.triggerMethod("before:item:added", view); // Store the child view itself so we can properly // remove and/or close it later this.children.add(view); // Render it and show it this.renderItemView(view, index); // call the "show" method if the collection view // has already been shown if (this._isShown){ Marionette.triggerMethod.call(view, "show"); } // this view was added this.triggerMethod("after:item:added", view); }, // Set up the child view event forwarding. Uses an "itemview:" // prefix in front of all forwarded events. addChildViewEventForwarding: function(view){ var prefix = Marionette.getOption(this, "itemViewEventPrefix"); // Forward all child item view events through the parent, // prepending "itemview:" to the event name this.listenTo(view, "all", function(){ var args = slice(arguments); args[0] = prefix + ":" + args[0]; args.splice(1, 0, view); Marionette.triggerMethod.apply(this, args); }, this); }, // render the item view renderItemView: function(view, index) { view.render(); this.appendHtml(this, view, index); }, // Build an `itemView` for every model in the collection. buildItemView: function(item, ItemViewType, itemViewOptions){ var options = _.extend({model: item}, itemViewOptions); return new ItemViewType(options); }, // get the child view by item it holds, and remove it removeItemView: function(item){ var view = this.children.findByModel(item); this.removeChildView(view); this.checkEmpty(); }, // Remove the child view and close it removeChildView: function(view){ // shut down the child view properly, // including events that the collection has from it if (view){ this.stopListening(view); // call 'close' or 'remove', depending on which is found if (view.close) { view.close(); } else if (view.remove) { view.remove(); } this.children.remove(view); } this.triggerMethod("item:removed", view); }, // helper to show the empty view if the collection is empty checkEmpty: function() { // check if we're empty now, and if we are, show the // empty view if (!this.collection || this.collection.length === 0){ this.showEmptyView(); } }, // Append the HTML to the collection's `el`. // Override this method to do something other // then `.append`. appendHtml: function(collectionView, itemView, index){ collectionView.$el.append(itemView.el); }, // Internal method to set up the `children` object for // storing all of the child views _initChildViewStorage: function(){ this.children = new Backbone.ChildViewContainer(); }, // Handle cleanup and other closing needs for // the collection of views. close: function(){ if (this.isClosed){ return; } this.triggerMethod("collection:before:close"); this.closeChildren(); this.triggerMethod("collection:closed"); Marionette.View.prototype.close.apply(this, slice(arguments)); }, // Close the child views that this collection view // is holding on to, if any closeChildren: function(){ this.children.each(function(child){ this.removeChildView(child); }, this); this.checkEmpty(); } });