/src/body.js
JavaScript | 280 lines | 141 code | 37 blank | 102 comment | 33 complexity | 32705a7d4385545cb49c9a111eabf8e0 MD5 | raw file
Possible License(s): MIT
- /*
- backgrid
- http://github.com/wyuenho/backgrid
- Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
- Licensed under the MIT license.
- */
- /**
- Body is the table body which contains the rows inside a table. Body is
- responsible for refreshing the rows after sorting, insertion and removal.
- @class Backgrid.Body
- @extends Backbone.View
- */
- var Body = Backgrid.Body = Backbone.View.extend({
- /** @property */
- tagName: "tbody",
- /**
- Initializer.
- @param {Object} options
- @param {Backbone.Collection} options.collection
- @param {Backbone.Collection.<Backgrid.Column>|Array.<Backgrid.Column>|Array.<Object>} options.columns
- Column metadata.
- @param {Backgrid.Row} [options.row=Backgrid.Row] The Row class to use.
- @param {string} [options.emptyText] The text to display in the empty row.
- @throws {TypeError} If options.columns or options.collection is undefined.
- See Backgrid.Row.
- */
- initialize: function (options) {
- Backgrid.requireOptions(options, ["columns", "collection"]);
- this.columns = options.columns;
- if (!(this.columns instanceof Backbone.Collection)) {
- this.columns = new Columns(this.columns);
- }
- this.row = options.row || Row;
- this.rows = this.collection.map(function (model) {
- var row = new this.row({
- columns: this.columns,
- model: model
- });
- return row;
- }, this);
- this.emptyText = options.emptyText;
- this._unshiftEmptyRowMayBe();
- var collection = this.collection;
- this.listenTo(collection, "add", this.insertRow);
- this.listenTo(collection, "remove", this.removeRow);
- this.listenTo(collection, "sort", this.refresh);
- this.listenTo(collection, "reset", this.refresh);
- this.listenTo(collection, "backgrid:edited", this.moveToNextCell);
- },
- _unshiftEmptyRowMayBe: function () {
- if (this.rows.length === 0 && this.emptyText != null) {
- this.rows.unshift(new EmptyRow({
- emptyText: this.emptyText,
- columns: this.columns
- }));
- }
- },
- /**
- This method can be called either directly or as a callback to a
- [Backbone.Collecton#add](http://backbonejs.org/#Collection-add) event.
- When called directly, it accepts a model or an array of models and an
- option hash just like
- [Backbone.Collection#add](http://backbonejs.org/#Collection-add) and
- delegates to it. Once the model is added, a new row is inserted into the
- body and automatically rendered.
- When called as a callback of an `add` event, splices a new row into the
- body and renders it.
- @param {Backbone.Model} model The model to render as a row.
- @param {Backbone.Collection} collection When called directly, this
- parameter is actually the options to
- [Backbone.Collection#add](http://backbonejs.org/#Collection-add).
- @param {Object} options When called directly, this must be null.
- See:
- - [Backbone.Collection#add](http://backbonejs.org/#Collection-add)
- */
- insertRow: function (model, collection, options) {
- if (this.rows[0] instanceof EmptyRow) this.rows.pop().remove();
- // insertRow() is called directly
- if (!(collection instanceof Backbone.Collection) && !options) {
- this.collection.add(model, (options = collection));
- return;
- }
- options = _.extend({render: true}, options || {});
- var row = new this.row({
- columns: this.columns,
- model: model
- });
- var index = collection.indexOf(model);
- this.rows.splice(index, 0, row);
- var $el = this.$el;
- var $children = $el.children();
- var $rowEl = row.render().$el;
- if (options.render) {
- if (index >= $children.length) {
- $el.append($rowEl);
- }
- else {
- $children.eq(index).before($rowEl);
- }
- }
- },
- /**
- The method can be called either directly or as a callback to a
- [Backbone.Collection#remove](http://backbonejs.org/#Collection-remove)
- event.
- When called directly, it accepts a model or an array of models and an
- option hash just like
- [Backbone.Collection#remove](http://backbonejs.org/#Collection-remove) and
- delegates to it. Once the model is removed, a corresponding row is removed
- from the body.
- When called as a callback of a `remove` event, splices into the rows and
- removes the row responsible for rendering the model.
- @param {Backbone.Model} model The model to remove from the body.
- @param {Backbone.Collection} collection When called directly, this
- parameter is actually the options to
- [Backbone.Collection#remove](http://backbonejs.org/#Collection-remove).
- @param {Object} options When called directly, this must be null.
- See:
- - [Backbone.Collection#remove](http://backbonejs.org/#Collection-remove)
- */
- removeRow: function (model, collection, options) {
- // removeRow() is called directly
- if (!options) {
- this.collection.remove(model, (options = collection));
- this._unshiftEmptyRowMayBe();
- return;
- }
- if (_.isUndefined(options.render) || options.render) {
- this.rows[options.index].remove();
- }
- this.rows.splice(options.index, 1);
- this._unshiftEmptyRowMayBe();
- },
- /**
- Reinitialize all the rows inside the body and re-render them. Triggers a
- Backbone `backgrid:refresh` event from the collection along with the body
- instance as its sole parameter when done.
- */
- refresh: function () {
- for (var i = 0; i < this.rows.length; i++) {
- this.rows[i].remove();
- }
- this.rows = this.collection.map(function (model) {
- var row = new this.row({
- columns: this.columns,
- model: model
- });
- return row;
- }, this);
- this._unshiftEmptyRowMayBe();
- this.render();
- this.collection.trigger("backgrid:refresh", this);
- return this;
- },
- /**
- Renders all the rows inside this body. If the collection is empty and
- `options.emptyText` is defined and not null in the constructor, an empty
- row is rendered, otherwise no row is rendered.
- */
- render: function () {
- this.$el.empty();
- var fragment = document.createDocumentFragment();
- for (var i = 0; i < this.rows.length; i++) {
- var row = this.rows[i];
- fragment.appendChild(row.render().el);
- }
- this.el.appendChild(fragment);
- this.delegateEvents();
- return this;
- },
- /**
- Clean up this body and it's rows.
- @chainable
- */
- remove: function () {
- for (var i = 0; i < this.rows.length; i++) {
- var row = this.rows[i];
- row.remove.apply(row, arguments);
- }
- return Backbone.View.prototype.remove.apply(this, arguments);
- },
- /**
- Moves focus to the next renderable and editable cell and return the
- currently editing cell to display mode.
- @param {Backbone.Model} model The originating model
- @param {Backgrid.Column} column The originating model column
- @param {Backgrid.Command} command The Command object constructed from a DOM
- Event
- */
- moveToNextCell: function (model, column, command) {
- var i = this.collection.indexOf(model);
- var j = this.columns.indexOf(column);
- var cell, renderable, editable;
- this.rows[i].cells[j].exitEditMode();
- if (command.moveUp() || command.moveDown() || command.moveLeft() ||
- command.moveRight() || command.save()) {
- var l = this.columns.length;
- var maxOffset = l * this.collection.length;
- if (command.moveUp() || command.moveDown()) {
- var row = this.rows[i + (command.moveUp() ? -1 : 1)];
- if (row) {
- cell = row.cells[j];
- if (Backgrid.callByNeed(cell.column.editable(), cell.column, model)) {
- cell.enterEditMode();
- }
- }
- }
- else if (command.moveLeft() || command.moveRight()) {
- var right = command.moveRight();
- for (var offset = i * l + j + (right ? 1 : -1);
- offset >= 0 && offset < maxOffset;
- right ? offset++ : offset--) {
- var m = ~~(offset / l);
- var n = offset - m * l;
- cell = this.rows[m].cells[n];
- renderable = Backgrid.callByNeed(cell.column.renderable(), cell.column, cell.model);
- editable = Backgrid.callByNeed(cell.column.editable(), cell.column, model);
- if (renderable && editable) {
- cell.enterEditMode();
- break;
- }
- }
- }
- }
- }
- });