Add backgrid extensions for use in various tools: select-all, paginator and filter

pull/3/head
Akshay Joshi 2016-02-26 11:08:45 +00:00 committed by Dave Page
parent 3b2ef2961b
commit b3ff96d2db
16 changed files with 2929 additions and 21 deletions

View File

@ -1,24 +1,28 @@
This is a list of the third party libraries/code used in the application, not
including Python modules installed via Pip which are listed in requirements.txt.
Library Version Licence URL
======= ======= ======= ===
QT 4.6.2+ LGPL v2.1/3 http://www.qt.io/
Bootstrap 3.3.4 MIT http://getbootstrap.com/
jQuery 1.11.1 MIT http://jquery.com/
Modernizr 2.6.2 MIT/BSD http://modernizr.com/
AlertifyJS 1.1.0 MIT http://alertifyjs.com/
CodeMirror 4.12 MIT http://codemirror.net/
aciTree 4.5.0-rc.7 MIT/GPL http://acoderinsights.ro/en/aciTree-tree-view-with-jQuery
wcDocker 8b84d55415 MIT/GPL https://github.com/WebCabin/wcDocker
Require.js 2.1.18 BSD/MIT http://requirejs.org/
Underscore.js 1.8.3 MIT http://underscorejs.org/
Underscore.string 387ab72d49 MIT http://epeli.github.io/underscore.string/
Backform.js 5859b4f9db MIT https://github.com/AmiliaApp/backform
Backbone 1.1.2 MIT http://backbonejs.org
font-Awesome 4.3 SIL OFL http://fortawesome.github.io/Font-Awesome/
font-mfizz 1.2 MIT http://fizzed.com/oss/font-mfizz
backgrid.js 0.3.5 MIT http://backgridjs.com/
backbone.undo 0.2 MIT http://backbone.undojs.com/
bootstrap-switch 3.3.2 MIT http://www.bootstrap-switch.org/
select2 4.0.1 MIT https://select2.github.io/
Library Version Licence URL
======= ======= ======= ===
QT 4.6.2+ LGPL v2.1/3 http://www.qt.io/
Bootstrap 3.3.4 MIT http://getbootstrap.com/
jQuery 1.11.1 MIT http://jquery.com/
Modernizr 2.6.2 MIT/BSD http://modernizr.com/
AlertifyJS 1.1.0 MIT http://alertifyjs.com/
CodeMirror 4.12 MIT http://codemirror.net/
aciTree 4.5.0-rc.7 MIT/GPL http://acoderinsights.ro/en/aciTree-tree-view-with-jQuery
wcDocker 8b84d55415 MIT/GPL https://github.com/WebCabin/wcDocker
Require.js 2.1.18 BSD/MIT http://requirejs.org/
Underscore.js 1.8.3 MIT http://underscorejs.org/
Underscore.string 387ab72d49 MIT http://epeli.github.io/underscore.string/
Backform.js 5859b4f9db MIT https://github.com/AmiliaApp/backform
Backbone 1.1.2 MIT http://backbonejs.org
font-Awesome 4.3 SIL OFL http://fortawesome.github.io/Font-Awesome/
font-mfizz 1.2 MIT http://fizzed.com/oss/font-mfizz
backgrid.js 0.3.5 MIT http://backgridjs.com/
backbone.undo 0.2 MIT http://backbone.undojs.com/
bootstrap-switch 3.3.2 MIT http://www.bootstrap-switch.org/
select2 4.0.1 MIT https://select2.github.io/
backgrid-filter 01b2b21 MIT https://github.com/wyuenho/backgrid-filter
backbone.paginator 2.0.3 MIT http://github.com/backbone-paginator/backbone.paginator
backgrid-paginator 03632df MIT https://github.com/wyuenho/backgrid-paginator
backgrid-select-all 1a00053 MIT https://github.com/wyuenho/backgrid-select-all

View File

@ -0,0 +1,201 @@
/*
backgrid-filter
http://github.com/wyuenho/backgrid
Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
Licensed under the MIT @license.
*/
/*
Search Icon CSS derived from:
PURE CSS GUI ICONS
by Nicolas Gallagher
- http://nicolasgallagher.com/pure-css-gui-icons/
http://nicolasgallagher.com
http://twitter.com/necolas
Created: 29 July 2010
Version: 1.0.1
Dual licensed under MIT and GNU GPLv2 (c) Nicolas Gallagher
*/
.backgrid-filter.form-search {
position: relative;
width: 248px;
height: 30px;
margin: 20px;
}
/*
Search Icon
*/
.backgrid-filter .search {
position: absolute;
top: 50%;
left: 6px;
z-index: 1000;
width: 10px;
height: 20px;
margin-top: -10px;
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box;
}
.backgrid-filter .search:before {
position: absolute;
top: 50%;
left: 0;
width: 6px;
height: 6px;
margin-top: -6px;
background: transparent;
border: 3px solid gray;
-webkit-border-radius: 12px;
-moz-border-radius: 12px;
border-radius: 12px;
content: "";
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box;
}
.backgrid-filter .search:after {
position: absolute;
top: 50%;
left: 10px;
width: 3px;
height: 7px;
margin-top: 2px;
background-color: gray;
content: "";
-webkit-transform: rotate(-45deg);
-moz-transform: rotate(-45deg);
-ms-transform: rotate(-45deg);
-o-transform: rotate(-45deg);
transform: rotate(-45deg);
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box;
}
/*
Clear button
*/
.backgrid-filter .clear {
position: absolute;
top: 50%;
right: 8px;
z-index: 1000;
width: 10px;
height: 20px;
margin-top: -10px;
font-family: sans-serif;
font-size: 20px;
font-weight: bold;
line-height: 20px;
color: gray;
text-decoration: none;
}
.backgrid-filter input[type="search"] {
position: absolute;
display: inline-block;
width: 206px;
height: 20px;
padding: 4px 6px;
font-weight: normal;
color: #555;
vertical-align: middle;
background-color: #fff;
border: 1px solid #ccc;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-webkit-transition: border linear 0.2s, box-shadow linear 0.2s;
-moz-transition: border linear 0.2s, box-shadow linear 0.2s;
-o-transition: border linear 0.2s, box-shadow linear 0.2s;
transition: border linear 0.2s, box-shadow linear 0.2s;
}
/*
Normalize the search input box, with code borrowed from normalize.css.
https://github.com/necolas/normalize.css/
Copyright (c) Nicolas Gallagher and Jonathan Neal, MIT @license.
*/
/*
* 1. Correct font family not being inherited in all browsers.
* 2. Correct font size not being inherited in all browsers.
* 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome.
* 4. Address Firefox 4+ setting `line-height` on `input` using `!important` in the UA stylesheet.
*/
.backgrid-filter input {
margin: 0;
font-family: inherit;
font-size: 100%;
line-height: normal;
}
/*
* Re-set default cursor for disabled elements.
*/
.backgrid-filter input[disabled] {
cursor: default;
}
/*
* 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
* 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
* (include `-moz` to future-proof).
*/
.backgrid-filter input[type="search"] {
outline: none;
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box;
-webkit-appearance: none;
}
/*
* Remove the default clear button on IE
*/
.backgrid-filter input[type="search"]::-ms-clear {
display: none;
}
/*
* Remove the default clear button on WebKit browsers
*/
.backgrid-filter input[type="search"]::-webkit-search-cancel-button {
-webkit-appearance: none;
}
/*
* Remove inner padding and border in Firefox 4+.
*/
.backgrid-filter input::-moz-focus-inner {
padding: 0;
border: 0;
}
.backgrid-filter input[type="search"] {
padding-right: 18px;
padding-left: 22px;
}

