PageRenderTime 47ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/src/body.js

https://github.com/rhinoman/backgrid
JavaScript | 280 lines | 141 code | 37 blank | 102 comment | 33 complexity | 32705a7d4385545cb49c9a111eabf8e0 MD5 | raw file
Possible License(s): MIT
  1. /*
  2. backgrid
  3. http://github.com/wyuenho/backgrid
  4. Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
  5. Licensed under the MIT license.
  6. */
  7. /**
  8. Body is the table body which contains the rows inside a table. Body is
  9. responsible for refreshing the rows after sorting, insertion and removal.
  10. @class Backgrid.Body
  11. @extends Backbone.View
  12. */
  13. var Body = Backgrid.Body = Backbone.View.extend({
  14. /** @property */
  15. tagName: "tbody",
  16. /**
  17. Initializer.
  18. @param {Object} options
  19. @param {Backbone.Collection} options.collection
  20. @param {Backbone.Collection.<Backgrid.Column>|Array.<Backgrid.Column>|Array.<Object>} options.columns
  21. Column metadata.
  22. @param {Backgrid.Row} [options.row=Backgrid.Row] The Row class to use.
  23. @param {string} [options.emptyText] The text to display in the empty row.
  24. @throws {TypeError} If options.columns or options.collection is undefined.
  25. See Backgrid.Row.
  26. */
  27. initialize: function (options) {
  28. Backgrid.requireOptions(options, ["columns", "collection"]);
  29. this.columns = options.columns;
  30. if (!(this.columns instanceof Backbone.Collection)) {
  31. this.columns = new Columns(this.columns);
  32. }
  33. this.row = options.row || Row;
  34. this.rows = this.collection.map(function (model) {
  35. var row = new this.row({
  36. columns: this.columns,
  37. model: model
  38. });
  39. return row;
  40. }, this);
  41. this.emptyText = options.emptyText;
  42. this._unshiftEmptyRowMayBe();
  43. var collection = this.collection;
  44. this.listenTo(collection, "add", this.insertRow);
  45. this.listenTo(collection, "remove", this.removeRow);
  46. this.listenTo(collection, "sort", this.refresh);
  47. this.listenTo(collection, "reset", this.refresh);
  48. this.listenTo(collection, "backgrid:edited", this.moveToNextCell);
  49. },
  50. _unshiftEmptyRowMayBe: function () {
  51. if (this.rows.length === 0 && this.emptyText != null) {
  52. this.rows.unshift(new EmptyRow({
  53. emptyText: this.emptyText,
  54. columns: this.columns
  55. }));
  56. }
  57. },
  58. /**
  59. This method can be called either directly or as a callback to a
  60. [Backbone.Collecton#add](http://backbonejs.org/#Collection-add) event.
  61. When called directly, it accepts a model or an array of models and an
  62. option hash just like
  63. [Backbone.Collection#add](http://backbonejs.org/#Collection-add) and
  64. delegates to it. Once the model is added, a new row is inserted into the
  65. body and automatically rendered.
  66. When called as a callback of an `add` event, splices a new row into the
  67. body and renders it.
  68. @param {Backbone.Model} model The model to render as a row.
  69. @param {Backbone.Collection} collection When called directly, this
  70. parameter is actually the options to
  71. [Backbone.Collection#add](http://backbonejs.org/#Collection-add).
  72. @param {Object} options When called directly, this must be null.
  73. See:
  74. - [Backbone.Collection#add](http://backbonejs.org/#Collection-add)
  75. */
  76. insertRow: function (model, collection, options) {
  77. if (this.rows[0] instanceof EmptyRow) this.rows.pop().remove();
  78. // insertRow() is called directly
  79. if (!(collection instanceof Backbone.Collection) && !options) {
  80. this.collection.add(model, (options = collection));
  81. return;
  82. }
  83. options = _.extend({render: true}, options || {});
  84. var row = new this.row({
  85. columns: this.columns,
  86. model: model
  87. });
  88. var index = collection.indexOf(model);
  89. this.rows.splice(index, 0, row);
  90. var $el = this.$el;
  91. var $children = $el.children();
  92. var $rowEl = row.render().$el;
  93. if (options.render) {
  94. if (index >= $children.length) {
  95. $el.append($rowEl);
  96. }
  97. else {
  98. $children.eq(index).before($rowEl);
  99. }
  100. }
  101. },
  102. /**
  103. The method can be called either directly or as a callback to a
  104. [Backbone.Collection#remove](http://backbonejs.org/#Collection-remove)
  105. event.
  106. When called directly, it accepts a model or an array of models and an
  107. option hash just like
  108. [Backbone.Collection#remove](http://backbonejs.org/#Collection-remove) and
  109. delegates to it. Once the model is removed, a corresponding row is removed
  110. from the body.
  111. When called as a callback of a `remove` event, splices into the rows and
  112. removes the row responsible for rendering the model.
  113. @param {Backbone.Model} model The model to remove from the body.
  114. @param {Backbone.Collection} collection When called directly, this
  115. parameter is actually the options to
  116. [Backbone.Collection#remove](http://backbonejs.org/#Collection-remove).
  117. @param {Object} options When called directly, this must be null.
  118. See:
  119. - [Backbone.Collection#remove](http://backbonejs.org/#Collection-remove)
  120. */
  121. removeRow: function (model, collection, options) {
  122. // removeRow() is called directly
  123. if (!options) {
  124. this.collection.remove(model, (options = collection));
  125. this._unshiftEmptyRowMayBe();
  126. return;
  127. }
  128. if (_.isUndefined(options.render) || options.render) {
  129. this.rows[options.index].remove();
  130. }
  131. this.rows.splice(options.index, 1);
  132. this._unshiftEmptyRowMayBe();
  133. },
  134. /**
  135. Reinitialize all the rows inside the body and re-render them. Triggers a
  136. Backbone `backgrid:refresh` event from the collection along with the body
  137. instance as its sole parameter when done.
  138. */
  139. refresh: function () {
  140. for (var i = 0; i < this.rows.length; i++) {
  141. this.rows[i].remove();
  142. }
  143. this.rows = this.collection.map(function (model) {
  144. var row = new this.row({
  145. columns: this.columns,
  146. model: model
  147. });
  148. return row;
  149. }, this);
  150. this._unshiftEmptyRowMayBe();
  151. this.render();
  152. this.collection.trigger("backgrid:refresh", this);
  153. return this;
  154. },
  155. /**
  156. Renders all the rows inside this body. If the collection is empty and
  157. `options.emptyText` is defined and not null in the constructor, an empty
  158. row is rendered, otherwise no row is rendered.
  159. */
  160. render: function () {
  161. this.$el.empty();
  162. var fragment = document.createDocumentFragment();
  163. for (var i = 0; i < this.rows.length; i++) {
  164. var row = this.rows[i];
  165. fragment.appendChild(row.render().el);
  166. }
  167. this.el.appendChild(fragment);
  168. this.delegateEvents();
  169. return this;
  170. },
  171. /**
  172. Clean up this body and it's rows.
  173. @chainable
  174. */
  175. remove: function () {
  176. for (var i = 0; i < this.rows.length; i++) {
  177. var row = this.rows[i];
  178. row.remove.apply(row, arguments);
  179. }
  180. return Backbone.View.prototype.remove.apply(this, arguments);
  181. },
  182. /**
  183. Moves focus to the next renderable and editable cell and return the
  184. currently editing cell to display mode.
  185. @param {Backbone.Model} model The originating model
  186. @param {Backgrid.Column} column The originating model column
  187. @param {Backgrid.Command} command The Command object constructed from a DOM
  188. Event
  189. */
  190. moveToNextCell: function (model, column, command) {
  191. var i = this.collection.indexOf(model);
  192. var j = this.columns.indexOf(column);
  193. var cell, renderable, editable;
  194. this.rows[i].cells[j].exitEditMode();
  195. if (command.moveUp() || command.moveDown() || command.moveLeft() ||
  196. command.moveRight() || command.save()) {
  197. var l = this.columns.length;
  198. var maxOffset = l * this.collection.length;
  199. if (command.moveUp() || command.moveDown()) {
  200. var row = this.rows[i + (command.moveUp() ? -1 : 1)];
  201. if (row) {
  202. cell = row.cells[j];
  203. if (Backgrid.callByNeed(cell.column.editable(), cell.column, model)) {
  204. cell.enterEditMode();
  205. }
  206. }
  207. }
  208. else if (command.moveLeft() || command.moveRight()) {
  209. var right = command.moveRight();
  210. for (var offset = i * l + j + (right ? 1 : -1);
  211. offset >= 0 && offset < maxOffset;
  212. right ? offset++ : offset--) {
  213. var m = ~~(offset / l);
  214. var n = offset - m * l;
  215. cell = this.rows[m].cells[n];
  216. renderable = Backgrid.callByNeed(cell.column.renderable(), cell.column, cell.model);
  217. editable = Backgrid.callByNeed(cell.column.editable(), cell.column, model);
  218. if (renderable && editable) {
  219. cell.enterEditMode();
  220. break;
  221. }
  222. }
  223. }
  224. }
  225. }
  226. });