/bower_components/backbone.marionette/src/marionette.collectionview.js
JavaScript | 306 lines | 174 code | 53 blank | 79 comment | 19 complexity | e662baec79ccd2a95271fc77071ecc9b MD5 | raw file
- // 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();
- this.initRenderBuffer();
- },
- // Instead of inserting elements one by one into the page,
- // it's much more performant to insert elements into a document
- // fragment and then insert that document fragment into the page
- initRenderBuffer: function() {
- this.elBuffer = document.createDocumentFragment();
- },
- startBuffering: function() {
- this.initRenderBuffer();
- this.isBuffering = true;
- },
- endBuffering: function() {
- this.appendBuffer(this, this.elBuffer);
- this.initRenderBuffer();
- this.isBuffering = false;
- },
- // 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.startBuffering();
- this.closeEmptyView();
- this.closeChildren();
- if (this.collection && this.collection.length > 0) {
- this.showCollection();
- } else {
- this.showEmptyView();
- }
- this.endBuffering();
- },
- // 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 = this.getEmptyView();
- 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 empty view type
- getEmptyView: function(){
- return Marionette.getOption(this, "emptyView");
- },
- // 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();
- }
- },
- // You might need to override this if you've overridden appendHtml
- appendBuffer: function(collectionView, buffer) {
- collectionView.$el.append(buffer);
- },
- // Append the HTML to the collection's `el`.
- // Override this method to do something other
- // then `.append`.
- appendHtml: function(collectionView, itemView, index){
- if (collectionView.isBuffering) {
- // buffering happens on reset events and initial renders
- // in order to reduce the number of inserts into the
- // document, which are expensive.
- collectionView.elBuffer.appendChild(itemView.el);
- }
- else {
- // If we've already rendered the main collection, just
- // append the new items directly into the element.
- 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();
- }
- });