From b81b39550faa0c8fec321266efddf848daad5a7c Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Thu, 27 Jun 2013 00:23:00 -0700 Subject: [PATCH] Revert "updated backgrid" Also fixed his naive capitalization... This reverts commit 8fd7def9dd9212b2b17950c43320246e194181e7. --- UI/JsLibraries/backbone.backgrid.filter.js | 91 ++--- UI/JsLibraries/backbone.backgrid.js | 264 +++++------- UI/JsLibraries/backbone.backgrid.paginator.js | 379 ++++++------------ UI/JsLibraries/backbone.pageable.js | 76 +--- 4 files changed, 267 insertions(+), 543 deletions(-) diff --git a/UI/JsLibraries/backbone.backgrid.filter.js b/UI/JsLibraries/backbone.backgrid.filter.js index 17449a53c..abc95a386 100644 --- a/UI/JsLibraries/backbone.backgrid.filter.js +++ b/UI/JsLibraries/backbone.backgrid.filter.js @@ -6,12 +6,10 @@ Licensed under the MIT @license. */ -(function (root) { +(function ($, _, Backbone, Backgrid, lunr) { "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 for filtering the current collection. @@ -38,17 +36,14 @@ /** @property {string} [name='q'] Query key */ name: "q", - /** - @property {string} [placeholder] The HTML5 placeholder to appear beneath - the search box. - */ + /** @property The HTML5 placeholder to appear beneath the search box. */ placeholder: null, /** @param {Object} options @param {Backbone.Collection} options.collection - @param {string} [options.name] - @param {string} [options.placeholder] + @param {String} [options.name] + @param {String} [options.placeholder] */ initialize: function (options) { Backgrid.requireOptions(options, ["collection"]); @@ -56,21 +51,16 @@ this.name = options.name || this.name; this.placeholder = options.placeholder || this.placeholder; - // Persist the query on pagination var collection = this.collection, self = this; if (Backbone.PageableCollection && collection instanceof Backbone.PageableCollection && collection.mode == "server") { collection.queryParams[this.name] = function () { - return self.searchBox().val() || null; + return self.$el.find("input[type=text]").val(); }; } }, - searchBox: function () { - return this.$el.find("input[type=text]"); - }, - /** Upon search form submission, this event handler constructs a query parameter object and pass it to Collection#fetch for server-side @@ -78,22 +68,9 @@ */ search: function (e) { if (e) e.preventDefault(); - var 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}); + data[this.name] = this.$el.find("input[type=text]").val(); + this.collection.fetch({data: data}); }, /** @@ -102,8 +79,8 @@ */ clear: function (e) { if (e) e.preventDefault(); - this.searchBox().val(null); - this.collection.fetch({reset: true}); + this.$("input[type=text]").val(null); + this.collection.fetch(); }, /** @@ -138,7 +115,8 @@ e.preventDefault(); this.clear(); }, - "keydown input[type=text]": "search", + "change input[type=text]": "search", + "keyup input[type=text]": "search", "submit": function (e) { e.preventDefault(); this.search(); @@ -146,14 +124,14 @@ }, /** - @property {?Array.} [fields] A list of model field names to - search for matches. If null, all of the fields will be searched. + @property {?Array.} A list of model field names to search + for matches. If null, all of the fields will be searched. */ fields: null, /** - @property [wait=149] The time in milliseconds to wait since for since the - last change to the search box's value before searching. This value can be + @property wait The time in milliseconds to wait since for since the 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 search index is. */ @@ -165,9 +143,9 @@ @param {Object} options @param {Backbone.Collection} options.collection - @param {string} [options.placeholder] - @param {string} [options.fields] - @param {string} [options.wait=149] + @param {String} [options.placeholder] + @param {String} [options.fields] + @param {String} [options.wait=149] */ initialize: function (options) { ServerSideFilter.prototype.initialize.apply(this, arguments); @@ -177,8 +155,11 @@ this._debounceMethods(["search", "clear"]); - var collection = this.collection = this.collection.fullCollection || this.collection; + var collection = this.collection; 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) { shadowCollection.add(model, options); @@ -186,15 +167,9 @@ this.listenTo(collection, "remove", function (model, collection, options) { shadowCollection.remove(model, options); }); - this.listenTo(collection, "sort", function (col) { - if (!this.searchBox().val()) shadowCollection.reset(col.models); - }); - this.listenTo(collection, "reset", function (col, options) { + this.listenTo(collection, "sort reset", function (collection, options) { options = _.extend({reindex: true}, options || {}); - if (options.reindex && col === collection && - options.from == null && options.to == null) { - shadowCollection.reset(col.models); - } + if (options.reindex) shadowCollection.reset(collection.models); }); }, @@ -243,9 +218,7 @@ when all the matches have been found. */ search: function () { - var matcher = _.bind(this.makeMatcher(this.searchBox().val()), this); - var col = this.collection; - if (col.pageableCollection) col.pageableCollection.getFirstPage({silent: true}); + var matcher = _.bind(this.makeMatcher(this.$("input[type=text]").val()), this); this.collection.reset(this.shadowCollection.filter(matcher), {reindex: false}); }, @@ -253,7 +226,7 @@ Clears the search box and reset the collection to its original. */ clear: function () { - this.searchBox().val(null); + this.$("input[type=text]").val(null); this.collection.reset(this.shadowCollection.models, {reindex: false}); } @@ -289,7 +262,7 @@ @param {Object} options @param {Backbone.Collection} options.collection - @param {string} [options.placeholder] + @param {String} [options.placeholder] @param {string} [options.ref] `lunrjs` document reference attribute name. @param {Object} [options.fields] A hash of `lunrjs` index field names and boost value. @@ -300,7 +273,7 @@ this.ref = options.ref || this.ref; - var collection = this.collection = this.collection.fullCollection || this.collection; + var collection = this.collection; this.listenTo(collection, "add", this.addToIndex); this.listenTo(collection, "remove", this.removeFromIndex); this.listenTo(collection, "reset", this.resetIndex); @@ -378,17 +351,15 @@ query answer. */ search: function () { - var searchResults = this.index.search(this.searchBox().val()); + var searchResults = this.index.search(this.$("input[type=text]").val()); var models = []; for (var i = 0; i < searchResults.length; i++) { var result = searchResults[i]; models.push(this.shadowCollection.get(result.ref)); } - var col = this.collection; - if (col.pageableCollection) col.pageableCollection.getFirstPage({silent: true}); - col.reset(models, {reindex: false}); + this.collection.reset(models, {reindex: false}); } }); -}(this)); +}(jQuery, _, Backbone, Backgrid, lunr)); diff --git a/UI/JsLibraries/backbone.backgrid.js b/UI/JsLibraries/backbone.backgrid.js index 57462660f..94932e472 100644 --- a/UI/JsLibraries/backbone.backgrid.js +++ b/UI/JsLibraries/backbone.backgrid.js @@ -41,6 +41,12 @@ if (!String.prototype.trim || ws.trim()) { }; } +function capitalize(s) { + return s.charAt(0).toUpperCase() + s.slice(1); + +// 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; @@ -68,9 +74,7 @@ var Backgrid = root.Backgrid = { resolveNameToClass: function (name, suffix) { if (_.isString(name)) { - var key = _.map(name.split('-'), function (e) { - return e.slice(0, 1).toUpperCase() + e.slice(1); - }).join('') + suffix; + var key = _.map(name.split('-'), function (e) { return capitalize(e); }).join('') + suffix; var klass = Backgrid[key] || Backgrid.Extension[key]; if (_.isUndefined(klass)) { throw new ReferenceError("Class '" + key + "' not found"); @@ -79,17 +83,7 @@ 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); @@ -107,7 +101,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, @@ -745,33 +739,12 @@ var Cell = Backgrid.Cell = Backbone.View.extend({ if (!(this.column instanceof Column)) { this.column = new Column(this.column); } - - var column = this.column, model = this.model, $el = this.$el; - - this.formatter = Backgrid.resolveNameToClass(column.get("formatter") || - this.formatter, "Formatter"); - + this.formatter = Backgrid.resolveNameToClass(this.column.get("formatter") || this.formatter, "Formatter"); this.editor = Backgrid.resolveNameToClass(this.editor, "CellEditor"); - - this.listenTo(model, "change:" + column.get("name"), function () { - if (!$el.hasClass("editor")) this.render(); + this.listenTo(this.model, "change:" + this.column.get("name"), function () { + if (!this.$el.hasClass("editor")) this.render(); }); - - 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"); + this.listenTo(this.model, "backgrid:error", this.renderError); }, /** @@ -808,8 +781,7 @@ var Cell = Backgrid.Cell = Backbone.View.extend({ var model = this.model; var column = this.column; - var editable = Backgrid.callByNeed(column.get("editable"), column, model); - if (editable) { + if (column.get("editable")) { this.currentEditor = new this.editor({ column: this.column, @@ -858,7 +830,7 @@ var Cell = Backgrid.Cell = Backbone.View.extend({ */ remove: function () { if (this.currentEditor) { - this.currentEditor.remove.apply(this.currentEditor, arguments); + this.currentEditor.remove.apply(this, arguments); delete this.currentEditor; } return Backbone.View.prototype.remove.apply(this, arguments); @@ -1513,7 +1485,6 @@ var Column = Backgrid.Column = Backbone.Model.extend({ editable: true, renderable: true, formatter: undefined, - sortValue: undefined, cell: undefined, headerCell: undefined }, @@ -1522,36 +1493,22 @@ 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|string} [attrs.sortable=true] - - @param {boolean|string} [attrs.editable=true] - - @param {boolean|string} [attrs.renderable=true] - - @param {Backgrid.CellFormatter | Object | string} [attrs.formatter] The + @param {boolean} [attrs.sortable=true] + @param {boolean} [attrs.editable=true] + @param {boolean} [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 formatter is a string but a formatter class of + @throws {ReferenceError} If attrs.cell is a string but a cell class of said name cannot be found in the Backgrid module. See: @@ -1567,32 +1524,8 @@ var Column = Backgrid.Column = Backbone.Model.extend({ } var headerCell = Backgrid.resolveNameToClass(this.get("headerCell"), "HeaderCell"); - var cell = Backgrid.resolveNameToClass(this.get("cell"), "Cell"); - - 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 }); + this.set({ cell: cell, headerCell: headerCell }, { silent: true }); } }); @@ -1656,11 +1589,22 @@ 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); @@ -1704,8 +1648,11 @@ var Row = Backgrid.Row = Backbone.View.extend({ this.$el.empty(); var fragment = document.createDocumentFragment(); + for (var i = 0; i < this.cells.length; i++) { - fragment.appendChild(this.cells[i].render().el); + var cell = this.cells[i]; + fragment.appendChild(cell.render().el); + if (!cell.column.get("renderable")) cell.$el.hide(); } this.el.appendChild(fragment); @@ -1821,24 +1768,7 @@ 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"); }, /** @@ -1865,9 +1795,9 @@ var HeaderCell = Backgrid.HeaderCell = Backbone.View.extend({ @private */ - _resetCellDirection: function (columnToSort, direction, comparator, collection) { + _resetCellDirection: function (sortByColName, direction, comparator, collection) { if (collection == this.collection) { - if (columnToSort !== this.column) this.direction(null); + if (sortByColName !== this.column.get("name")) this.direction(null); else this.direction(direction); } }, @@ -1880,12 +1810,34 @@ var HeaderCell = Backgrid.HeaderCell = Backbone.View.extend({ onClick: function (e) { e.preventDefault(); - 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"); + 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; + }); + } } }, @@ -1902,37 +1854,31 @@ 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, direction, comparator and a reference to the collection. + with the column name, direction, comparator and a reference to the + collection. - @param {Backgrid.Column} column + @param {string} columnName @param {null|"ascending"|"descending"} direction + @param {function(*, *): number} [comparator] See [Backbone.Collection#comparator](http://backbonejs.org/#Collection-comparator) */ - sort: function (column, direction) { + sort: function (columnName, direction, comparator) { + + comparator = comparator || this._cidComparator; var collection = this.collection; - var order; - if (direction === "ascending") order = -1; - else if (direction === "descending") order = 1; - else order = null; + if (Backbone.PageableCollection && collection instanceof Backbone.PageableCollection) { + var order; + if (direction === "ascending") order = -1; + else if (direction === "descending") order = 1; + else order = null; - 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")}); + collection.setSorting(order ? columnName : null, order); if (collection.mode == "client") { - if (collection.fullCollection.comparator == null) { + if (!collection.fullCollection.comparator) { collection.fullCollection.comparator = comparator; } collection.fullCollection.sort(); @@ -1944,24 +1890,26 @@ var HeaderCell = Backgrid.HeaderCell = Backbone.View.extend({ collection.sort(); } - this.collection.trigger("backgrid:sort", column, direction, comparator, - this.collection); + this.collection.trigger("backgrid:sort", columnName, 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. - return function (left, right) { - // extract the values from the models - var l = func(left, attr), r = func(right, attr), t; + @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; + } - // 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; - }; + return 0; }, /** @@ -1969,9 +1917,7 @@ var HeaderCell = Backgrid.HeaderCell = Backbone.View.extend({ */ render: function () { this.$el.empty(); - var $label = $("").text(this.column.get("label")); - var sortable = Backgrid.callByNeed(this.column.get("sortable"), this.column, this.model); - if (sortable) $label.append(""); + var $label = $("").text(this.column.get("label")).append(""); this.$el.append($label); this.delegateEvents(); return this; @@ -2315,9 +2261,6 @@ 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()) { @@ -2326,12 +2269,7 @@ var Body = Backgrid.Body = Backbone.View.extend({ 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.get("editable"), cell.column, model)) { - cell.enterEditMode(); - } - } + if (row) row.cells[j].enterEditMode(); } else if (command.moveLeft() || command.moveRight()) { var right = command.moveRight(); @@ -2340,16 +2278,16 @@ var Body = Backgrid.Body = Backbone.View.extend({ right ? offset++ : offset--) { var m = ~~(offset / l); var n = offset - m * l; - 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) { + var cell = this.rows[m].cells[n]; + if (cell.column.get("renderable") && cell.column.get("editable")) { cell.enterEditMode(); break; } } } } + + this.rows[i].cells[j].exitEditMode(); } }); /* diff --git a/UI/JsLibraries/backbone.backgrid.paginator.js b/UI/JsLibraries/backbone.backgrid.paginator.js index 430a818f7..cceabca00 100644 --- a/UI/JsLibraries/backbone.backgrid.paginator.js +++ b/UI/JsLibraries/backbone.backgrid.paginator.js @@ -6,186 +6,18 @@ Licensed under the MIT @license. */ -(function (_, Backbone, Backgrid) { +(function ($, _, Backbone, Backgrid) { "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} 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 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 threshold, which is set to 10 by default, the page handles are rendered - within a sliding window, plus the rewind, back, forward and fast forward - control handles. The individual control handles can be turned off. + within a sliding window, plus the fast forward, fast backward, previous and + next page handles. The fast forward, fast backward, previous and next page + handles can be turned off. @class Backgrid.Extension.Paginator */ @@ -198,65 +30,97 @@ windowSize: 10, /** - @property {Object.>} controls You can - disable specific control handles by omitting certain keys. + @property {Object} fastForwardHandleLabels You can disable specific + handles by setting its value to `null`. */ - controls: { - rewind: { - label: "《", - title: "First" - }, - back: { - label: "〈", - title: "Previous" - }, - forward: { - label: "〉", - title: "Next" - }, - fastForward: { - label: "》", - title: "Last" - } + fastForwardHandleLabels: { + first: "《", + prev: "〈", + next: "〉", + last: "》" }, - /** - @property {Backgrid.Extension.PageHandle} pageHandle. The PageHandle - class to use for rendering individual handles - */ - pageHandle: PageHandle, + /** @property */ + template: _.template(''), /** @property */ - goBackFirstOnSort: true, + events: { + "click a": "changePage" + }, /** Initializer. @param {Object} options @param {Backbone.Collection} options.collection - @param {boolean} [options.controls] - @param {boolean} [options.pageHandle=Backgrid.Extension.PageHandle] - @param {boolean} [options.goBackFirstOnSort=true] + @param {boolean} [options.fastForwardHandleLabels] Whether to render fast forward buttons. */ initialize: function (options) { Backgrid.requireOptions(options, ["collection"]); - this.controls = options.controls || this.controls; - this.pageHandle = options.pageHandle || this.pageHandle; - var collection = this.collection; - this.listenTo(collection, "add", this.render); - this.listenTo(collection, "remove", this.render); - this.listenTo(collection, "reset", this.render); - if ((options.goBackFirstOnSort || this.goBackFirstOnSort) && - collection.fullCollection) { - this.listenTo(collection.fullCollection, "sort", function () { - collection.getFirstPage(); - }); + 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, "remove", this.render); + this.listenTo(collection, "reset", this.render); } }, - _calculateWindow: 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(); + 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); + } + }, + + /** + Internal method to create a list of page handle objects for the template + to render them. + + @return {Array.} an array of page handle objects hashes + */ + makeHandles: function () { + + var handles = []; var collection = this.collection; var state = collection.state; @@ -268,44 +132,48 @@ currentPage = firstPage ? currentPage - 1 : currentPage; var windowStart = Math.floor(currentPage / this.windowSize) * this.windowSize; var windowEnd = Math.min(lastPage + 1, windowStart + this.windowSize); - return [windowStart, windowEnd]; - }, - /** - Creates a list of page handle objects for rendering. - - @return {Array.} an array of page handle objects hashes - */ - 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 - })); + if (collection.mode !== "infinite") { + for (var i = windowStart; i < windowEnd; i++) { + handles.push({ + label: i + 1, + title: "No. " + (i + 1), + className: currentPage === i ? "active" : undefined + }); + } } - 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; }, @@ -316,24 +184,15 @@ render: function () { this.$el.empty(); - if (this.handles) { - for (var i = 0, l = this.handles.length; i < l; i++) { - this.handles[i].remove(); - } - } + this.$el.append(this.template({ + handles: this.makeHandles() + })); - 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); + this.delegateEvents(); return this; } }); -}(_, Backbone, Backgrid)); +}(jQuery, _, Backbone, Backgrid)); diff --git a/UI/JsLibraries/backbone.pageable.js b/UI/JsLibraries/backbone.pageable.js index 23f0b3e45..f63288064 100644 --- a/UI/JsLibraries/backbone.pageable.js +++ b/UI/JsLibraries/backbone.pageable.js @@ -1,5 +1,5 @@ /* - backbone-pageable 1.3.1 + backbone-pageable 1.3.0 http://github.com/wyuenho/backbone-pageable Copyright (c) 2013 Jimmy Yuen Ho Wong @@ -574,17 +574,6 @@ /** 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 and subsequently a `reset` event. @@ -597,8 +586,6 @@ @param {number} pageSize The new page size to set to #state. @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. @throws {TypeError} If `pageSize` is not a finite integer. @@ -611,24 +598,14 @@ setPageSize: function (pageSize, options) { pageSize = finiteInt(pageSize, "pageSize"); - options = options || {first: false}; + options = options || {}; - 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, { + this.state = this._checkState(_extend({}, this.state, { pageSize: pageSize, - currentPage: options.first ? state.firstPage : currentPage, - totalPages: totalPages + totalPages: ceil(this.state.totalRecords / pageSize) })); - return this.getPage(state.currentPage, _omit(options, ["first"])); + return this.getPage(this.state.currentPage, options); }, /** @@ -1015,14 +992,13 @@ encouraged to override #parseState and #parseRecords instead. @param {Object} resp The deserialized response data from the server. - @param {Object} the options for the ajax request @return {Array.} An array of model objects */ - parse: function (resp, options) { - var newState = this.parseState(resp, _clone(this.queryParams), _clone(this.state), options); + parse: function (resp) { + var newState = this.parseState(resp, _clone(this.queryParams), _clone(this.state)); if (newState) this.state = this._checkState(_extend({}, this.state, newState)); - return this.parseRecords(resp, options); + return this.parseRecords(resp); }, /** @@ -1040,16 +1016,10 @@ `totalRecords` value is enough to trigger a full pagination state recalculation. - parseState: function (resp, queryParams, state, options) { + parseState: function (resp, queryParams, state) { 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 modifying the #state object. The behavior of directly modifying #state is undefined. @@ -1057,12 +1027,10 @@ @param {Object} resp The deserialized response data from the server. @param {Object} queryParams A copy of #queryParams. @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. */ - parseState: function (resp, queryParams, state, options) { + parseState: function (resp, queryParams, state) { if (resp && resp.length === 2 && _isObject(resp[0]) && _isArray(resp[1])) { var newState = _clone(state); @@ -1091,12 +1059,10 @@ response is returned directly. @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.} An array of model objects */ - parseRecords: function (resp, options) { + parseRecords: function (resp) { if (resp && resp.length === 2 && _isObject(resp[0]) && _isArray(resp[1])) { return resp[1]; } @@ -1172,7 +1138,7 @@ kvp = extraKvps[i]; v = kvp[1]; v = _isFunction(v) ? v.call(thisCopy) : v; - if (v != null) data[kvp[0]] = v; + data[kvp[0]] = v; } var fullCol = this.fullCollection, links = this.links; @@ -1246,11 +1212,11 @@ @param {string} [sortKey=this.state.sortKey] See `state.sortKey`. @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). */ - _makeComparator: function (sortKey, order, sortValue) { + _makeComparator: function (sortKey, order) { + var state = this.state; sortKey = sortKey || state.sortKey; @@ -1258,12 +1224,8 @@ if (!sortKey || !order) return; - if (!sortValue) sortValue = function (model, attr) { - return model.get(attr); - }; - return function (left, right) { - var l = sortValue(left, sortKey), r = sortValue(right, sortKey), t; + var l = left.get(sortKey), r = right.get(sortKey), t; if (order === 1) t = l, l = r, r = t; if (l === r) return 0; else if (l < r) return -1; @@ -1282,11 +1244,6 @@ `sortKey` to `null` removes the comparator from both the current page and 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 @param {string} sortKey See `state.sortKey`. @@ -1295,7 +1252,6 @@ @param {"server"|"client"} [options.side] By default, `"client"` if `mode` is `"client"`, `"server"` otherwise. @param {boolean} [options.full=true] - @param {(function(Backbone.Model, string): Object) | string} [options.sortValue] */ setSorting: function (sortKey, order, options) { @@ -1314,7 +1270,7 @@ options = _extend({side: mode == "client" ? mode : "server", full: true}, options); - var comparator = this._makeComparator(sortKey, order, options.sortValue); + var comparator = this._makeComparator(sortKey, order); var full = options.full, side = options.side;