PageRenderTime 35ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/src/header.js

https://github.com/rhinoman/backgrid
JavaScript | 308 lines | 147 code | 43 blank | 118 comment | 42 complexity | 7f257939d1d7a21606c56dd9a2622305 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. HeaderCell is a special cell class that renders a column header cell. If the
  9. column is sortable, a sorter is also rendered and will trigger a table
  10. refresh after sorting.
  11. @class Backgrid.HeaderCell
  12. @extends Backbone.View
  13. */
  14. var HeaderCell = Backgrid.HeaderCell = Backbone.View.extend({
  15. /** @property */
  16. tagName: "th",
  17. /** @property */
  18. events: {
  19. "click a": "onClick"
  20. },
  21. /**
  22. @property {null|"ascending"|"descending"} _direction The current sorting
  23. direction of this column.
  24. */
  25. _direction: null,
  26. /**
  27. Initializer.
  28. @param {Object} options
  29. @param {Backgrid.Column|Object} options.column
  30. @throws {TypeError} If options.column or options.collection is undefined.
  31. */
  32. initialize: function (options) {
  33. Backgrid.requireOptions(options, ["column", "collection"]);
  34. this.column = options.column;
  35. if (!(this.column instanceof Column)) {
  36. this.column = new Column(this.column);
  37. }
  38. this.listenTo(this.collection, "backgrid:sort", this._resetCellDirection);
  39. var column = this.column, $el = this.$el;
  40. this.listenTo(column, "change:editable change:sortable change:renderable",
  41. function (column) {
  42. var changed = column.changedAttributes();
  43. for (var key in changed) {
  44. if (changed.hasOwnProperty(key)) {
  45. $el.toggleClass(key, changed[key]);
  46. }
  47. }
  48. });
  49. if (column.get("editable")) $el.addClass("editable");
  50. if (column.get("sortable")) $el.addClass("sortable");
  51. if (column.get("renderable")) $el.addClass("renderable");
  52. },
  53. /**
  54. Gets or sets the direction of this cell. If called directly without
  55. parameters, returns the current direction of this cell, otherwise sets
  56. it. If a `null` is given, sets this cell back to the default order.
  57. @param {null|"ascending"|"descending"} dir
  58. @return {null|string} The current direction or the changed direction.
  59. */
  60. direction: function (dir) {
  61. if (arguments.length) {
  62. if (this._direction) this.$el.removeClass(this._direction);
  63. if (dir) this.$el.addClass(dir);
  64. this._direction = dir;
  65. }
  66. return this._direction;
  67. },
  68. /**
  69. Event handler for the Backbone `backgrid:sort` event. Resets this cell's
  70. direction to default if sorting is being done on another column.
  71. @private
  72. */
  73. _resetCellDirection: function (columnToSort, direction, comparator, collection) {
  74. if (collection == this.collection) {
  75. if (columnToSort !== this.column) this.direction(null);
  76. else this.direction(direction);
  77. }
  78. },
  79. /**
  80. Event handler for the `click` event on the cell's anchor. If the column is
  81. sortable, clicking on the anchor will cycle through 3 sorting orderings -
  82. `ascending`, `descending`, and default.
  83. */
  84. onClick: function (e) {
  85. e.preventDefault();
  86. function cycleSort(header, col) {
  87. if (header.direction() === "ascending") header.sort(col, "descending");
  88. else if (header.direction() === "descending") header.sort(col, null);
  89. else header.sort(col, "ascending");
  90. }
  91. function toggleSort(header, col) {
  92. if (header.direction() === "ascending") header.sort(col, "descending");
  93. else header.sort(col, "ascending");
  94. }
  95. var column = this.column;
  96. var sortable = Backgrid.callByNeed(column.sortable(), column, this.model);
  97. if (sortable) {
  98. var sortType = column.get("sortType");
  99. if (sortType === "toggle") toggleSort(this, column);
  100. else cycleSort(this, column);
  101. }
  102. },
  103. /**
  104. If the underlying collection is a Backbone.PageableCollection in
  105. server-mode or infinite-mode, a page of models is fetched after sorting is
  106. done on the server.
  107. If the underlying collection is a Backbone.PageableCollection in
  108. client-mode, or any
  109. [Backbone.Collection](http://backbonejs.org/#Collection) instance, sorting
  110. is done on the client side. If the collection is an instance of a
  111. Backbone.PageableCollection, sorting will be done globally on all the pages
  112. and the current page will then be returned.
  113. Triggers a Backbone `backgrid:sort` event from the collection when done
  114. with the column, direction, comparator and a reference to the collection.
  115. @param {Backgrid.Column} column
  116. @param {null|"ascending"|"descending"} direction
  117. See [Backbone.Collection#comparator](http://backbonejs.org/#Collection-comparator)
  118. */
  119. sort: function (column, direction) {
  120. var collection = this.collection;
  121. var order;
  122. if (direction === "ascending") order = -1;
  123. else if (direction === "descending") order = 1;
  124. else order = null;
  125. var comparator = this.makeComparator(column.get("name"), order,
  126. order ?
  127. column.sortValue() :
  128. function (model) {
  129. return model.cid;
  130. });
  131. if (Backbone.PageableCollection &&
  132. collection instanceof Backbone.PageableCollection) {
  133. collection.setSorting(order && column.get("name"), order,
  134. {sortValue: column.sortValue()});
  135. if (collection.mode == "client") {
  136. if (collection.fullCollection.comparator == null) {
  137. collection.fullCollection.comparator = comparator;
  138. }
  139. collection.fullCollection.sort();
  140. }
  141. else collection.fetch({reset: true});
  142. }
  143. else {
  144. collection.comparator = comparator;
  145. collection.sort();
  146. }
  147. this.collection.trigger("backgrid:sort", column, direction, comparator,
  148. this.collection);
  149. },
  150. makeComparator: function (attr, order, func) {
  151. return function (left, right) {
  152. // extract the values from the models
  153. var l = func(left, attr), r = func(right, attr), t;
  154. // if descending order, swap left and right
  155. if (order === 1) t = l, l = r, r = t;
  156. // compare as usual
  157. if (l === r) return 0;
  158. else if (l < r) return -1;
  159. return 1;
  160. };
  161. },
  162. /**
  163. Renders a header cell with a sorter and a label.
  164. */
  165. render: function () {
  166. this.$el.empty();
  167. var $label = $("<a>").text(this.column.get("label"));
  168. var sortable = Backgrid.callByNeed(this.column.sortable(), this.column, this.model);
  169. if (sortable) $label.append("<b class='sort-caret'></b>");
  170. this.$el.append($label);
  171. this.delegateEvents();
  172. return this;
  173. }
  174. });
  175. /**
  176. HeaderRow is a controller for a row of header cells.
  177. @class Backgrid.HeaderRow
  178. @extends Backgrid.Row
  179. */
  180. var HeaderRow = Backgrid.HeaderRow = Backgrid.Row.extend({
  181. requiredOptions: ["columns", "collection"],
  182. /**
  183. Initializer.
  184. @param {Object} options
  185. @param {Backbone.Collection.<Backgrid.Column>|Array.<Backgrid.Column>|Array.<Object>} options.columns
  186. @param {Backgrid.HeaderCell} [options.headerCell] Customized default
  187. HeaderCell for all the columns. Supply a HeaderCell class or instance to a
  188. the `headerCell` key in a column definition for column-specific header
  189. rendering.
  190. @throws {TypeError} If options.columns or options.collection is undefined.
  191. */
  192. initialize: function () {
  193. Backgrid.Row.prototype.initialize.apply(this, arguments);
  194. },
  195. makeCell: function (column, options) {
  196. var headerCell = column.get("headerCell") || options.headerCell || HeaderCell;
  197. headerCell = new headerCell({
  198. column: column,
  199. collection: this.collection
  200. });
  201. return headerCell;
  202. }
  203. });
  204. /**
  205. Header is a special structural view class that renders a table head with a
  206. single row of header cells.
  207. @class Backgrid.Header
  208. @extends Backbone.View
  209. */
  210. var Header = Backgrid.Header = Backbone.View.extend({
  211. /** @property */
  212. tagName: "thead",
  213. /**
  214. Initializer. Initializes this table head view to contain a single header
  215. row view.
  216. @param {Object} options
  217. @param {Backbone.Collection.<Backgrid.Column>|Array.<Backgrid.Column>|Array.<Object>} options.columns Column metadata.
  218. @param {Backbone.Model} options.model The model instance to render.
  219. @throws {TypeError} If options.columns or options.model is undefined.
  220. */
  221. initialize: function (options) {
  222. Backgrid.requireOptions(options, ["columns", "collection"]);
  223. this.columns = options.columns;
  224. if (!(this.columns instanceof Backbone.Collection)) {
  225. this.columns = new Columns(this.columns);
  226. }
  227. this.row = new Backgrid.HeaderRow({
  228. columns: this.columns,
  229. collection: this.collection
  230. });
  231. },
  232. /**
  233. Renders this table head with a single row of header cells.
  234. */
  235. render: function () {
  236. this.$el.append(this.row.render().$el);
  237. this.delegateEvents();
  238. return this;
  239. },
  240. /**
  241. Clean up this header and its row.
  242. @chainable
  243. */
  244. remove: function () {
  245. this.row.remove.apply(this.row, arguments);
  246. return Backbone.View.prototype.remove.apply(this, arguments);
  247. }
  248. });