PageRenderTime 97ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 0ms

/bower_components/backbone.marionette/src/marionette.collectionview.js

https://github.com/robertd/benm
JavaScript | 306 lines | 174 code | 53 blank | 79 comment | 19 complexity | e662baec79ccd2a95271fc77071ecc9b MD5 | raw file
  1. // Collection View
  2. // ---------------
  3. // A view that iterates over a Backbone.Collection
  4. // and renders an individual ItemView for each model.
  5. Marionette.CollectionView = Marionette.View.extend({
  6. // used as the prefix for item view events
  7. // that are forwarded through the collectionview
  8. itemViewEventPrefix: "itemview",
  9. // constructor
  10. constructor: function(options){
  11. this._initChildViewStorage();
  12. Marionette.View.prototype.constructor.apply(this, slice(arguments));
  13. this._initialEvents();
  14. this.initRenderBuffer();
  15. },
  16. // Instead of inserting elements one by one into the page,
  17. // it's much more performant to insert elements into a document
  18. // fragment and then insert that document fragment into the page
  19. initRenderBuffer: function() {
  20. this.elBuffer = document.createDocumentFragment();
  21. },
  22. startBuffering: function() {
  23. this.initRenderBuffer();
  24. this.isBuffering = true;
  25. },
  26. endBuffering: function() {
  27. this.appendBuffer(this, this.elBuffer);
  28. this.initRenderBuffer();
  29. this.isBuffering = false;
  30. },
  31. // Configured the initial events that the collection view
  32. // binds to. Override this method to prevent the initial
  33. // events, or to add your own initial events.
  34. _initialEvents: function(){
  35. if (this.collection){
  36. this.listenTo(this.collection, "add", this.addChildView, this);
  37. this.listenTo(this.collection, "remove", this.removeItemView, this);
  38. this.listenTo(this.collection, "reset", this.render, this);
  39. }
  40. },
  41. // Handle a child item added to the collection
  42. addChildView: function(item, collection, options){
  43. this.closeEmptyView();
  44. var ItemView = this.getItemView(item);
  45. var index = this.collection.indexOf(item);
  46. this.addItemView(item, ItemView, index);
  47. },
  48. // Override from `Marionette.View` to guarantee the `onShow` method
  49. // of child views is called.
  50. onShowCalled: function(){
  51. this.children.each(function(child){
  52. Marionette.triggerMethod.call(child, "show");
  53. });
  54. },
  55. // Internal method to trigger the before render callbacks
  56. // and events
  57. triggerBeforeRender: function(){
  58. this.triggerMethod("before:render", this);
  59. this.triggerMethod("collection:before:render", this);
  60. },
  61. // Internal method to trigger the rendered callbacks and
  62. // events
  63. triggerRendered: function(){
  64. this.triggerMethod("render", this);
  65. this.triggerMethod("collection:rendered", this);
  66. },
  67. // Render the collection of items. Override this method to
  68. // provide your own implementation of a render function for
  69. // the collection view.
  70. render: function(){
  71. this.isClosed = false;
  72. this.triggerBeforeRender();
  73. this._renderChildren();
  74. this.triggerRendered();
  75. return this;
  76. },
  77. // Internal method. Separated so that CompositeView can have
  78. // more control over events being triggered, around the rendering
  79. // process
  80. _renderChildren: function(){
  81. this.startBuffering();
  82. this.closeEmptyView();
  83. this.closeChildren();
  84. if (this.collection && this.collection.length > 0) {
  85. this.showCollection();
  86. } else {
  87. this.showEmptyView();
  88. }
  89. this.endBuffering();
  90. },
  91. // Internal method to loop through each item in the
  92. // collection view and show it
  93. showCollection: function(){
  94. var ItemView;
  95. this.collection.each(function(item, index){
  96. ItemView = this.getItemView(item);
  97. this.addItemView(item, ItemView, index);
  98. }, this);
  99. },
  100. // Internal method to show an empty view in place of
  101. // a collection of item views, when the collection is
  102. // empty
  103. showEmptyView: function(){
  104. var EmptyView = this.getEmptyView();
  105. if (EmptyView && !this._showingEmptyView){
  106. this._showingEmptyView = true;
  107. var model = new Backbone.Model();
  108. this.addItemView(model, EmptyView, 0);
  109. }
  110. },
  111. // Internal method to close an existing emptyView instance
  112. // if one exists. Called when a collection view has been
  113. // rendered empty, and then an item is added to the collection.
  114. closeEmptyView: function(){
  115. if (this._showingEmptyView){
  116. this.closeChildren();
  117. delete this._showingEmptyView;
  118. }
  119. },
  120. // Retrieve the empty view type
  121. getEmptyView: function(){
  122. return Marionette.getOption(this, "emptyView");
  123. },
  124. // Retrieve the itemView type, either from `this.options.itemView`
  125. // or from the `itemView` in the object definition. The "options"
  126. // takes precedence.
  127. getItemView: function(item){
  128. var itemView = Marionette.getOption(this, "itemView");
  129. if (!itemView){
  130. throwError("An `itemView` must be specified", "NoItemViewError");
  131. }
  132. return itemView;
  133. },
  134. // Render the child item's view and add it to the
  135. // HTML for the collection view.
  136. addItemView: function(item, ItemView, index){
  137. // get the itemViewOptions if any were specified
  138. var itemViewOptions = Marionette.getOption(this, "itemViewOptions");
  139. if (_.isFunction(itemViewOptions)){
  140. itemViewOptions = itemViewOptions.call(this, item, index);
  141. }
  142. // build the view
  143. var view = this.buildItemView(item, ItemView, itemViewOptions);
  144. // set up the child view event forwarding
  145. this.addChildViewEventForwarding(view);
  146. // this view is about to be added
  147. this.triggerMethod("before:item:added", view);
  148. // Store the child view itself so we can properly
  149. // remove and/or close it later
  150. this.children.add(view);
  151. // Render it and show it
  152. this.renderItemView(view, index);
  153. // call the "show" method if the collection view
  154. // has already been shown
  155. if (this._isShown){
  156. Marionette.triggerMethod.call(view, "show");
  157. }
  158. // this view was added
  159. this.triggerMethod("after:item:added", view);
  160. },
  161. // Set up the child view event forwarding. Uses an "itemview:"
  162. // prefix in front of all forwarded events.
  163. addChildViewEventForwarding: function(view){
  164. var prefix = Marionette.getOption(this, "itemViewEventPrefix");
  165. // Forward all child item view events through the parent,
  166. // prepending "itemview:" to the event name
  167. this.listenTo(view, "all", function(){
  168. var args = slice(arguments);
  169. args[0] = prefix + ":" + args[0];
  170. args.splice(1, 0, view);
  171. Marionette.triggerMethod.apply(this, args);
  172. }, this);
  173. },
  174. // render the item view
  175. renderItemView: function(view, index) {
  176. view.render();
  177. this.appendHtml(this, view, index);
  178. },
  179. // Build an `itemView` for every model in the collection.
  180. buildItemView: function(item, ItemViewType, itemViewOptions){
  181. var options = _.extend({model: item}, itemViewOptions);
  182. return new ItemViewType(options);
  183. },
  184. // get the child view by item it holds, and remove it
  185. removeItemView: function(item){
  186. var view = this.children.findByModel(item);
  187. this.removeChildView(view);
  188. this.checkEmpty();
  189. },
  190. // Remove the child view and close it
  191. removeChildView: function(view){
  192. // shut down the child view properly,
  193. // including events that the collection has from it
  194. if (view){
  195. this.stopListening(view);
  196. // call 'close' or 'remove', depending on which is found
  197. if (view.close) { view.close(); }
  198. else if (view.remove) { view.remove(); }
  199. this.children.remove(view);
  200. }
  201. this.triggerMethod("item:removed", view);
  202. },
  203. // helper to show the empty view if the collection is empty
  204. checkEmpty: function() {
  205. // check if we're empty now, and if we are, show the
  206. // empty view
  207. if (!this.collection || this.collection.length === 0){
  208. this.showEmptyView();
  209. }
  210. },
  211. // You might need to override this if you've overridden appendHtml
  212. appendBuffer: function(collectionView, buffer) {
  213. collectionView.$el.append(buffer);
  214. },
  215. // Append the HTML to the collection's `el`.
  216. // Override this method to do something other
  217. // then `.append`.
  218. appendHtml: function(collectionView, itemView, index){
  219. if (collectionView.isBuffering) {
  220. // buffering happens on reset events and initial renders
  221. // in order to reduce the number of inserts into the
  222. // document, which are expensive.
  223. collectionView.elBuffer.appendChild(itemView.el);
  224. }
  225. else {
  226. // If we've already rendered the main collection, just
  227. // append the new items directly into the element.
  228. collectionView.$el.append(itemView.el);
  229. }
  230. },
  231. // Internal method to set up the `children` object for
  232. // storing all of the child views
  233. _initChildViewStorage: function(){
  234. this.children = new Backbone.ChildViewContainer();
  235. },
  236. // Handle cleanup and other closing needs for
  237. // the collection of views.
  238. close: function(){
  239. if (this.isClosed){ return; }
  240. this.triggerMethod("collection:before:close");
  241. this.closeChildren();
  242. this.triggerMethod("collection:closed");
  243. Marionette.View.prototype.close.apply(this, slice(arguments));
  244. },
  245. // Close the child views that this collection view
  246. // is holding on to, if any
  247. closeChildren: function(){
  248. this.children.each(function(child){
  249. this.removeChildView(child);
  250. }, this);
  251. this.checkEmpty();
  252. }
  253. });