View File

@ -0,0 +1 @@
.backgrid-filter.form-search{position:relative;width:248px;height:30px;margin:20px}.backgrid-filter .search{position:absolute;top:50%;left:6px;z-index:1000;width:10px;height:20px;margin-top:-10px;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.backgrid-filter .search:before{position:absolute;top:50%;left:0;width:6px;height:6px;margin-top:-6px;background:transparent;border:3px solid gray;-webkit-border-radius:12px;-moz-border-radius:12px;border-radius:12px;content:"";-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.backgrid-filter .search:after{position:absolute;top:50%;left:10px;width:3px;height:7px;margin-top:2px;background-color:gray;content:"";-webkit-transform:rotate(-45deg);-moz-transform:rotate(-45deg);-ms-transform:rotate(-45deg);-o-transform:rotate(-45deg);transform:rotate(-45deg);-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.backgrid-filter .clear{position:absolute;top:50%;right:8px;z-index:1000;width:10px;height:20px;margin-top:-10px;font-family:sans-serif;font-size:20px;font-weight:bold;line-height:20px;color:gray;text-decoration:none}.backgrid-filter input[type="search"]{position:absolute;display:inline-block;width:206px;height:20px;padding:4px 6px;font-weight:normal;color:#555;vertical-align:middle;background-color:#fff;border:1px solid #ccc;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border linear .2s,box-shadow linear .2s;-moz-transition:border linear .2s,box-shadow linear .2s;-o-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s}.backgrid-filter input{margin:0;font-family:inherit;font-size:100%;line-height:normal}.backgrid-filter input[disabled]{cursor:default}.backgrid-filter input[type="search"]{outline:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:none}.backgrid-filter input[type="search"]::-ms-clear{display:none}.backgrid-filter input::-moz-focus-inner{padding:0;border:0}.backgrid-filter input[type="search"]{padding-right:18px;padding-left:22px}

View File

@ -0,0 +1,58 @@
/*
backgrid-paginator
http://github.com/wyuenho/backgrid
Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
Licensed under the MIT license.
*/
.backgrid-paginator {
text-align: center;
border-top: none;
-webkit-border-radius: 0 0 4px 4px;
-moz-border-radius: 0 0 4px 4px;
border-radius: 0 0 4px 4px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.backgrid-paginator ul {
display: inline-block;
*display: inline;
margin: 5px 0;
*zoom: 1;
}
.backgrid-paginator ul > li {
display: inline;
}
.backgrid-paginator ul > li > a,
.backgrid-paginator ul > li > span {
float: left;
width: 30px;
height: 30px;
padding: 0;
line-height: 30px;
text-decoration: none;
}
.backgrid-paginator ul > li > a:hover,
.backgrid-paginator ul > .active > a,
.backgrid-paginator ul > .active > span {
background-color: #f5f5f5;
}
.backgrid-paginator ul > .active > a,
.backgrid-paginator ul > .active > span {
color: #999999;
cursor: default;
}
.backgrid-paginator ul > .disabled > span,
.backgrid-paginator ul > .disabled > a,
.backgrid-paginator ul > .disabled > a:hover {
color: #999999;
cursor: default;
}

View File

@ -0,0 +1 @@
.backgrid-paginator{text-align:center;border-top:0;-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.backgrid-paginator ul{display:inline-block;*display:inline;margin:5px 0;*zoom:1}.backgrid-paginator ul>li{display:inline}.backgrid-paginator ul>li>a,.backgrid-paginator ul>li>span{float:left;width:30px;height:30px;padding:0;line-height:30px;text-decoration:none}.backgrid-paginator ul>li>a:hover,.backgrid-paginator ul>.active>a,.backgrid-paginator ul>.active>span{background-color:#f5f5f5}.backgrid-paginator ul>.active>a,.backgrid-paginator ul>.active>span{color:#999;cursor:default}.backgrid-paginator ul>.disabled>span,.backgrid-paginator ul>.disabled>a,.backgrid-paginator ul>.disabled>a:hover{color:#999;cursor:default}

View File

@ -0,0 +1,12 @@
/*
backgrid-select-all
http://github.com/wyuenho/backgrid
Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
Licensed under the MIT @license.
*/
.backgrid .select-row-cell,
.backgrid .select-all-header-cell {
text-align: center;
}

View File

@ -0,0 +1 @@
.backgrid .select-row-cell,.backgrid .select-all-header-cell{text-align:center}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,512 @@
/*
backgrid-filter
http://github.com/wyuenho/backgrid
Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
Licensed under the MIT @license.
*/
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(["underscore", "backbone", "backgrid"], factory);
} else if (typeof exports == "object") {
// CommonJS
(function () {
var lunr;
try { lunr = require("lunr"); } catch (e) {}
module.exports = factory(require("underscore"),
require("backbone"),
require("backgrid"),
lunr);
}());
} else {
// Browser
factory(root._, root.Backbone, root.Backgrid, root.lunr);
}
}(this, function (_, Backbone, Backgrid, lunr) {
"use strict";
/**
ServerSideFilter is a search form widget that submits a query to the server
for filtering the current collection.
@class Backgrid.Extension.ServerSideFilter
*/
var ServerSideFilter = Backgrid.Extension.ServerSideFilter = Backbone.View.extend({
/** @property */
tagName: "form",
/** @property */
className: "backgrid-filter form-search",
/** @property {function(Object, ?Object=): string} template */
template: function (data) {
return '<span class="search">&nbsp;</span><input type="search" ' + (data.placeholder ? 'placeholder="' + data.placeholder + '"' : '') + ' name="' + data.name + '" ' + (data.value ? 'value="' + data.value + '"' : '') + '/><a class="clear" data-backgrid-action="clear" href="#">&times;</a>';
},
/** @property */
events: {
"keyup input[type=search]": "showClearButtonMaybe",
"click a[data-backgrid-action=clear]": "clear",
"submit": "search"
},
/** @property {string} [name='q'] Query key */
name: "q",
/** @property {string} [value] The search box value. */
value: null,
/**
@property {string} [placeholder] 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.value]
@param {string} [options.placeholder]
@param {function(Object): string} [options.template]
*/
initialize: function (options) {
ServerSideFilter.__super__.initialize.apply(this, arguments);
this.name = options.name || this.name;
this.value = options.value || this.value;
this.placeholder = options.placeholder || this.placeholder;
this.template = options.template || this.template;
// 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;
};
}
},
/**
Event handler. Clear the search box and reset the internal search value.
*/
clearSearchBox: function() {
this.value = null;
this.searchBox().val(null);
this.showClearButtonMaybe();
},
/**
Event handler. Show the clear button when the search box has text, hide
it otherwise.
*/
showClearButtonMaybe: function () {
var $clearButton = this.clearButton();
var searchTerms = this.searchBox().val();
if (searchTerms) $clearButton.show();
else $clearButton.hide();
},
/**
Returns the search input box.
*/
searchBox: function () {
return this.$el.find("input[type=search]");
},
/**
Returns the clear button.
*/
clearButton: function () {
return this.$el.find("a[data-backgrid-action=clear]");
},
/**
Returns the current search query.
*/
query: function() {
this.value = this.searchBox().val();
return this.value;
},
/**
Upon search form submission, this event handler constructs a query
parameter object and pass it to Collection#fetch for server-side
filtering.
If the collection is a PageableCollection, searching will go back to the
first page.
*/
search: function (e) {
if (e) e.preventDefault();
var data = {};
var query = this.query();
if (query) data[this.name] = query;
var collection = this.collection;
// go back to the first page on search
if (Backbone.PageableCollection &&
collection instanceof Backbone.PageableCollection) {
collection.getFirstPage({data: data, reset: true, fetch: true});
}
else collection.fetch({data: data, reset: true});
},
/**
Event handler for the clear button. Clears the search box and refetch the
collection.
If the collection is a PageableCollection, clearing will go back to the
first page.
*/
clear: function (e) {
if (e) e.preventDefault();
this.clearSearchBox();
var collection = this.collection;
// go back to the first page on clear
if (Backbone.PageableCollection &&
collection instanceof Backbone.PageableCollection) {
collection.getFirstPage({reset: true, fetch: true});
}
else collection.fetch({reset: true});
},
/**
Renders a search form with a text box, optionally with a placeholder and
a preset value if supplied during initialization.
*/
render: function () {
this.$el.empty().append(this.template({
name: this.name,
placeholder: this.placeholder,
value: this.value
}));
this.showClearButtonMaybe();
this.delegateEvents();
return this;
}
});
/**
ClientSideFilter is a search form widget that searches a collection for
model matches against a query on the client side. The exact matching
algorithm can be overriden by subclasses.
@class Backgrid.Extension.ClientSideFilter
@extends Backgrid.Extension.ServerSideFilter
*/
var ClientSideFilter = Backgrid.Extension.ClientSideFilter = ServerSideFilter.extend({
/** @property */
events: _.extend({}, ServerSideFilter.prototype.events, {
"click a[data-backgrid-action=clear]": function (e) {
e.preventDefault();
this.clear();
},
"keydown input[type=search]": "search",
"submit": function (e) {
e.preventDefault();
this.search();
}
}),
/**
@property {?Array.<string>} [fields] 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 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.
*/
wait: 149,
/**
Debounces the #search and #clear methods and makes a copy of the given
collection for searching.
@param {Object} options
@param {Backbone.Collection} options.collection
@param {string} [options.placeholder]
@param {string} [options.fields]
@param {string} [options.wait=149]
*/
initialize: function (options) {
ClientSideFilter.__super__.initialize.apply(this, arguments);
this.fields = options.fields || this.fields;
this.wait = options.wait || this.wait;
this._debounceMethods(["search", "clear"]);
var collection = this.collection = this.collection.fullCollection || this.collection;
var shadowCollection = this.shadowCollection = collection.clone();
this.listenTo(collection, "add", function (model, collection, options) {
shadowCollection.add(model, options);
});
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) {
options = _.extend({reindex: true}, options || {});
if (options.reindex && options.from == null && options.to == null) {
shadowCollection.reset(col.models);
}
});
},
_debounceMethods: function (methodNames) {
if (_.isString(methodNames)) methodNames = [methodNames];
this.undelegateEvents();
for (var i = 0, l = methodNames.length; i < l; i++) {
var methodName = methodNames[i];
var method = this[methodName];
this[methodName] = _.debounce(method, this.wait);
}
this.delegateEvents();
},
/**
Constructs a Javascript regular expression object for #makeMatcher.
This default implementation takes a query string and returns a Javascript
RegExp object that matches any of the words contained in the query string
case-insensitively. Override this method to return a different regular
expression matcher if this behavior is not desired.
@param {string} query The search query in the search box.
@return {RegExp} A RegExp object to match against model #fields.
*/
makeRegExp: function (query) {
return new RegExp(query.trim().split(/\s+/).join("|"), "i");
},
/**
This default implementation takes a query string and returns a matcher
function that looks for matches in the model's #fields or all of its
fields if #fields is null, for any of the words in the query
case-insensitively using the regular expression object returned from
#makeRegExp.
Most of time, you'd want to override the regular expression used for
matching. If so, please refer to the #makeRegExp documentation,
otherwise, you can override this method to return a custom matching
function.
Subclasses overriding this method must take care to conform to the
signature of the matcher function. The matcher function is a function
that takes a model as paramter and returns true if the model matches a
search, or false otherwise.
In addition, when the matcher function is called, its context will be
bound to this ClientSideFilter object so it has access to the filter's
attributes and methods.
@param {string} query The search query in the search box.
@return {function(Backbone.Model):boolean} A matching function.
*/
makeMatcher: function (query) {
var regexp = this.makeRegExp(query);
return function (model) {
var keys = this.fields || model.keys();
for (var i = 0, l = keys.length; i < l; i++) {
if (regexp.test(model.get(keys[i]) + "")) return true;
}
return false;
};
},
/**
Takes the query from the search box, constructs a matcher with it and
loops through collection looking for matches. Reset the given collection
when all the matches have been found.
If the collection is a PageableCollection, searching will go back to the
first page.
*/
search: function () {
var matcher = _.bind(this.makeMatcher(this.query()), this);
var col = this.collection;
if (col.pageableCollection) col.pageableCollection.getFirstPage({silent: true});
col.reset(this.shadowCollection.filter(matcher), {reindex: false});
},
/**
Clears the search box and reset the collection to its original.
If the collection is a PageableCollection, clearing will go back to the
first page.
*/
clear: function () {
this.clearSearchBox();
var col = this.collection;
if (col.pageableCollection) col.pageableCollection.getFirstPage({silent: true});
col.reset(this.shadowCollection.models, {reindex: false});
}
});
/**
LunrFilter is a ClientSideFilter that uses [lunrjs](http://lunrjs.com/) to
index the text fields of each model for a collection, and performs
full-text searching.
@class Backgrid.Extension.LunrFilter
@extends Backgrid.Extension.ClientSideFilter
*/
var LunrFilter = Backgrid.Extension.LunrFilter = ClientSideFilter.extend({
/**
@property {string} [ref="id"]lunrjs` document reference attribute name.
*/
ref: "id",
/**
@property {Object} fields A hash of `lunrjs` index field names and boost
value. Unlike ClientSideFilter#fields, LunrFilter#fields is _required_ to
initialize the index.
*/
fields: null,
/**
Indexes the underlying collection on construction. The index will refresh
when the underlying collection is reset. If any model is added, removed
or if any indexed fields of any models has changed, the index will be
updated.
@param {Object} options
@param {Backbone.Collection} options.collection
@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.
@param {number} [options.wait]
*/
initialize: function (options) {
LunrFilter.__super__.initialize.apply(this, arguments);
this.ref = options.ref || this.ref;
var collection = this.collection = this.collection.fullCollection || this.collection;
this.listenTo(collection, "add", this.addToIndex);
this.listenTo(collection, "remove", this.removeFromIndex);
this.listenTo(collection, "reset", this.resetIndex);
this.listenTo(collection, "change", this.updateIndex);
this.resetIndex(collection);
},
/**
Reindex the collection. If `options.reindex` is `false`, this method is a
no-op.
@param {Backbone.Collection} collection
@param {Object} [options]
@param {boolean} [options.reindex=true]
*/
resetIndex: function (collection, options) {
options = _.extend({reindex: true}, options || {});
if (options.reindex) {
var self = this;
this.index = lunr(function () {
_.each(self.fields, function (boost, fieldName) {
this.field(fieldName, boost);
this.ref(self.ref);
}, this);
});
collection.each(function (model) {
this.addToIndex(model);
}, this);
}
},
/**
Adds the given model to the index.
@param {Backbone.Model} model
*/
addToIndex: function (model) {
var index = this.index;
var doc = model.toJSON();
if (index.documentStore.has(doc[this.ref])) index.update(doc);
else index.add(doc);
},
/**
Removes the given model from the index.
@param {Backbone.Model} model
*/
removeFromIndex: function (model) {
var index = this.index;
var doc = model.toJSON();
if (index.documentStore.has(doc[this.ref])) index.remove(doc);
},
/**
Updates the index for the given model.
@param {Backbone.Model} model
*/
updateIndex: function (model) {
var changed = model.changedAttributes();
if (changed && !_.isEmpty(_.intersection(_.keys(this.fields),
_.keys(changed)))) {
this.index.update(model.toJSON());
}
},
/**
Takes the query from the search box and performs a full-text search on
the client-side. The search result is returned by resetting the
underlying collection to the models after interrogating the index for the
query answer.
If the collection is a PageableCollection, searching will go back to the
first page.
*/
search: function () {
var col = this.collection;
if (!this.query()) {
col.reset(this.shadowCollection.models, {reindex: false});
return;
}
var searchResults = this.index.search(this.query());
var models = [];
for (var i = 0; i < searchResults.length; i++) {
var result = searchResults[i];
models.push(this.shadowCollection.get(result.ref));
}
if (col.pageableCollection) col.pageableCollection.getFirstPage({silent: true});
col.reset(models, {reindex: false});
}
});
}));

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,433 @@
/*
backgrid-paginator
http://github.com/wyuenho/backgrid
Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
Licensed under the MIT @license.
*/
(function (root, factory) {
// CommonJS
if (typeof exports == "object") {
module.exports = factory(require("underscore"),
require("backbone"),
require("backgrid"),
require("backbone.paginator"));
}
// AMD. Register as an anonymous module.
else if (typeof define === 'function' && define.amd) {
define(['underscore', 'backbone', 'backgrid', 'backbone.paginator'], factory);
}
// Browser
else {
factory(root._, root.Backbone, root.Backgrid);
}
}(this, 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` function or a string are required to be passed to the
constuctor under this mode. If a `title` function is provided, it __MUST__
accept a hash parameter `data`, which contains a key `label`. 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` function will be used. You are encouraged to provide your own
`title` function 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 a function that takes a `data`
parameter, which contains a mandatory `label` key which provides the
label value to be displayed.
*/
title: function (data) {
return 'Page ' + data.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) {
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;
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);
var collection = this.collection;
var state = collection.state;
var currentPage = state.currentPage;
var pageIndex = this.pageIndex;
if (this.isRewind && currentPage == state.firstPage ||
this.isBack && !collection.hasPreviousPage() ||
this.isForward && !collection.hasNextPage() ||
this.isFastForward && (currentPage == state.lastPage || state.totalPages < 1)) {
this.$el.addClass("disabled");
}
else if (!(this.isRewind ||
this.isBack ||
this.isForward ||
this.isFastForward) &&
state.currentPage == pageIndex) {
this.$el.addClass("active");
}
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, col = this.collection;
if (!$el.hasClass("active") && !$el.hasClass("disabled")) {
if (this.isRewind) col.getFirstPage();
else if (this.isBack) col.getPreviousPage();
else if (this.isForward) col.getNextPage();
else if (this.isFastForward) col.getLastPage();
else col.getPage(this.pageIndex, {reset: true});
}
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.
@class Backgrid.Extension.Paginator
*/
var Paginator = Backgrid.Extension.Paginator = Backbone.View.extend({
/** @property */
className: "backgrid-paginator",
/** @property */
windowSize: 10,
/**
@property {number} slideScale the number used by #slideHowMuch to scale
`windowSize` to yield the number of pages to slide. For example, the
default windowSize(10) * slideScale(0.5) yields 5, which means the window
will slide forward 5 pages as soon as you've reached page 6. The smaller
the scale factor the less pages to slide, and vice versa.
Also See:
- #slideMaybe
- #slideHowMuch
*/
slideScale: 0.5,
/**
@property {Object.<string, Object.<string, string>>} controls You can
disable specific control handles by setting the keys in question to
null. The defaults will be merged with your controls object, with your
changes taking precedent.
*/
controls: {
rewind: {
label: "《",
title: "First"
},
back: {
label: "〈",
title: "Previous"
},
forward: {
label: "〉",
title: "Next"
},
fastForward: {
label: "》",
title: "Last"
}
},
/** @property */
renderIndexedPageHandles: true,
/**
@property {Backgrid.Extension.PageHandle} pageHandle. The PageHandle
class to use for rendering individual handles
*/
pageHandle: PageHandle,
/** @property */
goBackFirstOnSort: true,
/**
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]
*/
initialize: function (options) {
var self = this;
self.controls = _.defaults(options.controls || {}, self.controls,
Paginator.prototype.controls);
_.extend(self, _.pick(options || {}, "windowSize", "pageHandle",
"slideScale", "goBackFirstOnSort",
"renderIndexedPageHandles"));
var col = self.collection;
self.listenTo(col, "add", self.render);
self.listenTo(col, "remove", self.render);
self.listenTo(col, "reset", self.render);
self.listenTo(col, "backgrid:sorted", function () {
if (self.goBackFirstOnSort) col.getFirstPage({reset: true});
});
},
/**
Decides whether the window should slide. This method should return 1 if
sliding should occur and 0 otherwise. The default is sliding should occur
if half of the pages in a window has been reached.
__Note__: All the parameters have been normalized to be 0-based.
@param {number} firstPage
@param {number} lastPage
@param {number} currentPage
@param {number} windowSize
@param {number} slideScale
@return {0|1}
*/
slideMaybe: function (firstPage, lastPage, currentPage, windowSize, slideScale) {
return Math.round(currentPage % windowSize / windowSize);
},
/**
Decides how many pages to slide when sliding should occur. The default
simply scales the `windowSize` to arrive at a fraction of the `windowSize`
to increment.
__Note__: All the parameters have been normalized to be 0-based.
@param {number} firstPage
@param {number} lastPage
@param {number} currentPage
@param {number} windowSize
@param {number} slideScale
@return {number}
*/
slideThisMuch: function (firstPage, lastPage, currentPage, windowSize, slideScale) {
return ~~(windowSize * slideScale);
},
_calculateWindow: function () {
var collection = this.collection;
var state = collection.state;
// convert all indices to 0-based here
var firstPage = state.firstPage;
var lastPage = +state.lastPage;
lastPage = Math.max(0, firstPage ? lastPage - 1 : lastPage);
var currentPage = Math.max(state.currentPage, state.firstPage);
currentPage = firstPage ? currentPage - 1 : currentPage;
var windowSize = this.windowSize;
var slideScale = this.slideScale;
var windowStart = Math.floor(currentPage / windowSize) * windowSize;
if (currentPage <= lastPage - this.slideThisMuch()) {
windowStart += (this.slideMaybe(firstPage, lastPage, currentPage, windowSize, slideScale) *
this.slideThisMuch(firstPage, lastPage, currentPage, windowSize, slideScale));
}
var windowEnd = Math.min(lastPage + 1, windowStart + windowSize);
return [windowStart, windowEnd];
},
/**
Creates a list of page handle objects for rendering.
@return {Array.<Object>} 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];
if (this.renderIndexedPageHandles) {
for (var i = winStart; i < winEnd; i++) {
handles.push(new this.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 this.pageHandle(handleCtorOpts);
if (key == "rewind" || key == "back") handles.unshift(handle);
else handles.push(handle);
}
}, this);
return handles;
},
/**
Render the paginator handles inside an unordered list.
*/
render: function () {
this.$el.empty();
if (this.handles) {
for (var i = 0, l = this.handles.length; i < l; i++) {
this.handles[i].remove();
}
}
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;
}
});
}));

View File

@ -0,0 +1,8 @@
/*
backgrid-paginator
http://github.com/wyuenho/backgrid
Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
Licensed under the MIT @license.
*/
!function(a,b){"object"==typeof exports&&(module.exports=b(require("underscore"),require("backbone"),require("backgrid"),require("backbone.paginator"))),"function"==typeof define&&define.amd?define(["underscore","backbone","backgrid","backbone.paginator"],b):b(a._,a.Backbone,a.Backgrid)}(this,function(a,b,c){"use strict";var d=c.Extension.PageHandle=b.View.extend({tagName:"li",events:{"click a":"changePage"},title:function(a){return"Page "+a.label},isRewind:!1,isBack:!1,isForward:!1,isFastForward:!1,initialize:function(b){var c=this.collection,d=c.state,e=d.currentPage,f=d.firstPage,g=d.lastPage;a.extend(this,a.pick(b,["isRewind","isBack","isForward","isFastForward"]));var h;this.isRewind?h=f:this.isBack?h=Math.max(f,e-1):this.isForward?h=Math.min(g,e+1):this.isFastForward?h=g:(h=+b.pageIndex,h=f?h+1:h),this.pageIndex=h,this.label=(b.label||(f?h:h+1))+"";var i=b.title||this.title;this.title=a.isFunction(i)?i({label:this.label}):i},render:function(){this.$el.empty();var a=document.createElement("a");a.href="#",this.title&&(a.title=this.title),a.innerHTML=this.label,this.el.appendChild(a);var b=this.collection,c=b.state,d=c.currentPage,e=this.pageIndex;return this.isRewind&&d==c.firstPage||this.isBack&&!b.hasPreviousPage()||this.isForward&&!b.hasNextPage()||this.isFastForward&&(d==c.lastPage||c.totalPages<1)?this.$el.addClass("disabled"):this.isRewind||this.isBack||this.isForward||this.isFastForward||c.currentPage!=e||this.$el.addClass("active"),this.delegateEvents(),this},changePage:function(a){a.preventDefault();var b=this.$el,c=this.collection;return b.hasClass("active")||b.hasClass("disabled")||(this.isRewind?c.getFirstPage():this.isBack?c.getPreviousPage():this.isForward?c.getNextPage():this.isFastForward?c.getLastPage():c.getPage(this.pageIndex,{reset:!0})),this}}),e=c.Extension.Paginator=b.View.extend({className:"backgrid-paginator",windowSize:10,slideScale:.5,controls:{rewind:{label:"《",title:"First"},back:{label:"〈",title:"Previous"},forward:{label:"〉",title:"Next"},fastForward:{label:"》",title:"Last"}},renderIndexedPageHandles:!0,pageHandle:d,goBackFirstOnSort:!0,initialize:function(b){var c=this;c.controls=a.defaults(b.controls||{},c.controls,e.prototype.controls),a.extend(c,a.pick(b||{},"windowSize","pageHandle","slideScale","goBackFirstOnSort","renderIndexedPageHandles"));var d=c.collection;c.listenTo(d,"add",c.render),c.listenTo(d,"remove",c.render),c.listenTo(d,"reset",c.render),c.listenTo(d,"backgrid:sorted",function(){c.goBackFirstOnSort&&d.getFirstPage({reset:!0})})},slideMaybe:function(a,b,c,d){return Math.round(c%d/d)},slideThisMuch:function(a,b,c,d,e){return~~(d*e)},_calculateWindow:function(){var a=this.collection,b=a.state,c=b.firstPage,d=+b.lastPage;d=Math.max(0,c?d-1:d);var e=Math.max(b.currentPage,b.firstPage);e=c?e-1:e;var f=this.windowSize,g=this.slideScale,h=Math.floor(e/f)*f;e<=d-this.slideThisMuch()&&(h+=this.slideMaybe(c,d,e,f,g)*this.slideThisMuch(c,d,e,f,g));var i=Math.min(d+1,h+f);return[h,i]},makeHandles:function(){var b=[],c=this.collection,d=this._calculateWindow(),e=d[0],f=d[1];if(this.renderIndexedPageHandles)for(var g=e;f>g;g++)b.push(new this.pageHandle({collection:c,pageIndex:g}));var h=this.controls;return a.each(["back","rewind","forward","fastForward"],function(a){var d=h[a];if(d){var e={collection:c,title:d.title,label:d.label};e["is"+a.slice(0,1).toUpperCase()+a.slice(1)]=!0;var f=new this.pageHandle(e);"rewind"==a||"back"==a?b.unshift(f):b.push(f)}},this),b},render:function(){if(this.$el.empty(),this.handles)for(var a=0,b=this.handles.length;b>a;a++)this.handles[a].remove();for(var c=this.handles=this.makeHandles(),d=document.createElement("ul"),a=0;a<c.length;a++)d.appendChild(c[a].render().el);return this.el.appendChild(d),this}})});

View File

@ -0,0 +1,294 @@
/*
backgrid-select-all
http://github.com/wyuenho/backgrid
Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
Licensed under the MIT @license.
*/
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(["backbone", "backgrid", "underscore"], factory);
} else if (typeof exports == "object") {
// CommonJS
module.exports = factory(require("backbone"), require("backgrid"), require("underscore"));
}
// Browser
else factory(root.Backbone, root.Backgrid, root._);
}(this, function (Backbone, Backgrid, _) {
"use strict";
/**
Renders a checkbox for row selection.
@class Backgrid.Extension.SelectRowCell
@extends Backbone.View
*/
var SelectRowCell = Backgrid.Extension.SelectRowCell = Backbone.View.extend({
/** @property */
className: "select-row-cell",
/** @property */
tagName: "td",
/** @property */
events: {
"keydown input[type=checkbox]": "onKeydown",
"change input[type=checkbox]": "onChange",
"click input[type=checkbox]": "enterEditMode"
},
/**
Initializer. If the underlying model triggers a `select` event, this cell
will change its checked value according to the event's `selected` value.
@param {Object} options
@param {Backgrid.Column} options.column
@param {Backbone.Model} options.model
*/
initialize: function (options) {
this.column = options.column;
if (!(this.column instanceof Backgrid.Column)) {
this.column = new Backgrid.Column(this.column);
}
var column = this.column, model = this.model, $el = this.$el;
this.listenTo(column, "change:renderable", function (column, renderable) {
$el.toggleClass("renderable", renderable);
});
if (Backgrid.callByNeed(column.renderable(), column, model)) $el.addClass("renderable");
this.listenTo(model, "backgrid:select", function (model, selected) {
this.checkbox().prop("checked", selected).change();
});
},
/**
Returns the checkbox.
*/
checkbox: function () {
return this.$el.find("input[type=checkbox]");
},
/**
Focuses the checkbox.
*/
enterEditMode: function () {
this.checkbox().focus();
},
/**
Unfocuses the checkbox.
*/
exitEditMode: function () {
this.checkbox().blur();
},
/**
Process keyboard navigation.
*/
onKeydown: function (e) {
var command = new Backgrid.Command(e);
if (command.passThru()) return true; // skip ahead to `change`
if (command.cancel()) {
e.stopPropagation();
this.checkbox().blur();
}
else if (command.save() || command.moveLeft() || command.moveRight() ||
command.moveUp() || command.moveDown()) {
e.preventDefault();
e.stopPropagation();
this.model.trigger("backgrid:edited", this.model, this.column, command);
}
},
/**
When the checkbox's value changes, this method will trigger a Backbone
`backgrid:selected` event with a reference of the model and the
checkbox's `checked` value.
*/
onChange: function () {
var checked = this.checkbox().prop("checked");
this.$el.parent().toggleClass("selected", checked);
this.model.trigger("backgrid:selected", this.model, checked);
},
/**
Renders a checkbox in a table cell.
*/
render: function () {
this.$el.empty().append('<input tabindex="-1" type="checkbox" />');
this.delegateEvents();
return this;
}
});
/**
Renders a checkbox to select all rows on the current page.
@class Backgrid.Extension.SelectAllHeaderCell
@extends Backgrid.Extension.SelectRowCell
*/
var SelectAllHeaderCell = Backgrid.Extension.SelectAllHeaderCell = SelectRowCell.extend({
/** @property */
className: "select-all-header-cell",
/** @property */
tagName: "th",
/**
Initializer. When this cell's checkbox is checked, a Backbone
`backgrid:select` event will be triggered for each model for the current
page in the underlying collection. If a `SelectRowCell` instance exists
for the rows representing the models, they will check themselves. If any
of the SelectRowCell instances trigger a Backbone `backgrid:selected`
event with a `false` value, this cell will uncheck its checkbox. In the
event of a Backbone `backgrid:refresh` event, which is triggered when the
body refreshes its rows, which can happen under a number of conditions
such as paging or the columns were reset, this cell will still remember
the previously selected models and trigger a Backbone `backgrid:select`
event on them such that the SelectRowCells can recheck themselves upon
refreshing.
@param {Object} options
@param {Backgrid.Column} options.column
@param {Backbone.Collection} options.collection
*/
initialize: function (options) {
this.column = options.column;
if (!(this.column instanceof Backgrid.Column)) {
this.column = new Backgrid.Column(this.column);
}
var collection = this.collection;
var selectedModels = this.selectedModels = {};
this.listenTo(collection.fullCollection || collection,
"backgrid:selected", function (model, selected) {
if (selected) selectedModels[model.id || model.cid] = 1;
else {
delete selectedModels[model.id || model.cid];
this.checkbox().prop("checked", false);
}
if (_.keys(selectedModels).length === (collection.fullCollection|| collection).length) {
this.checkbox().prop("checked", true);
}
});
this.listenTo(collection.fullCollection || collection, "remove", function (model) {
delete selectedModels[model.id || model.cid];
if ((collection.fullCollection || collection).length === 0) {
this.checkbox().prop("checked", false);
}
});
this.listenTo(collection, "backgrid:refresh", function () {
if ((collection.fullCollection || collection).length === 0) {
this.checkbox().prop("checked", false);
}
else {
var checked = this.checkbox().prop("checked");
for (var i = 0; i < collection.length; i++) {
var model = collection.at(i);
if (checked || selectedModels[model.id || model.cid]) {
model.trigger("backgrid:select", model, true);
}
}
}
});
var column = this.column, $el = this.$el;
this.listenTo(column, "change:renderable", function (column, renderable) {
$el.toggleClass("renderable", renderable);
});
if (Backgrid.callByNeed(column.renderable(), column, collection)) $el.addClass("renderable");
},
/**
Propagates the checked value of this checkbox to all the models of the
underlying collection by triggering a Backbone `backgrid:select` event on
the models on the current page, passing each model and the current
`checked` value of the checkbox in each event.
A `backgrid:selected` event will also be triggered with the current
`checked` value on all the models regardless of whether they are on the
current page.
This method triggers a 'backgrid:select-all' event on the collection
afterwards.
*/
onChange: function () {
var checked = this.checkbox().prop("checked");
var collection = this.collection;
collection.each(function (model) {
model.trigger("backgrid:select", model, checked);
});
if (collection.fullCollection) {
collection.fullCollection.each(function (model) {
if (!collection.get(model.cid)) {
model.trigger("backgrid:selected", model, checked);
}
});
}
this.collection.trigger("backgrid:select-all", this.collection, checked);
}
});
/**
Convenient method to retrieve a list of selected models. This method only
exists when the `SelectAll` extension has been included. Selected models
are retained across pagination.
@member Backgrid.Grid
@return {Array.<Backbone.Model>}
*/
Backgrid.Grid.prototype.getSelectedModels = function () {
var selectAllHeaderCell;
var headerCells = this.header.row.cells;
for (var i = 0, l = headerCells.length; i < l; i++) {
var headerCell = headerCells[i];
if (headerCell instanceof SelectAllHeaderCell) {
selectAllHeaderCell = headerCell;
break;
}
}
var result = [];
if (selectAllHeaderCell) {
var selectedModels = selectAllHeaderCell.selectedModels;
var collection = this.collection.fullCollection || this.collection;
for (var modelId in selectedModels) {
result.push(collection.get(modelId));
}
}
return result;
};
/**
Convenient method to deselect the selected models. This method is only
available when the `SelectAll` extension has been included.
@member Backgrid.Grid
*/
Backgrid.Grid.prototype.clearSelectedModels = function () {
var selectedModels = this.getSelectedModels();
for (var i = 0, l = selectedModels.length; i < l; i++) {
var model = selectedModels[i];
model.trigger("backgrid:select", model, false);
}
};
}));

View File

@ -0,0 +1,8 @@
/*
backgrid-select-all
http://github.com/wyuenho/backgrid
Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
Licensed under the MIT @license.
*/
!function(a,b){"object"==typeof exports?module.exports=b(require("backbone"),require("backgrid")):b(a.Backbone,a.Backgrid)}(this,function(a,b){"use strict";var c=b.Extension.SelectRowCell=a.View.extend({className:"select-row-cell",tagName:"td",events:{"keydown input[type=checkbox]":"onKeydown","change input[type=checkbox]":"onChange","click input[type=checkbox]":"enterEditMode"},initialize:function(a){this.column=a.column,this.column instanceof b.Column||(this.column=new b.Column(this.column));var c=this.column,d=this.model,e=this.$el;this.listenTo(c,"change:renderable",function(a,b){e.toggleClass("renderable",b)}),b.callByNeed(c.renderable(),c,d)&&e.addClass("renderable"),this.listenTo(d,"backgrid:select",function(a,b){this.$el.find("input[type=checkbox]").prop("checked",b).change()})},enterEditMode:function(){this.$el.find("input[type=checkbox]").focus()},exitEditMode:function(){this.$el.find("input[type=checkbox]").blur()},onKeydown:function(a){var c=new b.Command(a);return c.passThru()?!0:(c.cancel()?(a.stopPropagation(),this.$el.find("input[type=checkbox]").blur()):(c.save()||c.moveLeft()||c.moveRight()||c.moveUp()||c.moveDown())&&(a.preventDefault(),a.stopPropagation(),this.model.trigger("backgrid:edited",this.model,this.column,c)),void 0)},onChange:function(){var a=this.$el.find("input[type=checkbox]").prop("checked");this.$el.parent().toggleClass("selected",a),this.model.trigger("backgrid:selected",this.model,a)},render:function(){return this.$el.empty().append('<input tabindex="-1" type="checkbox" />'),this.delegateEvents(),this}}),d=b.Extension.SelectAllHeaderCell=c.extend({className:"select-all-header-cell",tagName:"th",initialize:function(a){this.column=a.column,this.column instanceof b.Column||(this.column=new b.Column(this.column));var c=this.collection,d=this.selectedModels={};this.listenTo(c.fullCollection||c,"backgrid:selected",function(a,b){b?d[a.id||a.cid]=1:(delete d[a.id||a.cid],this.$el.find("input[type=checkbox]").prop("checked",!1))}),this.listenTo(c.fullCollection||c,"remove",function(a){delete d[a.id||a.cid]}),this.listenTo(c,"backgrid:refresh",function(){for(var a=this.$el.find("input[type=checkbox]").prop("checked"),b=0;b<c.length;b++){var e=c.at(b);(a||d[e.id||e.cid])&&e.trigger("backgrid:select",e,!0)}});var e=this.column,f=this.$el;this.listenTo(e,"change:renderable",function(a,b){f.toggleClass("renderable",b)}),b.callByNeed(e.renderable(),e,c)&&f.addClass("renderable")},onChange:function(){var a=this.$el.find("input[type=checkbox]").prop("checked"),b=this.collection;b.each(function(b){b.trigger("backgrid:select",b,a)}),b.fullCollection&&b.fullCollection.each(function(c){b.get(c.cid)||c.trigger("backgrid:selected",c,a)}),this.collection.trigger("backgrid:select-all",this.collection,a)}});b.Grid.prototype.getSelectedModels=function(){for(var a,b=this.header.row.cells,c=0,e=b.length;e>c;c++){var f=b[c];if(f instanceof d){a=f;break}}var g=[];if(a){var h=a.selectedModels,i=this.collection.fullCollection||this.collection;for(var j in h)g.push(i.get(j))}return g},b.Grid.prototype.clearSelectedModels=function(){for(var a=this.getSelectedModels(),b=0,c=a.length;c>b;b++){var d=a[b];d.trigger("backgrid:select",d,!1)}}});

