updated backgrid
This commit is contained in:
parent
467c88f711
commit
8fd7def9dd
|
@ -6,10 +6,12 @@
|
||||||
Licensed under the MIT @license.
|
Licensed under the MIT @license.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function ($, _, Backbone, Backgrid, lunr) {
|
(function (root) {
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
var Backbone = root.Backbone, Backgrid = root.Backgrid, lunr = root.lunr;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
ServerSideFilter is a search form widget that submits a query to the server
|
ServerSideFilter is a search form widget that submits a query to the server
|
||||||
for filtering the current collection.
|
for filtering the current collection.
|
||||||
|
@ -36,14 +38,17 @@
|
||||||
/** @property {string} [name='q'] Query key */
|
/** @property {string} [name='q'] Query key */
|
||||||
name: "q",
|
name: "q",
|
||||||
|
|
||||||
/** @property The HTML5 placeholder to appear beneath the search box. */
|
/**
|
||||||
|
@property {string} [placeholder] The HTML5 placeholder to appear beneath
|
||||||
|
the search box.
|
||||||
|
*/
|
||||||
placeholder: null,
|
placeholder: null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@param {Object} options
|
@param {Object} options
|
||||||
@param {Backbone.Collection} options.collection
|
@param {Backbone.Collection} options.collection
|
||||||
@param {String} [options.name]
|
@param {string} [options.name]
|
||||||
@param {String} [options.placeholder]
|
@param {string} [options.placeholder]
|
||||||
*/
|
*/
|
||||||
initialize: function (options) {
|
initialize: function (options) {
|
||||||
Backgrid.requireOptions(options, ["collection"]);
|
Backgrid.requireOptions(options, ["collection"]);
|
||||||
|
@ -51,16 +56,21 @@
|
||||||
this.name = options.name || this.name;
|
this.name = options.name || this.name;
|
||||||
this.placeholder = options.placeholder || this.placeholder;
|
this.placeholder = options.placeholder || this.placeholder;
|
||||||
|
|
||||||
|
// Persist the query on pagination
|
||||||
var collection = this.collection, self = this;
|
var collection = this.collection, self = this;
|
||||||
if (Backbone.PageableCollection &&
|
if (Backbone.PageableCollection &&
|
||||||
collection instanceof Backbone.PageableCollection &&
|
collection instanceof Backbone.PageableCollection &&
|
||||||
collection.mode == "server") {
|
collection.mode == "server") {
|
||||||
collection.queryParams[this.name] = function () {
|
collection.queryParams[this.name] = function () {
|
||||||
return self.$el.find("input[type=text]").val();
|
return self.searchBox().val() || null;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
searchBox: function () {
|
||||||
|
return this.$el.find("input[type=text]");
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Upon search form submission, this event handler constructs a query
|
Upon search form submission, this event handler constructs a query
|
||||||
parameter object and pass it to Collection#fetch for server-side
|
parameter object and pass it to Collection#fetch for server-side
|
||||||
|
@ -68,9 +78,22 @@
|
||||||
*/
|
*/
|
||||||
search: function (e) {
|
search: function (e) {
|
||||||
if (e) e.preventDefault();
|
if (e) e.preventDefault();
|
||||||
|
|
||||||
var data = {};
|
var data = {};
|
||||||
data[this.name] = this.$el.find("input[type=text]").val();
|
|
||||||
this.collection.fetch({data: data});
|
// go back to the first page on search
|
||||||
|
var collection = this.collection;
|
||||||
|
if (Backbone.PageableCollection &&
|
||||||
|
collection instanceof Backbone.PageableCollection &&
|
||||||
|
collection.mode == "server") {
|
||||||
|
collection.state.currentPage = 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var query = this.searchBox().val();
|
||||||
|
if (query) data[this.name] = query;
|
||||||
|
}
|
||||||
|
|
||||||
|
collection.fetch({data: data, reset: true});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -79,8 +102,8 @@
|
||||||
*/
|
*/
|
||||||
clear: function (e) {
|
clear: function (e) {
|
||||||
if (e) e.preventDefault();
|
if (e) e.preventDefault();
|
||||||
this.$("input[type=text]").val(null);
|
this.searchBox().val(null);
|
||||||
this.collection.fetch();
|
this.collection.fetch({reset: true});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -115,8 +138,7 @@
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.clear();
|
this.clear();
|
||||||
},
|
},
|
||||||
"change input[type=text]": "search",
|
"keydown input[type=text]": "search",
|
||||||
"keyup input[type=text]": "search",
|
|
||||||
"submit": function (e) {
|
"submit": function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.search();
|
this.search();
|
||||||
|
@ -124,14 +146,14 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@property {?Array.<string>} A list of model field names to search
|
@property {?Array.<string>} [fields] A list of model field names to
|
||||||
for matches. If null, all of the fields will be searched.
|
search for matches. If null, all of the fields will be searched.
|
||||||
*/
|
*/
|
||||||
fields: null,
|
fields: null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@property wait The time in milliseconds to wait since for since the last
|
@property [wait=149] The time in milliseconds to wait since for since the
|
||||||
change to the search box's value before searching. This value can be
|
last change to the search box's value before searching. This value can be
|
||||||
adjusted depending on how often the search box is used and how large the
|
adjusted depending on how often the search box is used and how large the
|
||||||
search index is.
|
search index is.
|
||||||
*/
|
*/
|
||||||
|
@ -143,9 +165,9 @@
|
||||||
|
|
||||||
@param {Object} options
|
@param {Object} options
|
||||||
@param {Backbone.Collection} options.collection
|
@param {Backbone.Collection} options.collection
|
||||||
@param {String} [options.placeholder]
|
@param {string} [options.placeholder]
|
||||||
@param {String} [options.fields]
|
@param {string} [options.fields]
|
||||||
@param {String} [options.wait=149]
|
@param {string} [options.wait=149]
|
||||||
*/
|
*/
|
||||||
initialize: function (options) {
|
initialize: function (options) {
|
||||||
ServerSideFilter.prototype.initialize.apply(this, arguments);
|
ServerSideFilter.prototype.initialize.apply(this, arguments);
|
||||||
|
@ -155,11 +177,8 @@
|
||||||
|
|
||||||
this._debounceMethods(["search", "clear"]);
|
this._debounceMethods(["search", "clear"]);
|
||||||
|
|
||||||
var collection = this.collection;
|
var collection = this.collection = this.collection.fullCollection || this.collection;
|
||||||
var shadowCollection = this.shadowCollection = collection.clone();
|
var shadowCollection = this.shadowCollection = collection.clone();
|
||||||
shadowCollection.url = collection.url;
|
|
||||||
shadowCollection.sync = collection.sync;
|
|
||||||
shadowCollection.parse = collection.parse;
|
|
||||||
|
|
||||||
this.listenTo(collection, "add", function (model, collection, options) {
|
this.listenTo(collection, "add", function (model, collection, options) {
|
||||||
shadowCollection.add(model, options);
|
shadowCollection.add(model, options);
|
||||||
|
@ -167,9 +186,15 @@
|
||||||
this.listenTo(collection, "remove", function (model, collection, options) {
|
this.listenTo(collection, "remove", function (model, collection, options) {
|
||||||
shadowCollection.remove(model, options);
|
shadowCollection.remove(model, options);
|
||||||
});
|
});
|
||||||
this.listenTo(collection, "sort reset", function (collection, options) {
|
this.listenTo(collection, "sort", function (col) {
|
||||||
|
if (!this.searchBox().val()) shadowCollection.reset(col.models);
|
||||||
|
});
|
||||||
|
this.listenTo(collection, "reset", function (col, options) {
|
||||||
options = _.extend({reindex: true}, options || {});
|
options = _.extend({reindex: true}, options || {});
|
||||||
if (options.reindex) shadowCollection.reset(collection.models);
|
if (options.reindex && col === collection &&
|
||||||
|
options.from == null && options.to == null) {
|
||||||
|
shadowCollection.reset(col.models);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -218,7 +243,9 @@
|
||||||
when all the matches have been found.
|
when all the matches have been found.
|
||||||
*/
|
*/
|
||||||
search: function () {
|
search: function () {
|
||||||
var matcher = _.bind(this.makeMatcher(this.$("input[type=text]").val()), this);
|
var matcher = _.bind(this.makeMatcher(this.searchBox().val()), this);
|
||||||
|
var col = this.collection;
|
||||||
|
if (col.pageableCollection) col.pageableCollection.getFirstPage({silent: true});
|
||||||
this.collection.reset(this.shadowCollection.filter(matcher), {reindex: false});
|
this.collection.reset(this.shadowCollection.filter(matcher), {reindex: false});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -226,7 +253,7 @@
|
||||||
Clears the search box and reset the collection to its original.
|
Clears the search box and reset the collection to its original.
|
||||||
*/
|
*/
|
||||||
clear: function () {
|
clear: function () {
|
||||||
this.$("input[type=text]").val(null);
|
this.searchBox().val(null);
|
||||||
this.collection.reset(this.shadowCollection.models, {reindex: false});
|
this.collection.reset(this.shadowCollection.models, {reindex: false});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,7 +289,7 @@
|
||||||
|
|
||||||
@param {Object} options
|
@param {Object} options
|
||||||
@param {Backbone.Collection} options.collection
|
@param {Backbone.Collection} options.collection
|
||||||
@param {String} [options.placeholder]
|
@param {string} [options.placeholder]
|
||||||
@param {string} [options.ref] `lunrjs` document reference attribute name.
|
@param {string} [options.ref] `lunrjs` document reference attribute name.
|
||||||
@param {Object} [options.fields] A hash of `lunrjs` index field names and
|
@param {Object} [options.fields] A hash of `lunrjs` index field names and
|
||||||
boost value.
|
boost value.
|
||||||
|
@ -273,7 +300,7 @@
|
||||||
|
|
||||||
this.ref = options.ref || this.ref;
|
this.ref = options.ref || this.ref;
|
||||||
|
|
||||||
var collection = this.collection;
|
var collection = this.collection = this.collection.fullCollection || this.collection;
|
||||||
this.listenTo(collection, "add", this.addToIndex);
|
this.listenTo(collection, "add", this.addToIndex);
|
||||||
this.listenTo(collection, "remove", this.removeFromIndex);
|
this.listenTo(collection, "remove", this.removeFromIndex);
|
||||||
this.listenTo(collection, "reset", this.resetIndex);
|
this.listenTo(collection, "reset", this.resetIndex);
|
||||||
|
@ -351,15 +378,17 @@
|
||||||
query answer.
|
query answer.
|
||||||
*/
|
*/
|
||||||
search: function () {
|
search: function () {
|
||||||
var searchResults = this.index.search(this.$("input[type=text]").val());
|
var searchResults = this.index.search(this.searchBox().val());
|
||||||
var models = [];
|
var models = [];
|
||||||
for (var i = 0; i < searchResults.length; i++) {
|
for (var i = 0; i < searchResults.length; i++) {
|
||||||
var result = searchResults[i];
|
var result = searchResults[i];
|
||||||
models.push(this.shadowCollection.get(result.ref));
|
models.push(this.shadowCollection.get(result.ref));
|
||||||
}
|
}
|
||||||
this.collection.reset(models, {reindex: false});
|
var col = this.collection;
|
||||||
|
if (col.pageableCollection) col.pageableCollection.getFirstPage({silent: true});
|
||||||
|
col.reset(models, {reindex: false});
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}(jQuery, _, Backbone, Backgrid, lunr));
|
}(this));
|
||||||
|
|
|
@ -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) {
|
function lpad(str, length, padstr) {
|
||||||
var paddingLen = length - (str + '').length;
|
var paddingLen = length - (str + '').length;
|
||||||
paddingLen = paddingLen < 0 ? 0 : paddingLen;
|
paddingLen = paddingLen < 0 ? 0 : paddingLen;
|
||||||
|
@ -72,7 +68,9 @@ var Backgrid = root.Backgrid = {
|
||||||
|
|
||||||
resolveNameToClass: function (name, suffix) {
|
resolveNameToClass: function (name, suffix) {
|
||||||
if (_.isString(name)) {
|
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];
|
var klass = Backgrid[key] || Backgrid.Extension[key];
|
||||||
if (_.isUndefined(klass)) {
|
if (_.isUndefined(klass)) {
|
||||||
throw new ReferenceError("Class '" + key + "' not found");
|
throw new ReferenceError("Class '" + key + "' not found");
|
||||||
|
@ -81,7 +79,17 @@ var Backgrid = root.Backgrid = {
|
||||||
}
|
}
|
||||||
|
|
||||||
return name;
|
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);
|
_.extend(Backgrid, Backbone.Events);
|
||||||
|
|
||||||
|
@ -99,7 +107,7 @@ _.extend(Backgrid, Backbone.Events);
|
||||||
var Command = Backgrid.Command = function (evt) {
|
var Command = Backgrid.Command = function (evt) {
|
||||||
_.extend(this, {
|
_.extend(this, {
|
||||||
altKey: !!evt.altKey,
|
altKey: !!evt.altKey,
|
||||||
char: evt.char,
|
"char": evt["char"],
|
||||||
charCode: evt.charCode,
|
charCode: evt.charCode,
|
||||||
ctrlKey: !!evt.ctrlKey,
|
ctrlKey: !!evt.ctrlKey,
|
||||||
key: evt.key,
|
key: evt.key,
|
||||||
|
@ -737,12 +745,33 @@ var Cell = Backgrid.Cell = Backbone.View.extend({
|
||||||
if (!(this.column instanceof Column)) {
|
if (!(this.column instanceof Column)) {
|
||||||
this.column = new Column(this.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.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 model = this.model;
|
||||||
var column = this.column;
|
var column = this.column;
|
||||||
|
|
||||||
if (column.get("editable")) {
|
var editable = Backgrid.callByNeed(column.get("editable"), column, model);
|
||||||
|
if (editable) {
|
||||||
|
|
||||||
this.currentEditor = new this.editor({
|
this.currentEditor = new this.editor({
|
||||||
column: this.column,
|
column: this.column,
|
||||||
|
@ -828,7 +858,7 @@ var Cell = Backgrid.Cell = Backbone.View.extend({
|
||||||
*/
|
*/
|
||||||
remove: function () {
|
remove: function () {
|
||||||
if (this.currentEditor) {
|
if (this.currentEditor) {
|
||||||
this.currentEditor.remove.apply(this, arguments);
|
this.currentEditor.remove.apply(this.currentEditor, arguments);
|
||||||
delete this.currentEditor;
|
delete this.currentEditor;
|
||||||
}
|
}
|
||||||
return Backbone.View.prototype.remove.apply(this, arguments);
|
return Backbone.View.prototype.remove.apply(this, arguments);
|
||||||
|
@ -1483,6 +1513,7 @@ var Column = Backgrid.Column = Backbone.Model.extend({
|
||||||
editable: true,
|
editable: true,
|
||||||
renderable: true,
|
renderable: true,
|
||||||
formatter: undefined,
|
formatter: undefined,
|
||||||
|
sortValue: undefined,
|
||||||
cell: undefined,
|
cell: undefined,
|
||||||
headerCell: undefined
|
headerCell: undefined
|
||||||
},
|
},
|
||||||
|
@ -1491,22 +1522,36 @@ var Column = Backgrid.Column = Backbone.Model.extend({
|
||||||
Initializes this Column instance.
|
Initializes this Column instance.
|
||||||
|
|
||||||
@param {Object} attrs Column attributes.
|
@param {Object} attrs Column attributes.
|
||||||
|
|
||||||
@param {string} attrs.name The name of the model attribute.
|
@param {string} attrs.name The name of the model attribute.
|
||||||
|
|
||||||
@param {string|Backgrid.Cell} attrs.cell The cell type.
|
@param {string|Backgrid.Cell} attrs.cell The cell type.
|
||||||
If this is a string, the capitalized form will be used to look up a
|
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
|
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
|
is supplied, it is initialized with a hash of parameters. If a Cell
|
||||||
instance is supplied, it is used directly.
|
instance is supplied, it is used directly.
|
||||||
|
|
||||||
@param {string|Backgrid.HeaderCell} [attrs.headerCell] The header cell type.
|
@param {string|Backgrid.HeaderCell} [attrs.headerCell] The header cell type.
|
||||||
|
|
||||||
@param {string} [attrs.label] The label to show in the header.
|
@param {string} [attrs.label] The label to show in the header.
|
||||||
@param {boolean} [attrs.sortable=true]
|
|
||||||
@param {boolean} [attrs.editable=true]
|
@param {boolean|string} [attrs.sortable=true]
|
||||||
@param {boolean} [attrs.renderable=true]
|
|
||||||
|
@param {boolean|string} [attrs.editable=true]
|
||||||
|
|
||||||
|
@param {boolean|string} [attrs.renderable=true]
|
||||||
|
|
||||||
@param {Backgrid.CellFormatter | Object | string} [attrs.formatter] The
|
@param {Backgrid.CellFormatter | Object | string} [attrs.formatter] The
|
||||||
formatter to use to convert between raw model values and user input.
|
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 {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.
|
said name cannot be found in the Backgrid module.
|
||||||
|
|
||||||
See:
|
See:
|
||||||
|
@ -1522,8 +1567,32 @@ var Column = Backgrid.Column = Backbone.Model.extend({
|
||||||
}
|
}
|
||||||
|
|
||||||
var headerCell = Backgrid.resolveNameToClass(this.get("headerCell"), "HeaderCell");
|
var headerCell = Backgrid.resolveNameToClass(this.get("headerCell"), "HeaderCell");
|
||||||
|
|
||||||
var cell = Backgrid.resolveNameToClass(this.get("cell"), "Cell");
|
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));
|
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) {
|
this.listenTo(columns, "add", function (column, columns) {
|
||||||
var i = columns.indexOf(column);
|
var i = columns.indexOf(column);
|
||||||
var cell = this.makeCell(column, options);
|
var cell = this.makeCell(column, options);
|
||||||
cells.splice(i, 0, cell);
|
cells.splice(i, 0, cell);
|
||||||
|
|
||||||
if (!cell.column.get("renderable")) cell.$el.hide();
|
|
||||||
|
|
||||||
var $el = this.$el;
|
var $el = this.$el;
|
||||||
if (i === 0) {
|
if (i === 0) {
|
||||||
$el.prepend(cell.render().$el);
|
$el.prepend(cell.render().$el);
|
||||||
|
@ -1646,11 +1704,8 @@ var Row = Backgrid.Row = Backbone.View.extend({
|
||||||
this.$el.empty();
|
this.$el.empty();
|
||||||
|
|
||||||
var fragment = document.createDocumentFragment();
|
var fragment = document.createDocumentFragment();
|
||||||
|
|
||||||
for (var i = 0; i < this.cells.length; i++) {
|
for (var i = 0; i < this.cells.length; i++) {
|
||||||
var cell = this.cells[i];
|
fragment.appendChild(this.cells[i].render().el);
|
||||||
fragment.appendChild(cell.render().el);
|
|
||||||
if (!cell.column.get("renderable")) cell.$el.hide();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.el.appendChild(fragment);
|
this.el.appendChild(fragment);
|
||||||
|
@ -1766,7 +1821,24 @@ var HeaderCell = Backgrid.HeaderCell = Backbone.View.extend({
|
||||||
if (!(this.column instanceof Column)) {
|
if (!(this.column instanceof Column)) {
|
||||||
this.column = new Column(this.column);
|
this.column = new Column(this.column);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.listenTo(this.collection, "backgrid:sort", this._resetCellDirection);
|
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
|
@private
|
||||||
*/
|
*/
|
||||||
_resetCellDirection: function (sortByColName, direction, comparator, collection) {
|
_resetCellDirection: function (columnToSort, direction, comparator, collection) {
|
||||||
if (collection == this.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);
|
else this.direction(direction);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1808,34 +1880,12 @@ var HeaderCell = Backgrid.HeaderCell = Backbone.View.extend({
|
||||||
onClick: function (e) {
|
onClick: function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
var columnName = this.column.get("name");
|
var column = this.column;
|
||||||
|
var sortable = Backgrid.callByNeed(column.get("sortable"), column, this.model);
|
||||||
if (this.column.get("sortable")) {
|
if (sortable) {
|
||||||
if (this.direction() === "ascending") {
|
if (this.direction() === "ascending") this.sort(column, "descending");
|
||||||
this.sort(columnName, "descending", function (left, right) {
|
else if (this.direction() === "descending") this.sort(column, null);
|
||||||
var leftVal = left.get(columnName);
|
else this.sort(column, "ascending");
|
||||||
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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1852,31 +1902,37 @@ var HeaderCell = Backgrid.HeaderCell = Backbone.View.extend({
|
||||||
and the current page will then be returned.
|
and the current page will then be returned.
|
||||||
|
|
||||||
Triggers a Backbone `backgrid:sort` event from the collection when done
|
Triggers a Backbone `backgrid:sort` event from the collection when done
|
||||||
with the column name, direction, comparator and a reference to the
|
with the column, direction, comparator and a reference to the collection.
|
||||||
collection.
|
|
||||||
|
|
||||||
@param {string} columnName
|
@param {Backgrid.Column} column
|
||||||
@param {null|"ascending"|"descending"} direction
|
@param {null|"ascending"|"descending"} direction
|
||||||
@param {function(*, *): number} [comparator]
|
|
||||||
|
|
||||||
See [Backbone.Collection#comparator](http://backbonejs.org/#Collection-comparator)
|
See [Backbone.Collection#comparator](http://backbonejs.org/#Collection-comparator)
|
||||||
*/
|
*/
|
||||||
sort: function (columnName, direction, comparator) {
|
sort: function (column, direction) {
|
||||||
|
|
||||||
comparator = comparator || this._cidComparator;
|
|
||||||
|
|
||||||
var collection = this.collection;
|
var collection = this.collection;
|
||||||
|
|
||||||
if (Backbone.PageableCollection && collection instanceof Backbone.PageableCollection) {
|
|
||||||
var order;
|
var order;
|
||||||
if (direction === "ascending") order = -1;
|
if (direction === "ascending") order = -1;
|
||||||
else if (direction === "descending") order = 1;
|
else if (direction === "descending") order = 1;
|
||||||
else order = null;
|
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.mode == "client") {
|
||||||
if (!collection.fullCollection.comparator) {
|
if (collection.fullCollection.comparator == null) {
|
||||||
collection.fullCollection.comparator = comparator;
|
collection.fullCollection.comparator = comparator;
|
||||||
}
|
}
|
||||||
collection.fullCollection.sort();
|
collection.fullCollection.sort();
|
||||||
|
@ -1888,26 +1944,24 @@ var HeaderCell = Backgrid.HeaderCell = Backbone.View.extend({
|
||||||
collection.sort();
|
collection.sort();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.collection.trigger("backgrid:sort", columnName, direction, comparator, this.collection);
|
this.collection.trigger("backgrid:sort", column, direction, comparator,
|
||||||
|
this.collection);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
makeComparator: function (attr, order, func) {
|
||||||
Default comparator for Backbone.Collections. Sorts cids in ascending
|
|
||||||
order. The cids of the models are assumed to be in insertion order.
|
|
||||||
|
|
||||||
@private
|
return function (left, right) {
|
||||||
@param {*} left
|
// extract the values from the models
|
||||||
@param {*} right
|
var l = func(left, attr), r = func(right, attr), t;
|
||||||
*/
|
|
||||||
_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 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 () {
|
render: function () {
|
||||||
this.$el.empty();
|
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.$el.append($label);
|
||||||
this.delegateEvents();
|
this.delegateEvents();
|
||||||
return this;
|
return this;
|
||||||
|
@ -2259,6 +2315,9 @@ var Body = Backgrid.Body = Backbone.View.extend({
|
||||||
moveToNextCell: function (model, column, command) {
|
moveToNextCell: function (model, column, command) {
|
||||||
var i = this.collection.indexOf(model);
|
var i = this.collection.indexOf(model);
|
||||||
var j = this.columns.indexOf(column);
|
var j = this.columns.indexOf(column);
|
||||||
|
var cell, renderable, editable;
|
||||||
|
|
||||||
|
this.rows[i].cells[j].exitEditMode();
|
||||||
|
|
||||||
if (command.moveUp() || command.moveDown() || command.moveLeft() ||
|
if (command.moveUp() || command.moveDown() || command.moveLeft() ||
|
||||||
command.moveRight() || command.save()) {
|
command.moveRight() || command.save()) {
|
||||||
|
@ -2267,7 +2326,12 @@ var Body = Backgrid.Body = Backbone.View.extend({
|
||||||
|
|
||||||
if (command.moveUp() || command.moveDown()) {
|
if (command.moveUp() || command.moveDown()) {
|
||||||
var row = this.rows[i + (command.moveUp() ? -1 : 1)];
|
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()) {
|
else if (command.moveLeft() || command.moveRight()) {
|
||||||
var right = command.moveRight();
|
var right = command.moveRight();
|
||||||
|
@ -2276,16 +2340,16 @@ var Body = Backgrid.Body = Backbone.View.extend({
|
||||||
right ? offset++ : offset--) {
|
right ? offset++ : offset--) {
|
||||||
var m = ~~(offset / l);
|
var m = ~~(offset / l);
|
||||||
var n = offset - m * l;
|
var n = offset - m * l;
|
||||||
var cell = this.rows[m].cells[n];
|
cell = this.rows[m].cells[n];
|
||||||
if (cell.column.get("renderable") && cell.column.get("editable")) {
|
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();
|
cell.enterEditMode();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.rows[i].cells[j].exitEditMode();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -6,18 +6,186 @@
|
||||||
Licensed under the MIT @license.
|
Licensed under the MIT @license.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function ($, _, Backbone, Backgrid) {
|
(function (_, Backbone, Backgrid) {
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
PageHandle is a class that renders the actual page handles and reacts to
|
||||||
|
click events for pagination.
|
||||||
|
|
||||||
|
This class acts in two modes - control or discrete page handle modes. If
|
||||||
|
one of the `is*` flags is `true`, an instance of this class is under
|
||||||
|
control page handle mode. Setting a `pageIndex` to an instance of this
|
||||||
|
class under control mode has no effect and the correct page index will
|
||||||
|
always be inferred from the `is*` flag. Only one of the `is*` flags should
|
||||||
|
be set to `true` at a time. For example, an instance of this class cannot
|
||||||
|
simultaneously be a rewind control and a fast forward control. A `label`
|
||||||
|
and a `title` template or a string are required to be passed to the
|
||||||
|
constuctor under this mode. If a `title` template is provided, it __MUST__
|
||||||
|
accept a parameter `label`. When the `label` is provided to the `title`
|
||||||
|
template function, its result will be used to render the generated anchor's
|
||||||
|
title attribute.
|
||||||
|
|
||||||
|
If all of the `is*` flags is set to `false`, which is the default, an
|
||||||
|
instance of this class will be in discrete page handle mode. An instance
|
||||||
|
under this mode requires the `pageIndex` to be passed from the constructor
|
||||||
|
as an option and it __MUST__ be a 0-based index of the list of page numbers
|
||||||
|
to render. The constuctor will normalize the base to the same base the
|
||||||
|
underlying PageableCollection collection instance uses. A `label` is not
|
||||||
|
required under this mode, which will default to the equivalent 1-based page
|
||||||
|
index calculated from `pageIndex` and the underlying PageableCollection
|
||||||
|
instance. A provided `label` will still be honored however. The `title`
|
||||||
|
parameter is also not required under this mode, in which case the default
|
||||||
|
`title` template will be used. You are encouraged to provide your own
|
||||||
|
`title` template however if you wish to localize the title strings.
|
||||||
|
|
||||||
|
If this page handle represents the current page, an `active` class will be
|
||||||
|
placed on the root list element.
|
||||||
|
|
||||||
|
if this page handle is at the border of the list of pages, a `disabled`
|
||||||
|
class will be placed on the root list element.
|
||||||
|
|
||||||
|
Only page handles that are neither `active` nor `disabled` will respond to
|
||||||
|
click events and triggers pagination.
|
||||||
|
|
||||||
|
@class Backgrid.Extension.PageHandle
|
||||||
|
*/
|
||||||
|
var PageHandle = Backgrid.Extension.PageHandle = Backbone.View.extend({
|
||||||
|
|
||||||
|
/** @property */
|
||||||
|
tagName: "li",
|
||||||
|
|
||||||
|
/** @property */
|
||||||
|
events: {
|
||||||
|
"click a": "changePage"
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
@property {string|function(Object.<string, string>): string} title
|
||||||
|
The title to use for the `title` attribute of the generated page handle
|
||||||
|
anchor elements. It can be a string or an Underscore template function
|
||||||
|
that takes a mandatory `label` parameter.
|
||||||
|
*/
|
||||||
|
title: _.template('Page <%- label %>'),
|
||||||
|
|
||||||
|
/**
|
||||||
|
@property {boolean} isRewind Whether this handle represents a rewind
|
||||||
|
control
|
||||||
|
*/
|
||||||
|
isRewind: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
@property {boolean} isBack Whether this handle represents a back
|
||||||
|
control
|
||||||
|
*/
|
||||||
|
isBack: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
@property {boolean} isForward Whether this handle represents a forward
|
||||||
|
control
|
||||||
|
*/
|
||||||
|
isForward: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
@property {boolean} isFastForward Whether this handle represents a fast
|
||||||
|
forward control
|
||||||
|
*/
|
||||||
|
isFastForward: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
Initializer.
|
||||||
|
|
||||||
|
@param {Object} options
|
||||||
|
@param {Backbone.Collection} options.collection
|
||||||
|
@param {number} pageIndex 0-based index of the page number this handle
|
||||||
|
handles. This parameter will be normalized to the base the underlying
|
||||||
|
PageableCollection uses.
|
||||||
|
@param {string} [options.label] If provided it is used to render the
|
||||||
|
anchor text, otherwise the normalized pageIndex will be used
|
||||||
|
instead. Required if any of the `is*` flags is set to `true`.
|
||||||
|
@param {string} [options.title]
|
||||||
|
@param {boolean} [options.isRewind=false]
|
||||||
|
@param {boolean} [options.isBack=false]
|
||||||
|
@param {boolean} [options.isForward=false]
|
||||||
|
@param {boolean} [options.isFastForward=false]
|
||||||
|
*/
|
||||||
|
initialize: function (options) {
|
||||||
|
Backbone.View.prototype.initialize.apply(this, arguments);
|
||||||
|
|
||||||
|
var collection = this.collection;
|
||||||
|
var state = collection.state;
|
||||||
|
var currentPage = state.currentPage;
|
||||||
|
var firstPage = state.firstPage;
|
||||||
|
var lastPage = state.lastPage;
|
||||||
|
|
||||||
|
_.extend(this, _.pick(options,
|
||||||
|
["isRewind", "isBack", "isForward", "isFastForward"]));
|
||||||
|
|
||||||
|
var pageIndex;
|
||||||
|
if (this.isRewind) pageIndex = firstPage;
|
||||||
|
else if (this.isBack) pageIndex = Math.max(firstPage, currentPage - 1);
|
||||||
|
else if (this.isForward) pageIndex = Math.min(lastPage, currentPage + 1);
|
||||||
|
else if (this.isFastForward) pageIndex = lastPage;
|
||||||
|
else {
|
||||||
|
pageIndex = +options.pageIndex;
|
||||||
|
pageIndex = (firstPage ? pageIndex + 1 : pageIndex);
|
||||||
|
}
|
||||||
|
this.pageIndex = pageIndex;
|
||||||
|
|
||||||
|
if (((this.isRewind || this.isBack) && currentPage == firstPage) ||
|
||||||
|
((this.isForward || this.isFastForward) && currentPage == lastPage)) {
|
||||||
|
this.$el.addClass("disabled");
|
||||||
|
}
|
||||||
|
else if (!(this.isRewind ||
|
||||||
|
this.isBack ||
|
||||||
|
this.isForward ||
|
||||||
|
this.isFastForward) &&
|
||||||
|
currentPage == pageIndex) {
|
||||||
|
this.$el.addClass("active");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.label = (options.label || (firstPage ? pageIndex : pageIndex + 1)) + '';
|
||||||
|
var title = options.title || this.title;
|
||||||
|
this.title = _.isFunction(title) ? title({label: this.label}) : title;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
Renders a clickable anchor element under a list item.
|
||||||
|
*/
|
||||||
|
render: function () {
|
||||||
|
this.$el.empty();
|
||||||
|
var anchor = document.createElement("a");
|
||||||
|
anchor.href = '#';
|
||||||
|
if (this.title) anchor.title = this.title;
|
||||||
|
anchor.innerHTML = this.label;
|
||||||
|
this.el.appendChild(anchor);
|
||||||
|
this.delegateEvents();
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
jQuery click event handler. Goes to the page this PageHandle instance
|
||||||
|
represents. No-op if this page handle is currently active or disabled.
|
||||||
|
*/
|
||||||
|
changePage: function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var $el = this.$el;
|
||||||
|
if (!$el.hasClass("active") && !$el.hasClass("disabled")) {
|
||||||
|
this.collection.getPage(this.pageIndex);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Paginator is a Backgrid extension that renders a series of configurable
|
Paginator is a Backgrid extension that renders a series of configurable
|
||||||
pagination handles. This extension is best used for splitting a large data
|
pagination handles. This extension is best used for splitting a large data
|
||||||
set across multiple pages. If the number of pages is larger then a
|
set across multiple pages. If the number of pages is larger then a
|
||||||
threshold, which is set to 10 by default, the page handles are rendered
|
threshold, which is set to 10 by default, the page handles are rendered
|
||||||
within a sliding window, plus the fast forward, fast backward, previous and
|
within a sliding window, plus the rewind, back, forward and fast forward
|
||||||
next page handles. The fast forward, fast backward, previous and next page
|
control handles. The individual control handles can be turned off.
|
||||||
handles can be turned off.
|
|
||||||
|
|
||||||
@class Backgrid.Extension.Paginator
|
@class Backgrid.Extension.Paginator
|
||||||
*/
|
*/
|
||||||
|
@ -30,97 +198,65 @@
|
||||||
windowSize: 10,
|
windowSize: 10,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@property {Object} fastForwardHandleLabels You can disable specific
|
@property {Object.<string, Object.<string, string>>} controls You can
|
||||||
handles by setting its value to `null`.
|
disable specific control handles by omitting certain keys.
|
||||||
*/
|
*/
|
||||||
fastForwardHandleLabels: {
|
controls: {
|
||||||
first: "《",
|
rewind: {
|
||||||
prev: "〈",
|
label: "《",
|
||||||
next: "〉",
|
title: "First"
|
||||||
last: "》"
|
},
|
||||||
|
back: {
|
||||||
|
label: "〈",
|
||||||
|
title: "Previous"
|
||||||
|
},
|
||||||
|
forward: {
|
||||||
|
label: "〉",
|
||||||
|
title: "Next"
|
||||||
|
},
|
||||||
|
fastForward: {
|
||||||
|
label: "》",
|
||||||
|
title: "Last"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/** @property */
|
/**
|
||||||
template: _.template('<ul><% _.each(handles, function (handle) { %><li <% if (handle.className) { %>class="<%= handle.className %>"<% } %>><a href="#" <% if (handle.title) {%> title="<%= handle.title %>"<% } %>><%= handle.label %></a></li><% }); %></ul>'),
|
@property {Backgrid.Extension.PageHandle} pageHandle. The PageHandle
|
||||||
|
class to use for rendering individual handles
|
||||||
|
*/
|
||||||
|
pageHandle: PageHandle,
|
||||||
|
|
||||||
/** @property */
|
/** @property */
|
||||||
events: {
|
goBackFirstOnSort: true,
|
||||||
"click a": "changePage"
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Initializer.
|
Initializer.
|
||||||
|
|
||||||
@param {Object} options
|
@param {Object} options
|
||||||
@param {Backbone.Collection} options.collection
|
@param {Backbone.Collection} options.collection
|
||||||
@param {boolean} [options.fastForwardHandleLabels] Whether to render fast forward buttons.
|
@param {boolean} [options.controls]
|
||||||
|
@param {boolean} [options.pageHandle=Backgrid.Extension.PageHandle]
|
||||||
|
@param {boolean} [options.goBackFirstOnSort=true]
|
||||||
*/
|
*/
|
||||||
initialize: function (options) {
|
initialize: function (options) {
|
||||||
Backgrid.requireOptions(options, ["collection"]);
|
Backgrid.requireOptions(options, ["collection"]);
|
||||||
|
|
||||||
|
this.controls = options.controls || this.controls;
|
||||||
|
this.pageHandle = options.pageHandle || this.pageHandle;
|
||||||
|
|
||||||
var collection = this.collection;
|
var collection = this.collection;
|
||||||
var fullCollection = collection.fullCollection;
|
|
||||||
if (fullCollection) {
|
|
||||||
this.listenTo(fullCollection, "add", this.render);
|
|
||||||
this.listenTo(fullCollection, "remove", this.render);
|
|
||||||
this.listenTo(fullCollection, "reset", this.render);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.listenTo(collection, "add", this.render);
|
this.listenTo(collection, "add", this.render);
|
||||||
this.listenTo(collection, "remove", this.render);
|
this.listenTo(collection, "remove", this.render);
|
||||||
this.listenTo(collection, "reset", this.render);
|
this.listenTo(collection, "reset", this.render);
|
||||||
}
|
if ((options.goBackFirstOnSort || this.goBackFirstOnSort) &&
|
||||||
},
|
collection.fullCollection) {
|
||||||
|
this.listenTo(collection.fullCollection, "sort", function () {
|
||||||
/**
|
|
||||||
jQuery event handler for the page handlers. Goes to the right page upon
|
|
||||||
clicking.
|
|
||||||
|
|
||||||
@param {Event} e
|
|
||||||
*/
|
|
||||||
changePage: function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
var $li = $(e.target).parent();
|
|
||||||
if (!$li.hasClass("active") && !$li.hasClass("disabled")) {
|
|
||||||
|
|
||||||
var label = $(e.target).text();
|
|
||||||
var ffLabels = this.fastForwardHandleLabels;
|
|
||||||
|
|
||||||
var collection = this.collection;
|
|
||||||
|
|
||||||
if (ffLabels) {
|
|
||||||
switch (label) {
|
|
||||||
case ffLabels.first:
|
|
||||||
collection.getFirstPage();
|
collection.getFirstPage();
|
||||||
return;
|
});
|
||||||
case ffLabels.prev:
|
|
||||||
collection.getPreviousPage();
|
|
||||||
return;
|
|
||||||
case ffLabels.next:
|
|
||||||
collection.getNextPage();
|
|
||||||
return;
|
|
||||||
case ffLabels.last:
|
|
||||||
collection.getLastPage();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var state = collection.state;
|
|
||||||
var pageIndex = +label;
|
|
||||||
collection.getPage(state.firstPage === 0 ? pageIndex - 1 : pageIndex);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
_calculateWindow: function () {
|
||||||
Internal method to create a list of page handle objects for the template
|
|
||||||
to render them.
|
|
||||||
|
|
||||||
@return {Array.<Object>} an array of page handle objects hashes
|
|
||||||
*/
|
|
||||||
makeHandles: function () {
|
|
||||||
|
|
||||||
var handles = [];
|
|
||||||
var collection = this.collection;
|
var collection = this.collection;
|
||||||
var state = collection.state;
|
var state = collection.state;
|
||||||
|
|
||||||
|
@ -132,48 +268,44 @@
|
||||||
currentPage = firstPage ? currentPage - 1 : currentPage;
|
currentPage = firstPage ? currentPage - 1 : currentPage;
|
||||||
var windowStart = Math.floor(currentPage / this.windowSize) * this.windowSize;
|
var windowStart = Math.floor(currentPage / this.windowSize) * this.windowSize;
|
||||||
var windowEnd = Math.min(lastPage + 1, windowStart + this.windowSize);
|
var windowEnd = Math.min(lastPage + 1, windowStart + this.windowSize);
|
||||||
|
return [windowStart, windowEnd];
|
||||||
|
},
|
||||||
|
|
||||||
if (collection.mode !== "infinite") {
|
/**
|
||||||
for (var i = windowStart; i < windowEnd; i++) {
|
Creates a list of page handle objects for rendering.
|
||||||
handles.push({
|
|
||||||
label: i + 1,
|
@return {Array.<Object>} an array of page handle objects hashes
|
||||||
title: "No. " + (i + 1),
|
*/
|
||||||
className: currentPage === i ? "active" : undefined
|
makeHandles: function () {
|
||||||
|
|
||||||
|
var handles = [];
|
||||||
|
var collection = this.collection;
|
||||||
|
|
||||||
|
var window = this._calculateWindow();
|
||||||
|
var winStart = window[0], winEnd = window[1];
|
||||||
|
|
||||||
|
for (var i = winStart; i < winEnd; i++) {
|
||||||
|
handles.push(new PageHandle({
|
||||||
|
collection: collection,
|
||||||
|
pageIndex: i
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
var controls = this.controls;
|
||||||
|
_.each(["back", "rewind", "forward", "fastForward"], function (key) {
|
||||||
|
var value = controls[key];
|
||||||
|
if (value) {
|
||||||
|
var handleCtorOpts = {
|
||||||
|
collection: collection,
|
||||||
|
title: value.title,
|
||||||
|
label: value.label
|
||||||
|
};
|
||||||
|
handleCtorOpts["is" + key.slice(0, 1).toUpperCase() + key.slice(1)] = true;
|
||||||
|
var handle = new PageHandle(handleCtorOpts);
|
||||||
|
if (key == "rewind" || key == "back") handles.unshift(handle);
|
||||||
|
else handles.push(handle);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var ffLabels = this.fastForwardHandleLabels;
|
|
||||||
if (ffLabels) {
|
|
||||||
|
|
||||||
if (ffLabels.prev) {
|
|
||||||
handles.unshift({
|
|
||||||
label: ffLabels.prev,
|
|
||||||
className: collection.hasPrevious() ? void 0 : "disabled"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ffLabels.first) {
|
|
||||||
handles.unshift({
|
|
||||||
label: ffLabels.first,
|
|
||||||
className: collection.hasPrevious() ? void 0 : "disabled"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ffLabels.next) {
|
|
||||||
handles.push({
|
|
||||||
label: ffLabels.next,
|
|
||||||
className: collection.hasNext() ? void 0 : "disabled"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ffLabels.last) {
|
|
||||||
handles.push({
|
|
||||||
label: ffLabels.last,
|
|
||||||
className: collection.hasNext() ? void 0 : "disabled"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return handles;
|
return handles;
|
||||||
},
|
},
|
||||||
|
@ -184,15 +316,24 @@
|
||||||
render: function () {
|
render: function () {
|
||||||
this.$el.empty();
|
this.$el.empty();
|
||||||
|
|
||||||
this.$el.append(this.template({
|
if (this.handles) {
|
||||||
handles: this.makeHandles()
|
for (var i = 0, l = this.handles.length; i < l; i++) {
|
||||||
}));
|
this.handles[i].remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.delegateEvents();
|
var handles = this.handles = this.makeHandles();
|
||||||
|
|
||||||
|
var ul = document.createElement("ul");
|
||||||
|
for (var i = 0; i < handles.length; i++) {
|
||||||
|
ul.appendChild(handles[i].render().el);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.el.appendChild(ul);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}(jQuery, _, Backbone, Backgrid));
|
}(_, Backbone, Backgrid));
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
backbone-pageable 1.3.0
|
backbone-pageable 1.3.1
|
||||||
http://github.com/wyuenho/backbone-pageable
|
http://github.com/wyuenho/backbone-pageable
|
||||||
|
|
||||||
Copyright (c) 2013 Jimmy Yuen Ho Wong
|
Copyright (c) 2013 Jimmy Yuen Ho Wong
|
||||||
|
@ -574,6 +574,17 @@
|
||||||
/**
|
/**
|
||||||
Change the page size of this collection.
|
Change the page size of this collection.
|
||||||
|
|
||||||
|
Under most if not all circumstances, you should call this method to
|
||||||
|
change the page size of a pageable collection because it will keep the
|
||||||
|
pagination state sane. By default, the method will recalculate the
|
||||||
|
current page number to one that will retain the current page's models
|
||||||
|
when increasing the page size. When decreasing the page size, this method
|
||||||
|
will retain the last models to the current page that will fit into the
|
||||||
|
smaller page size.
|
||||||
|
|
||||||
|
If `options.first` is true, changing the page size will also reset the
|
||||||
|
current page back to the first page instead of trying to be smart.
|
||||||
|
|
||||||
For server mode operations, changing the page size will trigger a #fetch
|
For server mode operations, changing the page size will trigger a #fetch
|
||||||
and subsequently a `reset` event.
|
and subsequently a `reset` event.
|
||||||
|
|
||||||
|
@ -586,6 +597,8 @@
|
||||||
|
|
||||||
@param {number} pageSize The new page size to set to #state.
|
@param {number} pageSize The new page size to set to #state.
|
||||||
@param {Object} [options] {@link #fetch} options.
|
@param {Object} [options] {@link #fetch} options.
|
||||||
|
@param {boolean} [options.first=false] Reset the current page number to
|
||||||
|
the first page if `true`.
|
||||||
@param {boolean} [options.fetch] If `true`, force a fetch in client mode.
|
@param {boolean} [options.fetch] If `true`, force a fetch in client mode.
|
||||||
|
|
||||||
@throws {TypeError} If `pageSize` is not a finite integer.
|
@throws {TypeError} If `pageSize` is not a finite integer.
|
||||||
|
@ -598,14 +611,24 @@
|
||||||
setPageSize: function (pageSize, options) {
|
setPageSize: function (pageSize, options) {
|
||||||
pageSize = finiteInt(pageSize, "pageSize");
|
pageSize = finiteInt(pageSize, "pageSize");
|
||||||
|
|
||||||
options = options || {};
|
options = options || {first: false};
|
||||||
|
|
||||||
this.state = this._checkState(_extend({}, this.state, {
|
var state = this.state;
|
||||||
|
var totalPages = ceil(state.totalRecords / pageSize);
|
||||||
|
var currentPage = max(state.firstPage,
|
||||||
|
floor(totalPages *
|
||||||
|
(state.firstPage ?
|
||||||
|
state.currentPage :
|
||||||
|
state.currentPage + 1) /
|
||||||
|
state.totalPages));
|
||||||
|
|
||||||
|
state = this.state = this._checkState(_extend({}, state, {
|
||||||
pageSize: pageSize,
|
pageSize: pageSize,
|
||||||
totalPages: ceil(this.state.totalRecords / pageSize)
|
currentPage: options.first ? state.firstPage : currentPage,
|
||||||
|
totalPages: totalPages
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return this.getPage(this.state.currentPage, options);
|
return this.getPage(state.currentPage, _omit(options, ["first"]));
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -992,13 +1015,14 @@
|
||||||
encouraged to override #parseState and #parseRecords instead.
|
encouraged to override #parseState and #parseRecords instead.
|
||||||
|
|
||||||
@param {Object} resp The deserialized response data from the server.
|
@param {Object} resp The deserialized response data from the server.
|
||||||
|
@param {Object} the options for the ajax request
|
||||||
|
|
||||||
@return {Array.<Object>} An array of model objects
|
@return {Array.<Object>} An array of model objects
|
||||||
*/
|
*/
|
||||||
parse: function (resp) {
|
parse: function (resp, options) {
|
||||||
var newState = this.parseState(resp, _clone(this.queryParams), _clone(this.state));
|
var newState = this.parseState(resp, _clone(this.queryParams), _clone(this.state), options);
|
||||||
if (newState) this.state = this._checkState(_extend({}, this.state, newState));
|
if (newState) this.state = this._checkState(_extend({}, this.state, newState));
|
||||||
return this.parseRecords(resp);
|
return this.parseRecords(resp, options);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1016,10 +1040,16 @@
|
||||||
`totalRecords` value is enough to trigger a full pagination state
|
`totalRecords` value is enough to trigger a full pagination state
|
||||||
recalculation.
|
recalculation.
|
||||||
|
|
||||||
parseState: function (resp, queryParams, state) {
|
parseState: function (resp, queryParams, state, options) {
|
||||||
return {totalRecords: resp.total_entries};
|
return {totalRecords: resp.total_entries};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
If you want to use header fields use:
|
||||||
|
|
||||||
|
parseState: function (resp, queryParams, state, options) {
|
||||||
|
return {totalRecords: options.xhr.getResponseHeader("X-total")};
|
||||||
|
}
|
||||||
|
|
||||||
This method __MUST__ return a new state object instead of directly
|
This method __MUST__ return a new state object instead of directly
|
||||||
modifying the #state object. The behavior of directly modifying #state is
|
modifying the #state object. The behavior of directly modifying #state is
|
||||||
undefined.
|
undefined.
|
||||||
|
@ -1027,10 +1057,12 @@
|
||||||
@param {Object} resp The deserialized response data from the server.
|
@param {Object} resp The deserialized response data from the server.
|
||||||
@param {Object} queryParams A copy of #queryParams.
|
@param {Object} queryParams A copy of #queryParams.
|
||||||
@param {Object} state A copy of #state.
|
@param {Object} state A copy of #state.
|
||||||
|
@param {Object} [options] The options passed through from
|
||||||
|
`parse`. (backbone >= 0.9.10 only)
|
||||||
|
|
||||||
@return {Object} A new (partial) state object.
|
@return {Object} A new (partial) state object.
|
||||||
*/
|
*/
|
||||||
parseState: function (resp, queryParams, state) {
|
parseState: function (resp, queryParams, state, options) {
|
||||||
if (resp && resp.length === 2 && _isObject(resp[0]) && _isArray(resp[1])) {
|
if (resp && resp.length === 2 && _isObject(resp[0]) && _isArray(resp[1])) {
|
||||||
|
|
||||||
var newState = _clone(state);
|
var newState = _clone(state);
|
||||||
|
@ -1059,10 +1091,12 @@
|
||||||
response is returned directly.
|
response is returned directly.
|
||||||
|
|
||||||
@param {Object} resp The deserialized response data from the server.
|
@param {Object} resp The deserialized response data from the server.
|
||||||
|
@param {Object} [options] The options passed through from the
|
||||||
|
`parse`. (backbone >= 0.9.10 only)
|
||||||
|
|
||||||
@return {Array.<Object>} An array of model objects
|
@return {Array.<Object>} An array of model objects
|
||||||
*/
|
*/
|
||||||
parseRecords: function (resp) {
|
parseRecords: function (resp, options) {
|
||||||
if (resp && resp.length === 2 && _isObject(resp[0]) && _isArray(resp[1])) {
|
if (resp && resp.length === 2 && _isObject(resp[0]) && _isArray(resp[1])) {
|
||||||
return resp[1];
|
return resp[1];
|
||||||
}
|
}
|
||||||
|
@ -1138,7 +1172,7 @@
|
||||||
kvp = extraKvps[i];
|
kvp = extraKvps[i];
|
||||||
v = kvp[1];
|
v = kvp[1];
|
||||||
v = _isFunction(v) ? v.call(thisCopy) : v;
|
v = _isFunction(v) ? v.call(thisCopy) : v;
|
||||||
data[kvp[0]] = v;
|
if (v != null) data[kvp[0]] = v;
|
||||||
}
|
}
|
||||||
|
|
||||||
var fullCol = this.fullCollection, links = this.links;
|
var fullCol = this.fullCollection, links = this.links;
|
||||||
|
@ -1212,11 +1246,11 @@
|
||||||
|
|
||||||
@param {string} [sortKey=this.state.sortKey] See `state.sortKey`.
|
@param {string} [sortKey=this.state.sortKey] See `state.sortKey`.
|
||||||
@param {number} [order=this.state.order] See `state.order`.
|
@param {number} [order=this.state.order] See `state.order`.
|
||||||
|
@param {(function(Backbone.Model, string): Object) | string} [sortValue] See #setSorting.
|
||||||
|
|
||||||
See [Backbone.Collection.comparator](http://backbonejs.org/#Collection-comparator).
|
See [Backbone.Collection.comparator](http://backbonejs.org/#Collection-comparator).
|
||||||
*/
|
*/
|
||||||
_makeComparator: function (sortKey, order) {
|
_makeComparator: function (sortKey, order, sortValue) {
|
||||||
|
|
||||||
var state = this.state;
|
var state = this.state;
|
||||||
|
|
||||||
sortKey = sortKey || state.sortKey;
|
sortKey = sortKey || state.sortKey;
|
||||||
|
@ -1224,8 +1258,12 @@
|
||||||
|
|
||||||
if (!sortKey || !order) return;
|
if (!sortKey || !order) return;
|
||||||
|
|
||||||
|
if (!sortValue) sortValue = function (model, attr) {
|
||||||
|
return model.get(attr);
|
||||||
|
};
|
||||||
|
|
||||||
return function (left, right) {
|
return function (left, right) {
|
||||||
var l = left.get(sortKey), r = right.get(sortKey), t;
|
var l = sortValue(left, sortKey), r = sortValue(right, sortKey), t;
|
||||||
if (order === 1) t = l, l = r, r = t;
|
if (order === 1) t = l, l = r, r = t;
|
||||||
if (l === r) return 0;
|
if (l === r) return 0;
|
||||||
else if (l < r) return -1;
|
else if (l < r) return -1;
|
||||||
|
@ -1244,6 +1282,11 @@
|
||||||
`sortKey` to `null` removes the comparator from both the current page and
|
`sortKey` to `null` removes the comparator from both the current page and
|
||||||
the full collection.
|
the full collection.
|
||||||
|
|
||||||
|
If a `sortValue` function is given, it will be passed the `(model,
|
||||||
|
sortKey)` arguments and is used to extract a value from the model during
|
||||||
|
comparison sorts. If `sortValue` is not given, `model.get(sortKey)` is
|
||||||
|
used for sorting.
|
||||||
|
|
||||||
@chainable
|
@chainable
|
||||||
|
|
||||||
@param {string} sortKey See `state.sortKey`.
|
@param {string} sortKey See `state.sortKey`.
|
||||||
|
@ -1252,6 +1295,7 @@
|
||||||
@param {"server"|"client"} [options.side] By default, `"client"` if
|
@param {"server"|"client"} [options.side] By default, `"client"` if
|
||||||
`mode` is `"client"`, `"server"` otherwise.
|
`mode` is `"client"`, `"server"` otherwise.
|
||||||
@param {boolean} [options.full=true]
|
@param {boolean} [options.full=true]
|
||||||
|
@param {(function(Backbone.Model, string): Object) | string} [options.sortValue]
|
||||||
*/
|
*/
|
||||||
setSorting: function (sortKey, order, options) {
|
setSorting: function (sortKey, order, options) {
|
||||||
|
|
||||||
|
@ -1270,7 +1314,7 @@
|
||||||
options = _extend({side: mode == "client" ? mode : "server", full: true},
|
options = _extend({side: mode == "client" ? mode : "server", full: true},
|
||||||
options);
|
options);
|
||||||
|
|
||||||
var comparator = this._makeComparator(sortKey, order);
|
var comparator = this._makeComparator(sortKey, order, options.sortValue);
|
||||||
|
|
||||||
var full = options.full, side = options.side;
|
var full = options.full, side = options.side;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue