updated backgrid

This commit is contained in:
kay.one 2013-06-20 22:37:37 -07:00
parent 467c88f711
commit 8fd7def9dd
4 changed files with 543 additions and 265 deletions

View file

@ -41,10 +41,6 @@ if (!String.prototype.trim || ws.trim()) {
};
}
function capitalize(s) {
return String.fromCharCode(s.charCodeAt(0) - 32) + s.slice(1);
}
function lpad(str, length, padstr) {
var paddingLen = length - (str + '').length;
paddingLen = paddingLen < 0 ? 0 : paddingLen;
@ -72,7 +68,9 @@ var Backgrid = root.Backgrid = {
resolveNameToClass: function (name, suffix) {
if (_.isString(name)) {
var key = _.map(name.split('-'), function (e) { return capitalize(e); }).join('') + suffix;
var key = _.map(name.split('-'), function (e) {
return e.slice(0, 1).toUpperCase() + e.slice(1);
}).join('') + suffix;
var klass = Backgrid[key] || Backgrid.Extension[key];
if (_.isUndefined(klass)) {
throw new ReferenceError("Class '" + key + "' not found");
@ -81,7 +79,17 @@ var Backgrid = root.Backgrid = {
}
return name;
},
callByNeed: function () {
var value = arguments[0];
if (!_.isFunction(value)) return value;
var context = arguments[1];
var args = [].slice.call(arguments, 2);
return value.apply(context, !!(args + '') ? args : void 0);
}
};
_.extend(Backgrid, Backbone.Events);
@ -99,7 +107,7 @@ _.extend(Backgrid, Backbone.Events);
var Command = Backgrid.Command = function (evt) {
_.extend(this, {
altKey: !!evt.altKey,
char: evt.char,
"char": evt["char"],
charCode: evt.charCode,
ctrlKey: !!evt.ctrlKey,
key: evt.key,
@ -737,12 +745,33 @@ var Cell = Backgrid.Cell = Backbone.View.extend({
if (!(this.column instanceof Column)) {
this.column = new Column(this.column);
}
this.formatter = Backgrid.resolveNameToClass(this.column.get("formatter") || this.formatter, "Formatter");
var column = this.column, model = this.model, $el = this.$el;
this.formatter = Backgrid.resolveNameToClass(column.get("formatter") ||
this.formatter, "Formatter");
this.editor = Backgrid.resolveNameToClass(this.editor, "CellEditor");
this.listenTo(this.model, "change:" + this.column.get("name"), function () {
if (!this.$el.hasClass("editor")) this.render();
this.listenTo(model, "change:" + column.get("name"), function () {
if (!$el.hasClass("editor")) this.render();
});
this.listenTo(this.model, "backgrid:error", this.renderError);
this.listenTo(model, "backgrid:error", this.renderError);
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]);
}
}
});
if (column.get("editable")) $el.addClass("editable");
if (column.get("sortable")) $el.addClass("sortable");
if (column.get("renderable")) $el.addClass("renderable");
},
/**
@ -779,7 +808,8 @@ var Cell = Backgrid.Cell = Backbone.View.extend({
var model = this.model;
var column = this.column;
if (column.get("editable")) {
var editable = Backgrid.callByNeed(column.get("editable"), column, model);
if (editable) {
this.currentEditor = new this.editor({
column: this.column,
@ -828,7 +858,7 @@ var Cell = Backgrid.Cell = Backbone.View.extend({
*/
remove: function () {
if (this.currentEditor) {
this.currentEditor.remove.apply(this, arguments);
this.currentEditor.remove.apply(this.currentEditor, arguments);
delete this.currentEditor;
}
return Backbone.View.prototype.remove.apply(this, arguments);
@ -1483,6 +1513,7 @@ var Column = Backgrid.Column = Backbone.Model.extend({
editable: true,
renderable: true,
formatter: undefined,
sortValue: undefined,
cell: undefined,
headerCell: undefined
},
@ -1491,22 +1522,36 @@ var Column = Backgrid.Column = Backbone.Model.extend({
Initializes this Column instance.
@param {Object} attrs Column attributes.
@param {string} attrs.name The name of the model attribute.
@param {string|Backgrid.Cell} attrs.cell The cell type.
If this is a string, the capitalized form will be used to look up a
cell class in Backbone, i.e.: string => StringCell. If a Cell subclass
is supplied, it is initialized with a hash of parameters. If a Cell
instance is supplied, it is used directly.
@param {string|Backgrid.HeaderCell} [attrs.headerCell] The header cell type.
@param {string} [attrs.label] The label to show in the header.
@param {boolean} [attrs.sortable=true]
@param {boolean} [attrs.editable=true]
@param {boolean} [attrs.renderable=true]
@param {Backgrid.CellFormatter|Object|string} [attrs.formatter] The
@param {boolean|string} [attrs.sortable=true]
@param {boolean|string} [attrs.editable=true]
@param {boolean|string} [attrs.renderable=true]
@param {Backgrid.CellFormatter | Object | string} [attrs.formatter] The
formatter to use to convert between raw model values and user input.
@param {(function(Backbone.Model, string): Object) | string} [sortValue] The
function to use to extract a value from the model for comparison during
sorting. If this value is a string, a method with the same name will be
looked up from the column instance.
@throws {TypeError} If attrs.cell or attrs.options are not supplied.
@throws {ReferenceError} If attrs.cell is a string but a cell class of
@throws {ReferenceError} If formatter is a string but a formatter class of
said name cannot be found in the Backgrid module.
See:
@ -1522,8 +1567,32 @@ var Column = Backgrid.Column = Backbone.Model.extend({
}
var headerCell = Backgrid.resolveNameToClass(this.get("headerCell"), "HeaderCell");
var cell = Backgrid.resolveNameToClass(this.get("cell"), "Cell");
this.set({ cell: cell, headerCell: headerCell }, { silent: true });
var sortValue = this.get("sortValue");
if (sortValue == null) sortValue = function (model, colName) {
return model.get(colName);
};
else if (_.isString(sortValue)) sortValue = this[sortValue];
var sortable = this.get("sortable");
if (_.isString(sortable)) sortable = this[sortable];
var editable = this.get("editable");
if (_.isString(editable)) editable = this[editable];
var renderable = this.get("renderable");
if (_.isString(renderable)) renderable = this[renderable];
this.set({
cell: cell,
headerCell: headerCell,
sortable: sortable,
editable: editable,
renderable: renderable,
sortValue: sortValue
}, { silent: true });
}
});
@ -1587,22 +1656,11 @@ var Row = Backgrid.Row = Backbone.View.extend({
cells.push(this.makeCell(columns.at(i), options));
}
this.listenTo(columns, "change:renderable", function (column, renderable) {
for (var i = 0; i < cells.length; i++) {
var cell = cells[i];
if (cell.column.get("name") == column.get("name")) {
if (renderable) cell.$el.show(); else cell.$el.hide();
}
}
});
this.listenTo(columns, "add", function (column, columns) {
var i = columns.indexOf(column);
var cell = this.makeCell(column, options);
cells.splice(i, 0, cell);
if (!cell.column.get("renderable")) cell.$el.hide();
var $el = this.$el;
if (i === 0) {
$el.prepend(cell.render().$el);
@ -1646,11 +1704,8 @@ var Row = Backgrid.Row = Backbone.View.extend({
this.$el.empty();
var fragment = document.createDocumentFragment();
for (var i = 0; i < this.cells.length; i++) {
var cell = this.cells[i];
fragment.appendChild(cell.render().el);
if (!cell.column.get("renderable")) cell.$el.hide();
fragment.appendChild(this.cells[i].render().el);
}
this.el.appendChild(fragment);
@ -1766,7 +1821,24 @@ var HeaderCell = Backgrid.HeaderCell = Backbone.View.extend({
if (!(this.column instanceof Column)) {
this.column = new Column(this.column);
}
this.listenTo(this.collection, "backgrid:sort", this._resetCellDirection);
var column = this.column, $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]);
}
}
});
if (column.get("editable")) $el.addClass("editable");
if (column.get("sortable")) $el.addClass("sortable");
if (column.get("renderable")) $el.addClass("renderable");
},
/**
@ -1793,9 +1865,9 @@ var HeaderCell = Backgrid.HeaderCell = Backbone.View.extend({
@private
*/
_resetCellDirection: function (sortByColName, direction, comparator, collection) {
_resetCellDirection: function (columnToSort, direction, comparator, collection) {
if (collection == this.collection) {
if (sortByColName !== this.column.get("name")) this.direction(null);
if (columnToSort !== this.column) this.direction(null);
else this.direction(direction);
}
},
@ -1808,34 +1880,12 @@ var HeaderCell = Backgrid.HeaderCell = Backbone.View.extend({
onClick: function (e) {
e.preventDefault();
var columnName = this.column.get("name");
if (this.column.get("sortable")) {
if (this.direction() === "ascending") {
this.sort(columnName, "descending", function (left, right) {
var leftVal = left.get(columnName);
var rightVal = right.get(columnName);
if (leftVal === rightVal) {
return 0;
}
else if (leftVal > rightVal) { return -1; }
return 1;
});
}
else if (this.direction() === "descending") {
this.sort(columnName, null);
}
else {
this.sort(columnName, "ascending", function (left, right) {
var leftVal = left.get(columnName);
var rightVal = right.get(columnName);
if (leftVal === rightVal) {
return 0;
}
else if (leftVal < rightVal) { return -1; }
return 1;
});
}
var column = this.column;
var sortable = Backgrid.callByNeed(column.get("sortable"), column, this.model);
if (sortable) {
if (this.direction() === "ascending") this.sort(column, "descending");
else if (this.direction() === "descending") this.sort(column, null);
else this.sort(column, "ascending");
}
},
@ -1852,31 +1902,37 @@ var HeaderCell = Backgrid.HeaderCell = Backbone.View.extend({
and the current page will then be returned.
Triggers a Backbone `backgrid:sort` event from the collection when done
with the column name, direction, comparator and a reference to the
collection.
with the column, direction, comparator and a reference to the collection.
@param {string} columnName
@param {Backgrid.Column} column
@param {null|"ascending"|"descending"} direction
@param {function(*, *): number} [comparator]
See [Backbone.Collection#comparator](http://backbonejs.org/#Collection-comparator)
*/
sort: function (columnName, direction, comparator) {
comparator = comparator || this._cidComparator;
sort: function (column, direction) {
var collection = this.collection;
if (Backbone.PageableCollection && collection instanceof Backbone.PageableCollection) {
var order;
if (direction === "ascending") order = -1;
else if (direction === "descending") order = 1;
else order = null;
var order;
if (direction === "ascending") order = -1;
else if (direction === "descending") order = 1;
else order = null;
collection.setSorting(order ? columnName : null, order);
var comparator = this.makeComparator(column.get("name"), order,
order ?
column.get("sortValue") :
function (model) {
return model.cid;
});
if (Backbone.PageableCollection &&
collection instanceof Backbone.PageableCollection) {
collection.setSorting(order && column.get("name"), order,
{sortValue: column.get("sortValue")});
if (collection.mode == "client") {
if (!collection.fullCollection.comparator) {
if (collection.fullCollection.comparator == null) {
collection.fullCollection.comparator = comparator;
}
collection.fullCollection.sort();
@ -1888,26 +1944,24 @@ var HeaderCell = Backgrid.HeaderCell = Backbone.View.extend({
collection.sort();
}
this.collection.trigger("backgrid:sort", columnName, direction, comparator, this.collection);
this.collection.trigger("backgrid:sort", column, direction, comparator,
this.collection);
},
/**
Default comparator for Backbone.Collections. Sorts cids in ascending
order. The cids of the models are assumed to be in insertion order.
makeComparator: function (attr, order, func) {
@private
@param {*} left
@param {*} right
*/
_cidComparator: function (left, right) {
var lcid = left.cid, rcid = right.cid;
if (!_.isUndefined(lcid) && !_.isUndefined(rcid)) {
lcid = lcid.slice(1) * 1, rcid = rcid.slice(1) * 1;
if (lcid < rcid) return -1;
else if (lcid > rcid) return 1;
}
return function (left, right) {
// extract the values from the models
var l = func(left, attr), r = func(right, attr), t;
return 0;
// if descending order, swap left and right
if (order === 1) t = l, l = r, r = t;
// compare as usual
if (l === r) return 0;
else if (l < r) return -1;
return 1;
};
},
/**
@ -1915,7 +1969,9 @@ var HeaderCell = Backgrid.HeaderCell = Backbone.View.extend({
*/
render: function () {
this.$el.empty();
var $label = $("<a>").text(this.column.get("label")).append("<b class='sort-caret'></b>");
var $label = $("<a>").text(this.column.get("label"));
var sortable = Backgrid.callByNeed(this.column.get("sortable"), this.column, this.model);
if (sortable) $label.append("<b class='sort-caret'></b>");
this.$el.append($label);
this.delegateEvents();
return this;
@ -2259,6 +2315,9 @@ var Body = Backgrid.Body = Backbone.View.extend({
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()) {
@ -2267,7 +2326,12 @@ var Body = Backgrid.Body = Backbone.View.extend({
if (command.moveUp() || command.moveDown()) {
var row = this.rows[i + (command.moveUp() ? -1 : 1)];
if (row) row.cells[j].enterEditMode();
if (row) {
cell = row.cells[j];
if (Backgrid.callByNeed(cell.column.get("editable"), cell.column, model)) {
cell.enterEditMode();
}
}
}
else if (command.moveLeft() || command.moveRight()) {
var right = command.moveRight();
@ -2276,16 +2340,16 @@ var Body = Backgrid.Body = Backbone.View.extend({
right ? offset++ : offset--) {
var m = ~~(offset / l);
var n = offset - m * l;
var cell = this.rows[m].cells[n];
if (cell.column.get("renderable") && cell.column.get("editable")) {
cell = this.rows[m].cells[n];
renderable = Backgrid.callByNeed(cell.column.get("renderable"), cell.column, cell.model);
editable = Backgrid.callByNeed(cell.column.get("editable"), cell.column, model);
if (renderable && editable) {
cell.enterEditMode();
break;
}
}
}
}
this.rows[i].cells[j].exitEditMode();
}
});
/*