backgrid
Version:
Backgrid.js is a set of components for building semantic and easily stylable data grid widgets with Backbone.
226 lines (185 loc) • 6.85 kB
JavaScript
/*
backgrid
http://github.com/cloudflare/backgrid
Copyright (c) 2013-present Cloudflare, Inc. and contributors
Licensed under the MIT license.
*/
/**
HeaderCell is a special cell class that renders a column header cell. If the
column is sortable, a sorter is also rendered and will trigger a table
refresh after sorting.
@class Backgrid.HeaderCell
@extends Backbone.View
*/
var HeaderCell = Backgrid.HeaderCell = Backbone.View.extend({
/** @property */
tagName: "th",
/** @property */
events: {
"click button": "onClick"
},
/**
Initializer.
@param {Object} options
@param {Backgrid.Column|Object} options.column
@throws {TypeError} If options.column or options.collection is undefined.
*/
initialize: function (options) {
this.column = options.column;
if (!(this.column instanceof Column)) {
this.column = new Column(this.column);
}
var column = this.column, collection = this.collection, $el = this.$el;
this.listenTo(column, "change:editable change:sortable change:renderable",
function (column) {
var changed = column.changedAttributes();
for (var key in changed) {
if (changed.hasOwnProperty(key)) {
$el.toggleClass(key, changed[key]);
}
}
});
this.listenTo(column, "change:direction", this.setCellDirection);
this.listenTo(column, "change:name change:label", this.render);
if (Backgrid.callByNeed(column.editable(), column, collection)) $el.addClass("editable");
if (Backgrid.callByNeed(column.sortable(), column, collection)) $el.addClass("sortable");
if (Backgrid.callByNeed(column.renderable(), column, collection)) $el.addClass("renderable");
this.listenTo(collection.fullCollection || collection, "backgrid:sorted", this.removeCellDirection);
},
/**
Event handler for the collection's `backgrid:sorted` event. Removes
all the CSS direction classes.
*/
removeCellDirection: function () {
this.$el.removeClass("ascending").removeClass("descending");
this.column.set("direction", null);
},
/**
Event handler for the column's `change:direction` event. If this
HeaderCell's column is being sorted on, it applies the direction given as a
CSS class to the header cell. Removes all the CSS direction classes
otherwise.
*/
setCellDirection: function (column, direction) {
this.$el.removeClass("ascending").removeClass("descending");
if (column.cid == this.column.cid) this.$el.addClass(direction);
},
/**
Event handler for the `click` event on the cell's anchor. If the column is
sortable, clicking on the anchor will cycle through 3 sorting orderings -
`ascending`, `descending`, and default.
*/
onClick: function (e) {
e.preventDefault();
var column = this.column;
var collection = this.collection;
var event = "backgrid:sort";
function cycleSort(header, col) {
if (column.get("direction") === "ascending") collection.trigger(event, col, "descending");
else if (column.get("direction") === "descending") collection.trigger(event, col, null);
else collection.trigger(event, col, "ascending");
}
function toggleSort(header, col) {
if (column.get("direction") === "ascending") collection.trigger(event, col, "descending");
else collection.trigger(event, col, "ascending");
}
var sortable = Backgrid.callByNeed(column.sortable(), column, this.collection);
if (sortable) {
var sortType = column.get("sortType");
if (sortType === "toggle") toggleSort(this, column);
else cycleSort(this, column);
}
},
/**
Renders a header cell with a sorter, a label, and a class name for this
column.
*/
render: function () {
this.$el.empty();
var column = this.column;
var sortable = Backgrid.callByNeed(column.sortable(), column, this.collection);
var label;
if(sortable){
label = $("<button>").text(column.get("label")).append("<span class='sort-caret' aria-hidden='true'></span>");
} else {
label = document.createTextNode(column.get("label"));
}
this.$el.append(label);
this.$el.addClass(column.get("name"));
this.$el.addClass(column.get("direction"));
this.delegateEvents();
return this;
}
});
/**
HeaderRow is a controller for a row of header cells.
@class Backgrid.HeaderRow
@extends Backgrid.Row
*/
var HeaderRow = Backgrid.HeaderRow = Backgrid.Row.extend({
/**
Initializer.
@param {Object} options
@param {Backbone.Collection.<Backgrid.Column>|Array.<Backgrid.Column>|Array.<Object>} options.columns
@param {Backgrid.HeaderCell} [options.headerCell] Customized default
HeaderCell for all the columns. Supply a HeaderCell class or instance to a
the `headerCell` key in a column definition for column-specific header
rendering.
@throws {TypeError} If options.columns or options.collection is undefined.
*/
initialize: function () {
Backgrid.Row.prototype.initialize.apply(this, arguments);
},
makeCell: function (column, options) {
var headerCell = column.get("headerCell") || options.headerCell || HeaderCell;
headerCell = new headerCell({
column: column,
collection: this.collection
});
return headerCell;
}
});
/**
Header is a special structural view class that renders a table head with a
single row of header cells.
@class Backgrid.Header
@extends Backbone.View
*/
var Header = Backgrid.Header = Backbone.View.extend({
/** @property */
tagName: "thead",
/**
Initializer. Initializes this table head view to contain a single header
row view.
@param {Object} options
@param {Backbone.Collection.<Backgrid.Column>|Array.<Backgrid.Column>|Array.<Object>} options.columns Column metadata.
@param {Backbone.Model} options.model The model instance to render.
@throws {TypeError} If options.columns or options.model is undefined.
*/
initialize: function (options) {
this.columns = options.columns;
if (!(this.columns instanceof Backbone.Collection)) {
this.columns = new Columns(this.columns);
}
this.row = new Backgrid.HeaderRow({
columns: this.columns,
collection: this.collection
});
},
/**
Renders this table head with a single row of header cells.
*/
render: function () {
this.$el.append(this.row.render().$el);
this.delegateEvents();
return this;
},
/**
Clean up this header and its row.
@chainable
*/
remove: function () {
this.row.remove.apply(this.row, arguments);
return Backbone.View.prototype.remove.apply(this, arguments);
}
});