View File

@ -24,6 +24,9 @@
<link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap-datepicker3.css')}}"/>
<link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap-switch.css')}}"/>
<link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/backgrid/backgrid.css')}}"/>
<link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/backgrid/backgrid-select-all.css' if config.DEBUG else 'css/backgrid/backgrid-select-all.min.css')}}"/>
<link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/backgrid/backgrid-paginator.css' if config.DEBUG else 'css/backgrid/backgrid-paginator.min.css')}}"/>
<link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/backgrid/backgrid-filter.css' if config.DEBUG else 'css/backgrid/backgrid-filter.min.css')}}"/>
<link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/select2/select2.css' if config.DEBUG else 'css/select2/select2.min.css')}}"/>
<!-- View specified stylesheets -->
@ -32,6 +35,7 @@
{% endfor %}
<link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/overrides.css') }}"/>
{% block css_link %}{% endblock %}
<!-- Base template scripts -->
<script type="text/javascript" src="{{ url_for('static', filename='js/require.js' if config.DEBUG else 'js/require.min.js') }}"></script>
@ -44,6 +48,9 @@
"deps": ['underscore', 'jquery'],
"exports": 'Backbone'
},
"backbone.paginator": {
"deps": ['underscore', 'jquery', 'backbone']
},
"bootstrap": {
"deps": ['jquery'],
},
@ -51,6 +58,15 @@
"deps": ['backform'],
"exports": 'Backgrid',
},
"backgrid.select.all": {
"deps": ['backgrid']
},
"backgrid.paginator": {
"deps": ['backgrid', 'backbone.paginator']
},
"backgrid.filter": {
"deps": ['backgrid']
},
"bootstrap.switch": {
"deps": ['jquery', 'bootstrap'],
"exports": 'jQuery.fn.bootstrapSwitch'
@ -85,10 +101,14 @@
alertifyjs: "{{ url_for('static', filename='js/alertifyjs/' + ('alertify' if config.DEBUG else 'alertify.min')) }}",
'pgadmin.alertifyjs': "{{ url_for('static', filename='js/alertifyjs/pgadmin.defaults') }}",
backbone: "{{ url_for('static', filename='js/' + ('backbone' if config.DEBUG else 'backbone-min')) }}",
"backbone.paginator": "{{ url_for('static', filename='js/' + ('backbone.paginator' if config.DEBUG else 'backbone.paginator.min')) }}",
"bootstrap.datepicker": "{{ url_for('static', filename='js/' + ('bootstrap-datepicker' if config.DEBUG else 'bootstrap-datepicker.min')) }}",
"bootstrap.switch": "{{ url_for('static', filename='js/' + ('bootstrap-switch' if config.DEBUG else 'bootstrap-switch.min')) }}",
backform: "{{ url_for('static', filename='js/backform') }}",
backgrid: "{{ url_for('static', filename='js/backgrid/' + ('backgrid' if config.DEBUG else 'backgrid.min')) }}",
"backgrid.select.all": "{{ url_for('static', filename='js/backgrid/' + ('backgrid-select-all' if config.DEBUG else 'backgrid-select-all.min')) }}",
"backgrid.paginator": "{{ url_for('static', filename='js/backgrid/' + ('backgrid-paginator' if config.DEBUG else 'backgrid-paginator.min')) }}",
"backgrid.filter": "{{ url_for('static', filename='js/backgrid/' + ('backgrid-filter' if config.DEBUG else 'backgrid-filter.min')) }}",
"backbone.undo": "{{ url_for('static', filename='js/' + ('backbone.undo' if config.DEBUG else 'backbone.undo.min')) }}",
"pgadmin.backgrid": "{{ url_for('static', filename='js/backgrid/backgrid.pgadmin') }}",
'pgadmin.backform': "{{ url_for('static', filename='js/backform.pgadmin') }}"{% for script in current_app.javascripts %},