From 1063a2c87e3acb31808bf75025d090c5d14bcdd3 Mon Sep 17 00:00:00 2001 From: Fergal Moran Date: Mon, 17 Jun 2013 12:16:42 +0100 Subject: [PATCH] Added missing backgrid files --- static/js/libs/backgrid/backgrid.css | 215 ++ static/js/libs/backgrid/backgrid.js | 2531 +++++++++++++++++ static/js/libs/backgrid/backgrid.min.css | 8 + static/js/libs/backgrid/backgrid.min.js | 8 + .../extensions/filter/backgrid-filter.css | 19 + .../extensions/filter/backgrid-filter.js | 365 +++ .../extensions/filter/backgrid-filter.min.css | 8 + .../extensions/filter/backgrid-filter.min.js | 8 + .../moment-cell/backgrid-moment-cell.css | 14 + .../moment-cell/backgrid-moment-cell.js | 175 ++ .../moment-cell/backgrid-moment-cell.min.css | 8 + .../moment-cell/backgrid-moment-cell.min.js | 8 + .../paginator/backgrid-paginator.css | 58 + .../paginator/backgrid-paginator.js | 198 ++ .../paginator/backgrid-paginator.min.css | 8 + .../paginator/backgrid-paginator.min.js | 8 + .../select-all/backgrid-select-all.css | 11 + .../select-all/backgrid-select-all.js | 215 ++ .../select-all/backgrid-select-all.min.css | 8 + .../select-all/backgrid-select-all.min.js | 8 + .../select2-cell/backgrid-select2-cell.css | 19 + .../select2-cell/backgrid-select2-cell.js | 121 + .../backgrid-select2-cell.min.css | 8 + .../select2-cell/backgrid-select2-cell.min.js | 8 + .../text-cell/backgrid-text-cell.css | 31 + .../text-cell/backgrid-text-cell.js | 159 ++ .../text-cell/backgrid-text-cell.min.css | 8 + .../text-cell/backgrid-text-cell.min.js | 8 + 28 files changed, 4243 insertions(+) create mode 100644 static/js/libs/backgrid/backgrid.css create mode 100644 static/js/libs/backgrid/backgrid.js create mode 100644 static/js/libs/backgrid/backgrid.min.css create mode 100644 static/js/libs/backgrid/backgrid.min.js create mode 100644 static/js/libs/backgrid/extensions/filter/backgrid-filter.css create mode 100644 static/js/libs/backgrid/extensions/filter/backgrid-filter.js create mode 100644 static/js/libs/backgrid/extensions/filter/backgrid-filter.min.css create mode 100644 static/js/libs/backgrid/extensions/filter/backgrid-filter.min.js create mode 100644 static/js/libs/backgrid/extensions/moment-cell/backgrid-moment-cell.css create mode 100644 static/js/libs/backgrid/extensions/moment-cell/backgrid-moment-cell.js create mode 100644 static/js/libs/backgrid/extensions/moment-cell/backgrid-moment-cell.min.css create mode 100644 static/js/libs/backgrid/extensions/moment-cell/backgrid-moment-cell.min.js create mode 100644 static/js/libs/backgrid/extensions/paginator/backgrid-paginator.css create mode 100644 static/js/libs/backgrid/extensions/paginator/backgrid-paginator.js create mode 100644 static/js/libs/backgrid/extensions/paginator/backgrid-paginator.min.css create mode 100644 static/js/libs/backgrid/extensions/paginator/backgrid-paginator.min.js create mode 100644 static/js/libs/backgrid/extensions/select-all/backgrid-select-all.css create mode 100644 static/js/libs/backgrid/extensions/select-all/backgrid-select-all.js create mode 100644 static/js/libs/backgrid/extensions/select-all/backgrid-select-all.min.css create mode 100644 static/js/libs/backgrid/extensions/select-all/backgrid-select-all.min.js create mode 100644 static/js/libs/backgrid/extensions/select2-cell/backgrid-select2-cell.css create mode 100644 static/js/libs/backgrid/extensions/select2-cell/backgrid-select2-cell.js create mode 100644 static/js/libs/backgrid/extensions/select2-cell/backgrid-select2-cell.min.css create mode 100644 static/js/libs/backgrid/extensions/select2-cell/backgrid-select2-cell.min.js create mode 100644 static/js/libs/backgrid/extensions/text-cell/backgrid-text-cell.css create mode 100644 static/js/libs/backgrid/extensions/text-cell/backgrid-text-cell.js create mode 100644 static/js/libs/backgrid/extensions/text-cell/backgrid-text-cell.min.css create mode 100644 static/js/libs/backgrid/extensions/text-cell/backgrid-text-cell.min.js diff --git a/static/js/libs/backgrid/backgrid.css b/static/js/libs/backgrid/backgrid.css new file mode 100644 index 0000000..8aaa384 --- /dev/null +++ b/static/js/libs/backgrid/backgrid.css @@ -0,0 +1,215 @@ +/* + backgrid + http://github.com/wyuenho/backgrid + + Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors + Licensed under the MIT @license. +*/ + +.backgrid-container { + position: relative; + display: block; + width: 100%; + height: 465px; + padding: 0; + overflow: auto; + border: 0; +} + +.backgrid { + width: 100%; + max-width: 100%; + background-color: transparent; + border: 1px solid #DDD; + border-collapse: collapse; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} + +.backgrid th, +.backgrid td { + height: 20px; + max-width: 250px; + padding: 4px 5px; + overflow: hidden; + line-height: 20px; + text-align: left; + text-overflow: ellipsis; + white-space: nowrap; + vertical-align: middle; + border: 1px solid #DDD; +} + +.backgrid th { + font-weight: bold; + cursor: pointer; +} + +.backgrid th a { + white-space: nowrap; +} + +.backgrid thead th { + vertical-align: bottom; + background-color: #f9f9f9; +} + +.backgrid.backgrid-striped tbody tr:nth-child(odd) td, +.backgrid.backgrid-striped tbody tr:nth-child(odd) th { + background-color: #f9f9f9; +} + +.backgrid tbody tr.empty { + font-style: italic; + color: gray; +} + +.backgrid tbody tr.empty td { + text-align: center; +} + +.backgrid td.editor, +.backgrid tbody tr:nth-child(odd) td.editor { + background-color: rgba(82, 168, 236, 0.1); + outline: 1px solid rgba(82, 168, 236, 0.8); + outline-offset: -1px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + -webkit-transition-duration: 200ms; + -moz-transition-duration: 200ms; + -o-transition-duration: 200ms; + transition-duration: 200ms; + -webkit-transition-property: width, outline, background-color; + -moz-transition-property: width, outline, background-color; + -o-transition-property: width, outline, background-color; + transition-property: width, outline, background-color; + -webkit-transition-timing-function: ease-in-out; + -moz-transition-timing-function: ease-in-out; + -o-transition-timing-function: ease-in-out; + transition-timing-function: ease-in-out; +} + +.backgrid td.editor input[type=text] { + display: block; + width: 100%; + height: 100%; + padding: 0; + margin: 0; + background-color: transparent; + border: 0; + outline: 0; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + -webkit-appearance: none; + -moz-appearance: none; +} + +.backgrid td.error, +.backgrid tbody tr:nth-child(odd) td.error { + background-color: rgba(255, 210, 77, 0.1); + outline: 1px solid #ffd24d; +} + +.backgrid td.editor :focus, +.backgrid th.editor :focus { + outline: 0; +} + +.backgrid .sort-caret { + display: inline-block; + width: 0; + height: 0; + margin-left: 0.3em; + border: 0; + content: ""; +} + +.backgrid .ascending .sort-caret { + vertical-align: baseline; + border-top: none; + border-right: 4px solid transparent; + border-bottom: 4px solid #000000; + border-left: 4px solid transparent; +} + +.backgrid .descending .sort-caret { + vertical-align: super; + border-top: 4px solid #000000; + border-right: 4px solid transparent; + border-bottom: none; + border-left: 4px solid transparent; +} + +.backgrid .string-cell, +.backgrid .uri-cell, +.backgrid .email-cell, +.backgrid .string-cell.editor input[type=text], +.backgrid .uri-cell.editor input[type=text], +.backgrid .email-cell.editor input[type=text] { + text-align: left; +} + +.backgrid .date-cell, +.backgrid .time-cell, +.backgrid .datetime-cell, +.backgrid .number-cell, +.backgrid .integer-cell, +.backgrid .date-cell.editor input[type=text], +.backgrid .time-cell.editor input[type=text], +.backgrid .datetime-cell.editor input[type=text], +.backgrid .number-cell.editor input[type=text], +.backgrid .integer-cell.editor input[type=text] { + text-align: right; +} + +.backgrid .boolean-cell, +.backgrid .boolean-cell.editor input[type=checkbox] { + text-align: center; +} + +.backgrid .select-cell { + text-align: center; +} + +.backgrid .select-cell.editor { + padding: 0; +} + +.backgrid .select-cell.editor select { + display: block; + width: 100%; + height: 28px; + padding: 4px 5px; + margin: 0; + line-height: 28px; + vertical-align: middle; + background-color: white; + border: 0; + outline: 0; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.backgrid .select-cell.editor :focus { + border: 0; + outline: 0; +} + +.backgrid .select-cell.editor select::-moz-focus-inner, +.backgrid .select-cell.editor optgroup::-moz-focus-inner, +.backgrid .select-cell.editor option::-moz-focus-inner, +.backgrid .select-cell.editor select::-o-focus-inner, +.backgrid .select-cell.editor optgroup::-o-focus-inner, +.backgrid .select-cell.editor option::-o-focus-inner { + border: 0; +} diff --git a/static/js/libs/backgrid/backgrid.js b/static/js/libs/backgrid/backgrid.js new file mode 100644 index 0000000..387e93e --- /dev/null +++ b/static/js/libs/backgrid/backgrid.js @@ -0,0 +1,2531 @@ +/* + backgrid + http://github.com/wyuenho/backgrid + + Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors + Licensed under the MIT @license. +*/ +(function (root, $, _, Backbone) { + + "use strict"; +/* + backgrid + http://github.com/wyuenho/backgrid + + Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors + Licensed under the MIT @license. +*/ + +var window = root; + +// Copyright 2009, 2010 Kristopher Michael Kowal +// https://github.com/kriskowal/es5-shim +// ES5 15.5.4.20 +// http://es5.github.com/#x15.5.4.20 +var ws = "\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003" + + "\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028" + + "\u2029\uFEFF"; +if (!String.prototype.trim || ws.trim()) { + // http://blog.stevenlevithan.com/archives/faster-trim-javascript + // http://perfectionkills.com/whitespace-deviations/ + ws = "[" + ws + "]"; + var trimBeginRegexp = new RegExp("^" + ws + ws + "*"), + trimEndRegexp = new RegExp(ws + ws + "*$"); + String.prototype.trim = function trim() { + if (this === undefined || this === null) { + throw new TypeError("can't convert " + this + " to object"); + } + return String(this) + .replace(trimBeginRegexp, "") + .replace(trimEndRegexp, ""); + }; +} + +function capitalize(s) { + return String.fromCharCode(s.charCodeAt(0) - 32) + s.slice(1); +} + +function lpad(str, length, padstr) { + var paddingLen = length - (str + '').length; + paddingLen = paddingLen < 0 ? 0 : paddingLen; + var padding = ''; + for (var i = 0; i < paddingLen; i++) { + padding = padding + padstr; + } + return padding + str; +} + +var Backgrid = root.Backgrid = { + + VERSION: "0.2.6", + + Extension: {}, + + requireOptions: function (options, requireOptionKeys) { + for (var i = 0; i < requireOptionKeys.length; i++) { + var key = requireOptionKeys[i]; + if (_.isUndefined(options[key])) { + throw new TypeError("'" + key + "' is required"); + } + } + }, + + resolveNameToClass: function (name, suffix) { + if (_.isString(name)) { + 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"); + } + return klass; + } + + return name; + } +}; +_.extend(Backgrid, Backbone.Events); + +/** + Command translates a DOM Event into commands that Backgrid + recognizes. Interested parties can listen on selected Backgrid events that + come with an instance of this class and act on the commands. + + It is also possible to globally rebind the keyboard shortcuts by replacing + the methods in this class' prototype. + + @class Backgrid.Command + @constructor + */ +var Command = Backgrid.Command = function (evt) { + _.extend(this, { + altKey: !!evt.altKey, + char: evt.char, + charCode: evt.charCode, + ctrlKey: !!evt.ctrlKey, + key: evt.key, + keyCode: evt.keyCode, + locale: evt.locale, + location: evt.location, + metaKey: !!evt.metaKey, + repeat: !!evt.repeat, + shiftKey: !!evt.shiftKey, + which: evt.which + }); +}; +_.extend(Command.prototype, { + /** + Up Arrow + + @member Backgrid.Command + */ + moveUp: function () { return this.keyCode == 38; }, + /** + Down Arrow + + @member Backgrid.Command + */ + moveDown: function () { return this.keyCode === 40; }, + /** + Shift Tab + + @member Backgrid.Command + */ + moveLeft: function () { return this.shiftKey && this.keyCode === 9; }, + /** + Tab + + @member Backgrid.Command + */ + moveRight: function () { return !this.shiftKey && this.keyCode === 9; }, + /** + Enter + + @member Backgrid.Command + */ + save: function () { return this.keyCode === 13; }, + /** + Esc + + @member Backgrid.Command + */ + cancel: function () { return this.keyCode === 27; }, + /** + None of the above. + + @member Backgrid.Command + */ + passThru: function () { + return !(this.moveUp() || this.moveDown() || this.moveLeft() || + this.moveRight() || this.save() || this.cancel()); + } +}); + +/* + backgrid + http://github.com/wyuenho/backgrid + + Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors + Licensed under the MIT @license. +*/ + +/** + Just a convenient class for interested parties to subclass. + + The default Cell classes don't require the formatter to be a subclass of + Formatter as long as the fromRaw(rawData) and toRaw(formattedData) methods + are defined. + + @abstract + @class Backgrid.CellFormatter + @constructor +*/ +var CellFormatter = Backgrid.CellFormatter = function () {}; +_.extend(CellFormatter.prototype, { + + /** + Takes a raw value from a model and returns an optionally formatted string + for display. The default implementation simply returns the supplied value + as is without any type conversion. + + @member Backgrid.CellFormatter + @param {*} rawData + @return {*} + */ + fromRaw: function (rawData) { + return rawData; + }, + + /** + Takes a formatted string, usually from user input, and returns a + appropriately typed value for persistence in the model. + + If the user input is invalid or unable to be converted to a raw value + suitable for persistence in the model, toRaw must return `undefined`. + + @member Backgrid.CellFormatter + @param {string} formattedData + @return {*|undefined} + */ + toRaw: function (formattedData) { + return formattedData; + } + +}); + +/** + A floating point number formatter. Doesn't understand notation at the moment. + + @class Backgrid.NumberFormatter + @extends Backgrid.CellFormatter + @constructor + @throws {RangeError} If decimals < 0 or > 20. +*/ +var NumberFormatter = Backgrid.NumberFormatter = function (options) { + options = options ? _.clone(options) : {}; + _.extend(this, this.defaults, options); + + if (this.decimals < 0 || this.decimals > 20) { + throw new RangeError("decimals must be between 0 and 20"); + } +}; +NumberFormatter.prototype = new CellFormatter(); +_.extend(NumberFormatter.prototype, { + + /** + @member Backgrid.NumberFormatter + @cfg {Object} options + + @cfg {number} [options.decimals=2] Number of decimals to display. Must be an integer. + + @cfg {string} [options.decimalSeparator='.'] The separator to use when + displaying decimals. + + @cfg {string} [options.orderSeparator=','] The separator to use to + separator thousands. May be an empty string. + */ + defaults: { + decimals: 2, + decimalSeparator: '.', + orderSeparator: ',' + }, + + HUMANIZED_NUM_RE: /(\d)(?=(?:\d{3})+$)/g, + + /** + Takes a floating point number and convert it to a formatted string where + every thousand is separated by `orderSeparator`, with a `decimal` number of + decimals separated by `decimalSeparator`. The number returned is rounded + the usual way. + + @member Backgrid.NumberFormatter + @param {number} number + @return {string} + */ + fromRaw: function (number) { + if (_.isNull(number) || _.isUndefined(number)) return ''; + + number = number.toFixed(~~this.decimals); + + var parts = number.split('.'); + var integerPart = parts[0]; + var decimalPart = parts[1] ? (this.decimalSeparator || '.') + parts[1] : ''; + + return integerPart.replace(this.HUMANIZED_NUM_RE, '$1' + this.orderSeparator) + decimalPart; + }, + + /** + Takes a string, possibly formatted with `orderSeparator` and/or + `decimalSeparator`, and convert it back to a number. + + @member Backgrid.NumberFormatter + @param {string} formattedData + @return {number|undefined} Undefined if the string cannot be converted to + a number. + */ + toRaw: function (formattedData) { + var rawData = ''; + + var thousands = formattedData.trim().split(this.orderSeparator); + for (var i = 0; i < thousands.length; i++) { + rawData += thousands[i]; + } + + var decimalParts = rawData.split(this.decimalSeparator); + rawData = ''; + for (var i = 0; i < decimalParts.length; i++) { + rawData = rawData + decimalParts[i] + '.'; + } + + if (rawData[rawData.length - 1] === '.') { + rawData = rawData.slice(0, rawData.length - 1); + } + + var result = (rawData * 1).toFixed(~~this.decimals) * 1; + if (_.isNumber(result) && !_.isNaN(result)) return result; + } + +}); + +/** + Formatter to converts between various datetime formats. + + This class only understands ISO-8601 formatted datetime strings and UNIX + offset (number of milliseconds since UNIX Epoch). See + Backgrid.Extension.MomentFormatter if you need a much more flexible datetime + formatter. + + @class Backgrid.DatetimeFormatter + @extends Backgrid.CellFormatter + @constructor + @throws {Error} If both `includeDate` and `includeTime` are false. +*/ +var DatetimeFormatter = Backgrid.DatetimeFormatter = function (options) { + options = options ? _.clone(options) : {}; + _.extend(this, this.defaults, options); + + if (!this.includeDate && !this.includeTime) { + throw new Error("Either includeDate or includeTime must be true"); + } +}; +DatetimeFormatter.prototype = new CellFormatter(); +_.extend(DatetimeFormatter.prototype, { + + /** + @member Backgrid.DatetimeFormatter + + @cfg {Object} options + + @cfg {boolean} [options.includeDate=true] Whether the values include the + date part. + + @cfg {boolean} [options.includeTime=true] Whether the values include the + time part. + + @cfg {boolean} [options.includeMilli=false] If `includeTime` is true, + whether to include the millisecond part, if it exists. + */ + defaults: { + includeDate: true, + includeTime: true, + includeMilli: false + }, + + DATE_RE: /^([+\-]?\d{4})-(\d{2})-(\d{2})$/, + TIME_RE: /^(\d{2}):(\d{2}):(\d{2})(\.(\d{3}))?$/, + ISO_SPLITTER_RE: /T|Z| +/, + + _convert: function (data, validate) { + var date, time = null; + if (_.isNumber(data)) { + var jsDate = new Date(data); + date = lpad(jsDate.getUTCFullYear(), 4, 0) + '-' + lpad(jsDate.getUTCMonth() + 1, 2, 0) + '-' + lpad(jsDate.getUTCDate(), 2, 0); + time = lpad(jsDate.getUTCHours(), 2, 0) + ':' + lpad(jsDate.getUTCMinutes(), 2, 0) + ':' + lpad(jsDate.getUTCSeconds(), 2, 0); + } + else { + data = data.trim(); + var parts = data.split(this.ISO_SPLITTER_RE) || []; + date = this.DATE_RE.test(parts[0]) ? parts[0] : ''; + time = date && parts[1] ? parts[1] : this.TIME_RE.test(parts[0]) ? parts[0] : ''; + } + + var YYYYMMDD = this.DATE_RE.exec(date) || []; + var HHmmssSSS = this.TIME_RE.exec(time) || []; + + if (validate) { + if (this.includeDate && _.isUndefined(YYYYMMDD[0])) return; + if (this.includeTime && _.isUndefined(HHmmssSSS[0])) return; + if (!this.includeDate && date) return; + if (!this.includeTime && time) return; + } + + var jsDate = new Date(Date.UTC(YYYYMMDD[1] * 1 || 0, + YYYYMMDD[2] * 1 - 1 || 0, + YYYYMMDD[3] * 1 || 0, + HHmmssSSS[1] * 1 || null, + HHmmssSSS[2] * 1 || null, + HHmmssSSS[3] * 1 || null, + HHmmssSSS[5] * 1 || null)); + + var result = ''; + + if (this.includeDate) { + result = lpad(jsDate.getUTCFullYear(), 4, 0) + '-' + lpad(jsDate.getUTCMonth() + 1, 2, 0) + '-' + lpad(jsDate.getUTCDate(), 2, 0); + } + + if (this.includeTime) { + result = result + (this.includeDate ? 'T' : '') + lpad(jsDate.getUTCHours(), 2, 0) + ':' + lpad(jsDate.getUTCMinutes(), 2, 0) + ':' + lpad(jsDate.getUTCSeconds(), 2, 0); + + if (this.includeMilli) { + result = result + '.' + lpad(jsDate.getUTCMilliseconds(), 3, 0); + } + } + + if (this.includeDate && this.includeTime) { + result += "Z"; + } + + return result; + }, + + /** + Converts an ISO-8601 formatted datetime string to a datetime string, date + string or a time string. The timezone is ignored if supplied. + + @member Backgrid.DatetimeFormatter + @param {string} rawData + @return {string|null|undefined} ISO-8601 string in UTC. Null and undefined + values are returned as is. + */ + fromRaw: function (rawData) { + if (_.isNull(rawData) || _.isUndefined(rawData)) return ''; + return this._convert(rawData); + }, + + /** + Converts an ISO-8601 formatted datetime string to a datetime string, date + string or a time string. The timezone is ignored if supplied. This method + parses the input values exactly the same way as + Backgrid.Extension.MomentFormatter#fromRaw(), in addition to doing some + sanity checks. + + @member Backgrid.DatetimeFormatter + @param {string} formattedData + @return {string|undefined} ISO-8601 string in UTC. Undefined if a date is + found when `includeDate` is false, or a time is found when `includeTime` is + false, or if `includeDate` is true and a date is not found, or if + `includeTime` is true and a time is not found. + */ + toRaw: function (formattedData) { + return this._convert(formattedData, true); + } + +}); + +/** + Formatter to convert any value to string. + + @class Backgrid.StringFormatter + @extends Backgrid.CellFormatter + @constructor + */ +var StringFormatter = Backgrid.StringFormatter = function () {}; +StringFormatter.prototype = new CellFormatter(); +_.extend(StringFormatter.prototype, { + /** + Converts any value to a string using Ecmascript's implicit type + conversion. If the given value is `null` or `undefined`, an empty string is + returned instead. + + @member Backgrid.StringFormatter + @param {*} rawValue + @return {string} + */ + fromRaw: function (rawValue) { + if (_.isUndefined(rawValue) || _.isNull(rawValue)) return ''; + return rawValue + ''; + } +}); + +/** + Simple email validation formatter. + + @class Backgrid.EmailFormatter + @extends Backgrid.CellFormatter + @constructor + */ +var EmailFormatter = Backgrid.EmailFormatter = function () {}; +EmailFormatter.prototype = new CellFormatter(); +_.extend(EmailFormatter.prototype, { + /** + Return the input if it is a string that contains an '@' character and if + the strings before and after '@' are non-empty. If the input does not + validate, `undefined` is returned. + + @member Backgrid.EmailFormatter + @param {*} formattedData + @return {string|undefined} + */ + toRaw: function (formattedData) { + var parts = formattedData.trim().split("@"); + if (parts.length === 2 && _.all(parts)) { + return formattedData; + } + } +}); + +/** + Formatter for SelectCell. + + @class Backgrid.SelectFormatter + @extends Backgrid.CellFormatter + @constructor +*/ +var SelectFormatter = Backgrid.SelectFormatter = function () {}; +SelectFormatter.prototype = new CellFormatter(); +_.extend(SelectFormatter.prototype, { + + /** + Normalizes raw scalar or array values to an array. + + @member Backgrid.SelectFormatter + @param {*} rawValue + @return {Array.<*>} + */ + fromRaw: function (rawValue) { + return _.isArray(rawValue) ? rawValue : rawValue != null ? [rawValue] : []; + } +}); + +/* + backgrid + http://github.com/wyuenho/backgrid + + Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors + Licensed under the MIT @license. +*/ + +/** + Generic cell editor base class. Only defines an initializer for a number of + required parameters. + + @abstract + @class Backgrid.CellEditor + @extends Backbone.View +*/ +var CellEditor = Backgrid.CellEditor = Backbone.View.extend({ + + /** + Initializer. + + @param {Object} options + @param {Backgrid.CellFormatter} options.formatter + @param {Backgrid.Column} options.column + @param {Backbone.Model} options.model + + @throws {TypeError} If `formatter` is not a formatter instance, or when + `model` or `column` are undefined. + */ + initialize: function (options) { + Backgrid.requireOptions(options, ["formatter", "column", "model"]); + this.formatter = options.formatter; + this.column = options.column; + if (!(this.column instanceof Column)) { + this.column = new Column(this.column); + } + + this.listenTo(this.model, "backgrid:editing", this.postRender); + }, + + /** + Post-rendering setup and initialization. Focuses the cell editor's `el` in + this default implementation. **Should** be called by Cell classes after + calling Backgrid.CellEditor#render. + */ + postRender: function (model, column) { + if (column == null || column.get("name") == this.column.get("name")) { + this.$el.focus(); + } + return this; + } + +}); + +/** + InputCellEditor the cell editor type used by most core cell types. This cell + editor renders a text input box as its editor. The input will render a + placeholder if the value is empty on supported browsers. + + @class Backgrid.InputCellEditor + @extends Backgrid.CellEditor +*/ +var InputCellEditor = Backgrid.InputCellEditor = CellEditor.extend({ + + /** @property */ + tagName: "input", + + /** @property */ + attributes: { + type: "text" + }, + + /** @property */ + events: { + "blur": "saveOrCancel", + "keydown": "saveOrCancel" + }, + + /** + Initializer. Removes this `el` from the DOM when a `done` event is + triggered. + + @param {Object} options + @param {Backgrid.CellFormatter} options.formatter + @param {Backgrid.Column} options.column + @param {Backbone.Model} options.model + @param {string} [options.placeholder] + */ + initialize: function (options) { + CellEditor.prototype.initialize.apply(this, arguments); + + if (options.placeholder) { + this.$el.attr("placeholder", options.placeholder); + } + }, + + /** + Renders a text input with the cell value formatted for display, if it + exists. + */ + render: function () { + this.$el.val(this.formatter.fromRaw(this.model.get(this.column.get("name")))); + return this; + }, + + /** + If the key pressed is `enter`, `tab`, `up`, or `down`, converts the value + in the editor to a raw value for saving into the model using the formatter. + + If the key pressed is `esc` the changes are undone. + + If the editor goes out of focus (`blur`) but the value is invalid, the + event is intercepted and cancelled so the cell remains in focus pending for + further action. The changes are saved otherwise. + + Triggers a Backbone `backgrid:edited` event from the model when successful, + and `backgrid:error` if the value cannot be converted. Classes listening to + the `error` event, usually the Cell classes, should respond appropriately, + usually by rendering some kind of error feedback. + + @param {Event} e + */ + saveOrCancel: function (e) { + + var formatter = this.formatter; + var model = this.model; + var column = this.column; + + var command = new Command(e); + var blurred = e.type === "blur"; + + if (command.moveUp() || command.moveDown() || command.moveLeft() || command.moveRight() || + command.save() || blurred) { + + e.preventDefault(); + e.stopPropagation(); + + var val = this.$el.val(); + var newValue = formatter.toRaw(val); + if (_.isUndefined(newValue)) { + model.trigger("backgrid:error", model, column, val); + } + else { + model.set(column.get("name"), newValue); + model.trigger("backgrid:edited", model, column, command); + } + } + // esc + else if (command.cancel()) { + // undo + e.stopPropagation(); + model.trigger("backgrid:edited", model, column, command); + } + }, + + postRender: function (model, column) { + if (column == null || column.get("name") == this.column.get("name")) { + // move the cursor to the end on firefox if text is right aligned + if (this.$el.css("text-align") === "right") { + var val = this.$el.val(); + this.$el.focus().val(null).val(val); + } + else this.$el.focus(); + } + return this; + } + +}); + +/** + The super-class for all Cell types. By default, this class renders a plain + table cell with the model value converted to a string using the + formatter. The table cell is clickable, upon which the cell will go into + editor mode, which is rendered by a Backgrid.InputCellEditor instance by + default. Upon encountering any formatting errors, this class will add an + `error` CSS class to the table cell. + + @abstract + @class Backgrid.Cell + @extends Backbone.View +*/ +var Cell = Backgrid.Cell = Backbone.View.extend({ + + /** @property */ + tagName: "td", + + /** + @property {Backgrid.CellFormatter|Object|string} [formatter=new CellFormatter()] + */ + formatter: new CellFormatter(), + + /** + @property {Backgrid.CellEditor} [editor=Backgrid.InputCellEditor] The + default editor for all cell instances of this class. This value must be a + class, it will be automatically instantiated upon entering edit mode. + + See Backgrid.CellEditor + */ + editor: InputCellEditor, + + /** @property */ + events: { + "click": "enterEditMode" + }, + + /** + Initializer. + + @param {Object} options + @param {Backbone.Model} options.model + @param {Backgrid.Column} options.column + + @throws {ReferenceError} If formatter is a string but a formatter class of + said name cannot be found in the Backgrid module. + */ + initialize: function (options) { + Backgrid.requireOptions(options, ["model", "column"]); + this.column = options.column; + if (!(this.column instanceof Column)) { + this.column = new Column(this.column); + } + this.formatter = Backgrid.resolveNameToClass(this.column.get("formatter") || this.formatter, "Formatter"); + this.editor = Backgrid.resolveNameToClass(this.editor, "CellEditor"); + this.listenTo(this.model, "change:" + this.column.get("name"), function () { + if (!this.$el.hasClass("editor")) this.render(); + }); + this.listenTo(this.model, "backgrid:error", this.renderError); + }, + + /** + Render a text string in a table cell. The text is converted from the + model's raw value for this cell's column. + */ + render: function () { + this.$el.empty(); + this.$el.text(this.formatter.fromRaw(this.model.get(this.column.get("name")))); + this.delegateEvents(); + return this; + }, + + /** + If this column is editable, a new CellEditor instance is instantiated with + its required parameters. An `editor` CSS class is added to the cell upon + entering edit mode. + + This method triggers a Backbone `backgrid:edit` event from the model when + the cell is entering edit mode and an editor instance has been constructed, + but before it is rendered and inserted into the DOM. The cell and the + constructed cell editor instance are sent as event parameters when this + event is triggered. + + When this cell has finished switching to edit mode, a Backbone + `backgrid:editing` event is triggered from the model. The cell and the + constructed cell instance are also sent as parameters in the event. + + When the model triggers a `backgrid:error` event, it means the editor is + unable to convert the current user input to an apprpriate value for the + model's column, and an `error` CSS class is added to the cell accordingly. + */ + enterEditMode: function () { + var model = this.model; + var column = this.column; + + if (column.get("editable")) { + + this.currentEditor = new this.editor({ + column: this.column, + model: this.model, + formatter: this.formatter + }); + + model.trigger("backgrid:edit", model, column, this, this.currentEditor); + + // Need to redundantly undelegate events for Firefox + this.undelegateEvents(); + this.$el.empty(); + this.$el.append(this.currentEditor.$el); + this.currentEditor.render(); + this.$el.addClass("editor"); + + model.trigger("backgrid:editing", model, column, this, this.currentEditor); + } + }, + + /** + Put an `error` CSS class on the table cell. + */ + renderError: function (model, column) { + if (column == null || column.get("name") == this.column.get("name")) { + this.$el.addClass("error"); + } + }, + + /** + Removes the editor and re-render in display mode. + */ + exitEditMode: function () { + this.$el.removeClass("error"); + this.currentEditor.remove(); + this.stopListening(this.currentEditor); + delete this.currentEditor; + this.$el.removeClass("editor"); + this.render(); + }, + + /** + Clean up this cell. + + @chainable + */ + remove: function () { + if (this.currentEditor) { + this.currentEditor.remove.apply(this, arguments); + delete this.currentEditor; + } + return Backbone.View.prototype.remove.apply(this, arguments); + } + +}); + +/** + StringCell displays HTML escaped strings and accepts anything typed in. + + @class Backgrid.StringCell + @extends Backgrid.Cell +*/ +var StringCell = Backgrid.StringCell = Cell.extend({ + + /** @property */ + className: "string-cell", + + formatter: new StringFormatter() + +}); + +/** + UriCell renders an HTML `` anchor for the value and accepts URIs as user + input values. No type conversion or URL validation is done by the formatter + of this cell. Users who need URL validation are encourage to subclass UriCell + to take advantage of the parsing capabilities of the HTMLAnchorElement + available on HTML5-capable browsers or using a third-party library like + [URI.js](https://github.com/medialize/URI.js). + + @class Backgrid.UriCell + @extends Backgrid.Cell +*/ +var UriCell = Backgrid.UriCell = Cell.extend({ + + /** @property */ + className: "uri-cell", + + render: function () { + this.$el.empty(); + var formattedValue = this.formatter.fromRaw(this.model.get(this.column.get("name"))); + this.$el.append($("", { + tabIndex: -1, + href: formattedValue, + title: formattedValue, + target: "_blank" + }).text(formattedValue)); + this.delegateEvents(); + return this; + } + +}); + +/** + Like Backgrid.UriCell, EmailCell renders an HTML `` anchor for the + value. The `href` in the anchor is prefixed with `mailto:`. EmailCell will + complain if the user enters a string that doesn't contain the `@` sign. + + @class Backgrid.EmailCell + @extends Backgrid.StringCell +*/ +var EmailCell = Backgrid.EmailCell = StringCell.extend({ + + /** @property */ + className: "email-cell", + + formatter: new EmailFormatter(), + + render: function () { + this.$el.empty(); + var formattedValue = this.formatter.fromRaw(this.model.get(this.column.get("name"))); + this.$el.append($("", { + tabIndex: -1, + href: "mailto:" + formattedValue, + title: formattedValue + }).text(formattedValue)); + this.delegateEvents(); + return this; + } + +}); + +/** + NumberCell is a generic cell that renders all numbers. Numbers are formatted + using a Backgrid.NumberFormatter. + + @class Backgrid.NumberCell + @extends Backgrid.Cell +*/ +var NumberCell = Backgrid.NumberCell = Cell.extend({ + + /** @property */ + className: "number-cell", + + /** + @property {number} [decimals=2] Must be an integer. + */ + decimals: NumberFormatter.prototype.defaults.decimals, + + /** @property {string} [decimalSeparator='.'] */ + decimalSeparator: NumberFormatter.prototype.defaults.decimalSeparator, + + /** @property {string} [orderSeparator=','] */ + orderSeparator: NumberFormatter.prototype.defaults.orderSeparator, + + /** @property {Backgrid.CellFormatter} [formatter=Backgrid.NumberFormatter] */ + formatter: NumberFormatter, + + /** + Initializes this cell and the number formatter. + + @param {Object} options + @param {Backbone.Model} options.model + @param {Backgrid.Column} options.column + */ + initialize: function (options) { + Cell.prototype.initialize.apply(this, arguments); + this.formatter = new this.formatter({ + decimals: this.decimals, + decimalSeparator: this.decimalSeparator, + orderSeparator: this.orderSeparator + }); + } + +}); + +/** + An IntegerCell is just a Backgrid.NumberCell with 0 decimals. If a floating + point number is supplied, the number is simply rounded the usual way when + displayed. + + @class Backgrid.IntegerCell + @extends Backgrid.NumberCell +*/ +var IntegerCell = Backgrid.IntegerCell = NumberCell.extend({ + + /** @property */ + className: "integer-cell", + + /** + @property {number} decimals Must be an integer. + */ + decimals: 0 +}); + +/** + DatetimeCell is a basic cell that accepts datetime string values in RFC-2822 + or W3C's subset of ISO-8601 and displays them in ISO-8601 format. For a much + more sophisticated date time cell with better datetime formatting, take a + look at the Backgrid.Extension.MomentCell extension. + + @class Backgrid.DatetimeCell + @extends Backgrid.Cell + + See: + + - Backgrid.Extension.MomentCell + - Backgrid.DatetimeFormatter +*/ +var DatetimeCell = Backgrid.DatetimeCell = Cell.extend({ + + /** @property */ + className: "datetime-cell", + + /** + @property {boolean} [includeDate=true] + */ + includeDate: DatetimeFormatter.prototype.defaults.includeDate, + + /** + @property {boolean} [includeTime=true] + */ + includeTime: DatetimeFormatter.prototype.defaults.includeTime, + + /** + @property {boolean} [includeMilli=false] + */ + includeMilli: DatetimeFormatter.prototype.defaults.includeMilli, + + /** @property {Backgrid.CellFormatter} [formatter=Backgrid.DatetimeFormatter] */ + formatter: DatetimeFormatter, + + /** + Initializes this cell and the datetime formatter. + + @param {Object} options + @param {Backbone.Model} options.model + @param {Backgrid.Column} options.column + */ + initialize: function (options) { + Cell.prototype.initialize.apply(this, arguments); + this.formatter = new this.formatter({ + includeDate: this.includeDate, + includeTime: this.includeTime, + includeMilli: this.includeMilli + }); + + var placeholder = this.includeDate ? "YYYY-MM-DD" : ""; + placeholder += (this.includeDate && this.includeTime) ? "T" : ""; + placeholder += this.includeTime ? "HH:mm:ss" : ""; + placeholder += (this.includeTime && this.includeMilli) ? ".SSS" : ""; + + this.editor = this.editor.extend({ + attributes: _.extend({}, this.editor.prototype.attributes, this.editor.attributes, { + placeholder: placeholder + }) + }); + } + +}); + +/** + DateCell is a Backgrid.DatetimeCell without the time part. + + @class Backgrid.DateCell + @extends Backgrid.DatetimeCell +*/ +var DateCell = Backgrid.DateCell = DatetimeCell.extend({ + + /** @property */ + className: "date-cell", + + /** @property */ + includeTime: false + +}); + +/** + TimeCell is a Backgrid.DatetimeCell without the date part. + + @class Backgrid.TimeCell + @extends Backgrid.DatetimeCell +*/ +var TimeCell = Backgrid.TimeCell = DatetimeCell.extend({ + + /** @property */ + className: "time-cell", + + /** @property */ + includeDate: false + +}); + +/** + BooleanCellEditor renders a checkbox as its editor. + + @class Backgrid.BooleanCellEditor + @extends Backgrid.CellEditor +*/ +var BooleanCellEditor = Backgrid.BooleanCellEditor = CellEditor.extend({ + + /** @property */ + tagName: "input", + + /** @property */ + attributes: { + tabIndex: -1, + type: "checkbox" + }, + + /** @property */ + events: { + "mousedown": function () { + this.mouseDown = true; + }, + "blur": "enterOrExitEditMode", + "mouseup": function () { + this.mouseDown = false; + }, + "change": "saveOrCancel", + "keydown": "saveOrCancel" + }, + + /** + Renders a checkbox and check it if the model value of this column is true, + uncheck otherwise. + */ + render: function () { + var val = this.formatter.fromRaw(this.model.get(this.column.get("name"))); + this.$el.prop("checked", val); + return this; + }, + + /** + Event handler. Hack to deal with the case where `blur` is fired before + `change` and `click` on a checkbox. + */ + enterOrExitEditMode: function (e) { + if (!this.mouseDown) { + var model = this.model; + model.trigger("backgrid:edited", model, this.column, new Command(e)); + } + }, + + /** + Event handler. Save the value into the model if the event is `change` or + one of the keyboard navigation key presses. Exit edit mode without saving + if `escape` was pressed. + */ + saveOrCancel: function (e) { + var model = this.model; + var column = this.column; + var formatter = this.formatter; + var command = new Command(e); + // skip ahead to `change` when space is pressed + if (command.passThru() && e.type != "change") return true; + if (command.cancel()) { + e.stopPropagation(); + model.trigger("backgrid:edited", model, column, command); + } + + var $el = this.$el; + if (command.save() || command.moveLeft() || command.moveRight() || command.moveUp() || + command.moveDown()) { + e.preventDefault(); + e.stopPropagation(); + var val = formatter.toRaw($el.prop("checked")); + model.set(column.get("name"), val); + model.trigger("backgrid:edited", model, column, command); + } + else if (e.type == "change") { + var val = formatter.toRaw($el.prop("checked")); + model.set(column.get("name"), val); + $el.focus(); + } + } + +}); + +/** + BooleanCell renders a checkbox both during display mode and edit mode. The + checkbox is checked if the model value is true, unchecked otherwise. + + @class Backgrid.BooleanCell + @extends Backgrid.Cell +*/ +var BooleanCell = Backgrid.BooleanCell = Cell.extend({ + + /** @property */ + className: "boolean-cell", + + /** @property */ + editor: BooleanCellEditor, + + /** @property */ + events: { + "click": "enterEditMode" + }, + + /** + Renders a checkbox and check it if the model value of this column is true, + uncheck otherwise. + */ + render: function () { + this.$el.empty(); + this.$el.append($("", { + tabIndex: -1, + type: "checkbox", + checked: this.formatter.fromRaw(this.model.get(this.column.get("name"))) + })); + this.delegateEvents(); + return this; + } + +}); + +/** + SelectCellEditor renders an HTML `",{tabIndex:-1,type:"checkbox",checked:this.formatter.fromRaw(this.model.get(this.column.get("name")))})),this.delegateEvents(),this}});var T=h.SelectCellEditor=g.extend({tagName:"select",events:{change:"save",blur:"close",keydown:"close"},template:i.template(''),setOptionValues:function(e){this.optionValues=e},setMultiple:function(e){this.multiple=e,this.$el.prop("multiple",e)},_renderOptions:function(e,t){for(var i="",n=0;n-1});return i},render:function(){this.$el.empty();var e=i.result(this,"optionValues"),n=this.formatter.fromRaw(this.model.get(this.column.get("name")));if(!i.isArray(e))throw TypeError("optionValues must be an array");for(var r=null,o=null,r=null,s=null,l=null,a=0;a-1}));else{if(!i.isObject(r))throw TypeError("optionValues elements must be a name-value pair or an object hash of { name: 'optgroup label', value: [option name-value pairs] }");s=r.name,l=t("",{label:s}),l.append(this._renderOptions(r.values,n)),this.$el.append(l)}}return this.delegateEvents(),this},save:function(e){var t=this.model,i=this.column;t.set(i.get("name"),this.formatter.toRaw(this.$el.val())),t.trigger("backgrid:edited",t,i,new c(e))},close:function(e){var t=this.model,i=this.column,n=new c(e);n.cancel()?(e.stopPropagation(),t.trigger("backgrid:edited",t,i,new c(e))):(n.save()||n.moveLeft()||n.moveRight()||n.moveUp()||n.moveDown()||e.type=="blur")&&(e.preventDefault(),e.stopPropagation(),e.type=="blur"&&this.$el.find("option").length===1&&t.set(i.get("name"),this.formatter.toRaw(this.$el.val())),t.trigger("backgrid:edited",t,i,new c(e)))}});h.SelectCell=y.extend({className:"select-cell",editor:T,multiple:!1,formatter:new v,optionValues:void 0,delimiter:", ",initialize:function(){y.prototype.initialize.apply(this,arguments),h.requireOptions(this,["optionValues"]),this.listenTo(this.model,"backgrid:edit",function(e,t,i,n){t.get("name")==this.column.get("name")&&(n.setOptionValues(this.optionValues),n.setMultiple(this.multiple))})},render:function(){this.$el.empty();var e=this.optionValues,t=this.formatter.fromRaw(this.model.get(this.column.get("name"))),n=[];try{if(!i.isArray(e)||i.isEmpty(e))throw new TypeError;for(var r=0;r|Array.<{name: string, values: Array.}>}");throw u}return this.delegateEvents(),this}});var R=h.Column=n.Model.extend({defaults:{name:void 0,label:void 0,sortable:!0,editable:!0,renderable:!0,formatter:void 0,cell:void 0,headerCell:void 0},initialize:function(e){h.requireOptions(e,["cell","name"]),this.has("label")||this.set({label:this.get("name")},{silent:!0});var t=h.resolveNameToClass(this.get("headerCell"),"HeaderCell"),i=h.resolveNameToClass(this.get("cell"),"Cell");this.set({cell:i,headerCell:t},{silent:!0})}}),$=h.Columns=n.Collection.extend({model:R}),k=h.Row=n.View.extend({tagName:"tr",requiredOptions:["columns","model"],initialize:function(e){h.requireOptions(e,this.requiredOptions);var t=this.columns=e.columns;t instanceof n.Collection||(t=this.columns=new $(t));for(var i=this.cells=[],r=0;rr?-1:1}):this.direction()==="descending"?this.sort(t,null):this.sort(t,"ascending",function(e,i){var n=e.get(t),r=i.get(t);return n===r?0:r>n?-1:1}))},sort:function(e,t,i){i=i||this._cidComparator;var r=this.collection;if(n.PageableCollection&&r instanceof n.PageableCollection){var o;o="ascending"===t?-1:"descending"===t?1:null,r.setSorting(o?e:null,o),r.mode=="client"?(r.fullCollection.comparator||(r.fullCollection.comparator=i),r.fullCollection.sort()):r.fetch({reset:!0})}else r.comparator=i,r.sort();this.collection.trigger("backgrid:sort",e,t,i,this.collection)},_cidComparator:function(e,t){var n=e.cid,r=t.cid;if(!i.isUndefined(n)&&!i.isUndefined(r)){if(n=n.slice(1)*1,r=r.slice(1)*1,r>n)return-1;if(n>r)return 1}return 0},render:function(){this.$el.empty();var e=t("").text(this.column.get("label")).append("");return this.$el.append(e),this.delegateEvents(),this}});h.HeaderRow=h.Row.extend({requiredOptions:["columns","collection"],initialize:function(){h.Row.prototype.initialize.apply(this,arguments)},makeCell:function(e,t){var i=e.get("headerCell")||t.headerCell||N;return i=new i({column:e,collection:this.collection})}});var M=h.Header=n.View.extend({tagName:"thead",initialize:function(e){h.requireOptions(e,["columns","collection"]),this.columns=e.columns,this.columns instanceof n.Collection||(this.columns=new $(this.columns)),this.row=new h.HeaderRow({columns:this.columns,collection:this.collection})},render:function(){return this.$el.append(this.row.render().$el),this.delegateEvents(),this},remove:function(){return this.row.remove.apply(this.row,arguments),n.View.prototype.remove.apply(this,arguments)}}),O=h.Body=n.View.extend({tagName:"tbody",initialize:function(e){h.requireOptions(e,["columns","collection"]),this.columns=e.columns,this.columns instanceof n.Collection||(this.columns=new $(this.columns)),this.row=e.row||k,this.rows=this.collection.map(function(e){var t=new this.row({columns:this.columns,model:e});return t},this),this.emptyText=e.emptyText,this._unshiftEmptyRowMayBe();var t=this.collection;this.listenTo(t,"add",this.insertRow),this.listenTo(t,"remove",this.removeRow),this.listenTo(t,"sort",this.refresh),this.listenTo(t,"reset",this.refresh),this.listenTo(t,"backgrid:edited",this.moveToNextCell)},_unshiftEmptyRowMayBe:function(){this.rows.length===0&&this.emptyText!=null&&this.rows.unshift(new D({emptyText:this.emptyText,columns:this.columns}))},insertRow:function(e,t,r){if(this.rows[0]instanceof D&&this.rows.pop().remove(),!(t instanceof n.Collection||r))return this.collection.add(e,r=t),void 0;r=i.extend({render:!0},r||{});var o=new this.row({columns:this.columns,model:e}),s=t.indexOf(e);this.rows.splice(s,0,o);var l=this.$el,a=l.children(),h=o.render().$el;r.render&&(s>=a.length?l.append(h):a.eq(s).before(h))},removeRow:function(e,t,n){return n?((i.isUndefined(n.render)||n.render)&&this.rows[n.index].remove(),this.rows.splice(n.index,1),this._unshiftEmptyRowMayBe(),void 0):(this.collection.remove(e,n=t),this._unshiftEmptyRowMayBe(),void 0)},refresh:function(){for(var e=0;e=0&&s>h;a?h++:h--){var c=~~(h/o),d=h-c*o,u=this.rows[c].cells[d];if(u.column.get("renderable")&&u.column.get("editable")){u.enterEditMode();break}}}this.rows[n].cells[r].exitEditMode()}});h.Footer=n.View.extend({tagName:"tfoot",initialize:function(e){h.requireOptions(e,["columns","collection"]),this.columns=e.columns,this.columns instanceof n.Collection||(this.columns=new h.Columns(this.columns))}}),h.Grid=n.View.extend({tagName:"table",className:"backgrid",header:M,body:O,footer:null,initialize:function(e){h.requireOptions(e,["columns","collection"]),e.columns instanceof n.Collection||(e.columns=new $(e.columns)),this.columns=e.columns;var t=i.omit(e,["el","id","attributes","className","tagName","events"]);this.header=e.header||this.header,this.header=new this.header(t),this.body=e.body||this.body,this.body=new this.body(t),this.footer=e.footer||this.footer,this.footer&&(this.footer=new this.footer(t)),this.listenTo(this.columns,"reset",function(){this.header=new(this.header.remove().constructor)(t),this.body=new(this.body.remove().constructor)(t),this.footer&&(this.footer=new(this.footer.remove().constructor)(t)),this.render()})},insertRow:function(e,t,i){return this.body.insertRow(e,t,i)},removeRow:function(e,t,i){return this.body.removeRow(e,t,i)},insertColumn:function(e,t){return t=t||{render:!0},this.columns.add(e,t),this},removeColumn:function(e,t){return this.columns.remove(e,t),this},render:function(){return this.$el.empty(),this.$el.append(this.header.render().$el),this.footer&&this.$el.append(this.footer.render().$el),this.$el.append(this.body.render().$el),this.delegateEvents(),this.trigger("backgrid:rendered",this),this},remove:function(){return this.header.remove.apply(this.header,arguments),this.body.remove.apply(this.body,arguments),this.footer&&this.footer.remove.apply(this.footer,arguments),n.View.prototype.remove.apply(this,arguments)}})})(this,jQuery,_,Backbone); \ No newline at end of file diff --git a/static/js/libs/backgrid/extensions/filter/backgrid-filter.css b/static/js/libs/backgrid/extensions/filter/backgrid-filter.css new file mode 100644 index 0000000..4f145ea --- /dev/null +++ b/static/js/libs/backgrid/extensions/filter/backgrid-filter.css @@ -0,0 +1,19 @@ +/* + backgrid-filter + http://github.com/wyuenho/backgrid + + Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors + Licensed under the MIT @license. +*/ + +.backgrid-filter .close { + display: inline-block; + float: none; + width: 20px; + height: 20px; + margin-top: -4px; + font-size: 20px; + line-height: 20px; + text-align: center; + vertical-align: text-top; +} diff --git a/static/js/libs/backgrid/extensions/filter/backgrid-filter.js b/static/js/libs/backgrid/extensions/filter/backgrid-filter.js new file mode 100644 index 0000000..abc95a3 --- /dev/null +++ b/static/js/libs/backgrid/extensions/filter/backgrid-filter.js @@ -0,0 +1,365 @@ +/* + backgrid-filter + http://github.com/wyuenho/backgrid + + Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors + Licensed under the MIT @license. +*/ + +(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: _.template('
placeholder="<%- placeholder %>" <% } %> name="<%- name %>" />×
'), + + /** @property */ + events: { + "click .close": "clear", + "submit": "search" + }, + + /** @property {string} [name='q'] Query key */ + name: "q", + + /** @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] + */ + initialize: function (options) { + Backgrid.requireOptions(options, ["collection"]); + Backbone.View.prototype.initialize.apply(this, arguments); + this.name = options.name || this.name; + this.placeholder = options.placeholder || this.placeholder; + + var collection = this.collection, self = this; + if (Backbone.PageableCollection && + collection instanceof Backbone.PageableCollection && + collection.mode == "server") { + collection.queryParams[this.name] = function () { + return self.$el.find("input[type=text]").val(); + }; + } + }, + + /** + Upon search form submission, this event handler constructs a query + parameter object and pass it to Collection#fetch for server-side + filtering. + */ + search: function (e) { + if (e) e.preventDefault(); + var data = {}; + data[this.name] = this.$el.find("input[type=text]").val(); + this.collection.fetch({data: data}); + }, + + /** + Event handler for the close button. Clears the search box and refetch the + collection. + */ + clear: function (e) { + if (e) e.preventDefault(); + this.$("input[type=text]").val(null); + this.collection.fetch(); + }, + + /** + 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.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: { + "click .close": function (e) { + e.preventDefault(); + this.clear(); + }, + "change input[type=text]": "search", + "keyup input[type=text]": "search", + "submit": function (e) { + e.preventDefault(); + this.search(); + } + }, + + /** + @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 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. + */ + 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) { + ServerSideFilter.prototype.initialize.apply(this, arguments); + + this.fields = options.fields || this.fields; + this.wait = options.wait || this.wait; + + this._debounceMethods(["search", "clear"]); + + 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); + }); + this.listenTo(collection, "remove", function (model, collection, options) { + shadowCollection.remove(model, options); + }); + this.listenTo(collection, "sort reset", function (collection, options) { + options = _.extend({reindex: true}, options || {}); + if (options.reindex) shadowCollection.reset(collection.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(); + }, + + /** + 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. + + Subclasses overriding this method must take care to conform to the + signature of the matcher function. 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 = new RegExp(query.trim().split(/\W/).join("|"), "i"); + 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. + */ + search: function () { + var matcher = _.bind(this.makeMatcher(this.$("input[type=text]").val()), this); + this.collection.reset(this.shadowCollection.filter(matcher), {reindex: false}); + }, + + /** + Clears the search box and reset the collection to its original. + */ + clear: function () { + this.$("input[type=text]").val(null); + this.collection.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 + */ + 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) { + ClientSideFilter.prototype.initialize.apply(this, arguments); + + this.ref = options.ref || this.ref; + + var collection = 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. + */ + search: function () { + 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)); + } + this.collection.reset(models, {reindex: false}); + } + + }); + +}(jQuery, _, Backbone, Backgrid, lunr)); diff --git a/static/js/libs/backgrid/extensions/filter/backgrid-filter.min.css b/static/js/libs/backgrid/extensions/filter/backgrid-filter.min.css new file mode 100644 index 0000000..58c85a3 --- /dev/null +++ b/static/js/libs/backgrid/extensions/filter/backgrid-filter.min.css @@ -0,0 +1,8 @@ +/* + backgrid-filter + http://github.com/wyuenho/backgrid + + Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors + Licensed under the MIT @license. +*/ +.backgrid-filter .close{display:inline-block;float:none;width:20px;height:20px;margin-top:-4px;font-size:20px;line-height:20px;text-align:center;vertical-align:text-top} diff --git a/static/js/libs/backgrid/extensions/filter/backgrid-filter.min.js b/static/js/libs/backgrid/extensions/filter/backgrid-filter.min.js new file mode 100644 index 0000000..d8c2767 --- /dev/null +++ b/static/js/libs/backgrid/extensions/filter/backgrid-filter.min.js @@ -0,0 +1,8 @@ +/* + backgrid-filter + http://github.com/wyuenho/backgrid + + Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors + Licensed under the MIT @license. +*/ +(function(e,t,i,n,s){"use strict";var a=n.Extension.ServerSideFilter=i.View.extend({tagName:"form",className:"backgrid-filter form-search",template:t.template('
placeholder="<%- placeholder %>" <% } %> name="<%- name %>" />×
'),events:{"click .close":"clear",submit:"search"},name:"q",placeholder:null,initialize:function(e){n.requireOptions(e,["collection"]),i.View.prototype.initialize.apply(this,arguments),this.name=e.name||this.name,this.placeholder=e.placeholder||this.placeholder;var t=this.collection,s=this;i.PageableCollection&&t instanceof i.PageableCollection&&t.mode=="server"&&(t.queryParams[this.name]=function(){return s.$el.find("input[type=text]").val()})},search:function(e){e&&e.preventDefault();var t={};t[this.name]=this.$el.find("input[type=text]").val(),this.collection.fetch({data:t})},clear:function(e){e&&e.preventDefault(),this.$("input[type=text]").val(null),this.collection.fetch()},render:function(){return this.$el.empty().append(this.template({name:this.name,placeholder:this.placeholder,value:this.value})),this.delegateEvents(),this}}),l=n.Extension.ClientSideFilter=a.extend({events:{"click .close":function(e){e.preventDefault(),this.clear()},"change input[type=text]":"search","keyup input[type=text]":"search",submit:function(e){e.preventDefault(),this.search()}},fields:null,wait:149,initialize:function(e){a.prototype.initialize.apply(this,arguments),this.fields=e.fields||this.fields,this.wait=e.wait||this.wait,this._debounceMethods(["search","clear"]);var i=this.collection,n=this.shadowCollection=i.clone();n.url=i.url,n.sync=i.sync,n.parse=i.parse,this.listenTo(i,"add",function(e,t,i){n.add(e,i)}),this.listenTo(i,"remove",function(e,t,i){n.remove(e,i)}),this.listenTo(i,"sort reset",function(e,i){i=t.extend({reindex:!0},i||{}),i.reindex&&n.reset(e.models)})},_debounceMethods:function(e){t.isString(e)&&(e=[e]),this.undelegateEvents();for(var i=0,n=e.length;n>i;i++){var s=e[i],a=this[s];this[s]=t.debounce(a,this.wait)}this.delegateEvents()},makeMatcher:function(e){var t=new RegExp(e.trim().split(/\W/).join("|"),"i");return function(e){for(var i=this.fields||e.keys(),n=0,s=i.length;s>n;n++)if(t.test(e.get(i[n])+""))return!0;return!1}},search:function(){var e=t.bind(this.makeMatcher(this.$("input[type=text]").val()),this);this.collection.reset(this.shadowCollection.filter(e),{reindex:!1})},clear:function(){this.$("input[type=text]").val(null),this.collection.reset(this.shadowCollection.models,{reindex:!1})}});n.Extension.LunrFilter=l.extend({ref:"id",fields:null,initialize:function(e){l.prototype.initialize.apply(this,arguments),this.ref=e.ref||this.ref;var t=this.collection;this.listenTo(t,"add",this.addToIndex),this.listenTo(t,"remove",this.removeFromIndex),this.listenTo(t,"reset",this.resetIndex),this.listenTo(t,"change",this.updateIndex),this.resetIndex(t)},resetIndex:function(e,i){if(i=t.extend({reindex:!0},i||{}),i.reindex){var n=this;this.index=s(function(){t.each(n.fields,function(e,t){this.field(t,e),this.ref(n.ref)},this)}),e.each(function(e){this.addToIndex(e)},this)}},addToIndex:function(e){var t=this.index,i=e.toJSON();t.documentStore.has(i[this.ref])?t.update(i):t.add(i)},removeFromIndex:function(e){var t=this.index,i=e.toJSON();t.documentStore.has(i[this.ref])&&t.remove(i)},updateIndex:function(e){var i=e.changedAttributes();i&&!t.isEmpty(t.intersection(t.keys(this.fields),t.keys(i)))&&this.index.update(e.toJSON())},search:function(){for(var e=this.index.search(this.$("input[type=text]").val()),t=[],i=0;i 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; +} diff --git a/static/js/libs/backgrid/extensions/paginator/backgrid-paginator.js b/static/js/libs/backgrid/extensions/paginator/backgrid-paginator.js new file mode 100644 index 0000000..cceabca --- /dev/null +++ b/static/js/libs/backgrid/extensions/paginator/backgrid-paginator.js @@ -0,0 +1,198 @@ +/* + backgrid-paginator + http://github.com/wyuenho/backgrid + + Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors + Licensed under the MIT @license. +*/ + +(function ($, _, Backbone, Backgrid) { + + "use strict"; + + /** + 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 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 + */ + Backgrid.Extension.Paginator = Backbone.View.extend({ + + /** @property */ + className: "backgrid-paginator", + + /** @property */ + windowSize: 10, + + /** + @property {Object} fastForwardHandleLabels You can disable specific + handles by setting its value to `null`. + */ + fastForwardHandleLabels: { + first: "《", + prev: "〈", + next: "〉", + last: "》" + }, + + /** @property */ + template: _.template(''), + + /** @property */ + events: { + "click a": "changePage" + }, + + /** + Initializer. + + @param {Object} options + @param {Backbone.Collection} options.collection + @param {boolean} [options.fastForwardHandleLabels] Whether to render fast forward buttons. + */ + initialize: function (options) { + Backgrid.requireOptions(options, ["collection"]); + + var collection = this.collection; + var fullCollection = collection.fullCollection; + if (fullCollection) { + this.listenTo(fullCollection, "add", this.render); + this.listenTo(fullCollection, "remove", this.render); + this.listenTo(fullCollection, "reset", this.render); + } + else { + this.listenTo(collection, "add", this.render); + this.listenTo(collection, "remove", this.render); + this.listenTo(collection, "reset", this.render); + } + }, + + /** + 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; + + // 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 windowStart = Math.floor(currentPage / this.windowSize) * this.windowSize; + var windowEnd = Math.min(lastPage + 1, windowStart + this.windowSize); + + 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 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; + }, + + /** + Render the paginator handles inside an unordered list. + */ + render: function () { + this.$el.empty(); + + this.$el.append(this.template({ + handles: this.makeHandles() + })); + + this.delegateEvents(); + + return this; + } + + }); + +}(jQuery, _, Backbone, Backgrid)); diff --git a/static/js/libs/backgrid/extensions/paginator/backgrid-paginator.min.css b/static/js/libs/backgrid/extensions/paginator/backgrid-paginator.min.css new file mode 100644 index 0000000..ccca5dd --- /dev/null +++ b/static/js/libs/backgrid/extensions/paginator/backgrid-paginator.min.css @@ -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. +*/ +.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} diff --git a/static/js/libs/backgrid/extensions/paginator/backgrid-paginator.min.js b/static/js/libs/backgrid/extensions/paginator/backgrid-paginator.min.js new file mode 100644 index 0000000..ac904ec --- /dev/null +++ b/static/js/libs/backgrid/extensions/paginator/backgrid-paginator.min.js @@ -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(e,t,a,s){"use strict";s.Extension.Paginator=a.View.extend({className:"backgrid-paginator",windowSize:10,fastForwardHandleLabels:{first:"《",prev:"〈",next:"〉",last:"》"},template:t.template(''),events:{"click a":"changePage"},initialize:function(e){s.requireOptions(e,["collection"]);var t=this.collection,a=t.fullCollection;a?(this.listenTo(a,"add",this.render),this.listenTo(a,"remove",this.render),this.listenTo(a,"reset",this.render)):(this.listenTo(t,"add",this.render),this.listenTo(t,"remove",this.render),this.listenTo(t,"reset",this.render))},changePage:function(t){t.preventDefault();var a=e(t.target).parent();if(!a.hasClass("active")&&!a.hasClass("disabled")){var s=e(t.target).text(),i=this.fastForwardHandleLabels,l=this.collection;if(i)switch(s){case i.first:return l.getFirstPage(),void 0;case i.prev:return l.getPreviousPage(),void 0;case i.next:return l.getNextPage(),void 0;case i.last:return l.getLastPage(),void 0}var n=l.state,r=+s;l.getPage(n.firstPage===0?r-1:r)}},makeHandles:function(){var e=[],t=this.collection,a=t.state,s=a.firstPage,i=+a.lastPage;i=Math.max(0,s?i-1:i);var l=Math.max(a.currentPage,a.firstPage);l=s?l-1:l;var n=Math.floor(l/this.windowSize)*this.windowSize,r=Math.min(i+1,n+this.windowSize);if(t.mode!=="infinite")for(var d=n;r>d;d++)e.push({label:d+1,title:"No. "+(d+1),className:l===d?"active":void 0});var h=this.fastForwardHandleLabels;return h&&(h.prev&&e.unshift({label:h.prev,className:t.hasPrevious()?void 0:"disabled"}),h.first&&e.unshift({label:h.first,className:t.hasPrevious()?void 0:"disabled"}),h.next&&e.push({label:h.next,className:t.hasNext()?void 0:"disabled"}),h.last&&e.push({label:h.last,className:t.hasNext()?void 0:"disabled"})),e},render:function(){return this.$el.empty(),this.$el.append(this.template({handles:this.makeHandles()})),this.delegateEvents(),this}})})(jQuery,_,Backbone,Backgrid); \ No newline at end of file diff --git a/static/js/libs/backgrid/extensions/select-all/backgrid-select-all.css b/static/js/libs/backgrid/extensions/select-all/backgrid-select-all.css new file mode 100644 index 0000000..fa5ce81 --- /dev/null +++ b/static/js/libs/backgrid/extensions/select-all/backgrid-select-all.css @@ -0,0 +1,11 @@ +/* + 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; +} diff --git a/static/js/libs/backgrid/extensions/select-all/backgrid-select-all.js b/static/js/libs/backgrid/extensions/select-all/backgrid-select-all.js new file mode 100644 index 0000000..2fd05ef --- /dev/null +++ b/static/js/libs/backgrid/extensions/select-all/backgrid-select-all.js @@ -0,0 +1,215 @@ +/* + backgrid-select-all + http://github.com/wyuenho/backgrid + + Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors + Licensed under the MIT @license. +*/ +(function (window, $, _, Backbone, Backgrid) { + + /** + 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 :checkbox": "onKeydown", + "change :checkbox": "onChange", + "click :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) { + Backgrid.requireOptions(options, ["model", "column"]); + + this.column = options.column; + if (!(this.column instanceof Backgrid.Column)) { + this.column = new Backgrid.Column(this.column); + } + + this.listenTo(this.model, "backgrid:select", function (model, selected) { + this.$el.find(":checkbox").prop("checked", selected).change(); + }); + + }, + + /** + Focuses the checkbox. + */ + enterEditMode: function () { + this.$el.find(":checkbox").focus(); + }, + + /** + Unfocuses the checkbox. + */ + exitEditMode: function () { + this.$el.find(":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.$el.find(":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 (e) { + this.model.trigger("backgrid:selected", this.model, $(e.target).prop("checked")); + }, + + /** + Renders a checkbox in a table cell. + */ + render: function () { + this.$el.empty().append(''); + 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) { + Backgrid.requireOptions(options, ["column", "collection"]); + + 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, "backgrid:selected", function (model, selected) { + if (selected) selectedModels[model.id || model.cid] = model; + else { + delete selectedModels[model.id || model.cid]; + this.$el.find(":checkbox").prop("checked", false); + } + }); + + this.listenTo(collection, "remove", function (model) { + delete selectedModels[model.cid]; + }); + + this.listenTo(collection, "backgrid:refresh", function () { + this.$el.find(":checkbox").prop("checked", false); + for (var i = 0; i < collection.length; i++) { + var model = collection.at(i); + if (selectedModels[model.id || model.cid]) { + model.trigger('backgrid:select', model, true); + } + } + }); + }, + + /** + Progagates the checked value of this checkbox to all the models of the + underlying collection by triggering a Backbone `backgrid:select` event on + the models themselves, passing each model and the current `checked` value + of the checkbox in each event. + */ + onChange: function (e) { + var checked = $(e.target).prop("checked"); + + var collection = this.collection; + collection.each(function (model) { + model.trigger("backgrid:select", model, checked); + }); + } + + }); + + /** + Convenient method to retrieve a list of selected models. This method only + exists when the `SelectAll` extension has been included. + + @member Backgrid.Grid + @return {Array.} + */ + 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) { + for (var modelId in selectAllHeaderCell.selectedModels) { + result.push(this.collection.get(modelId)); + } + } + + return result; + }; + +}(window, jQuery, _, Backbone, Backgrid)); diff --git a/static/js/libs/backgrid/extensions/select-all/backgrid-select-all.min.css b/static/js/libs/backgrid/extensions/select-all/backgrid-select-all.min.css new file mode 100644 index 0000000..8e8636e --- /dev/null +++ b/static/js/libs/backgrid/extensions/select-all/backgrid-select-all.min.css @@ -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. +*/ +.backgrid .select-row-cell,.backgrid .select-all-header-cell{text-align:center} diff --git a/static/js/libs/backgrid/extensions/select-all/backgrid-select-all.min.js b/static/js/libs/backgrid/extensions/select-all/backgrid-select-all.min.js new file mode 100644 index 0000000..6ff55ab --- /dev/null +++ b/static/js/libs/backgrid/extensions/select-all/backgrid-select-all.min.js @@ -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(e,t,i,n,o){var c=o.Extension.SelectRowCell=n.View.extend({className:"select-row-cell",tagName:"td",events:{"keydown :checkbox":"onKeydown","change :checkbox":"onChange","click :checkbox":"enterEditMode"},initialize:function(e){o.requireOptions(e,["model","column"]),this.column=e.column,this.column instanceof o.Column||(this.column=new o.Column(this.column)),this.listenTo(this.model,"backgrid:select",function(e,t){this.$el.find(":checkbox").prop("checked",t).change()})},enterEditMode:function(){this.$el.find(":checkbox").focus()},exitEditMode:function(){this.$el.find(":checkbox").blur()},onKeydown:function(e){var t=new o.Command(e);return t.passThru()?!0:(t.cancel()?(e.stopPropagation(),this.$el.find(":checkbox").blur()):(t.save()||t.moveLeft()||t.moveRight()||t.moveUp()||t.moveDown())&&(e.preventDefault(),e.stopPropagation(),this.model.trigger("backgrid:edited",this.model,this.column,t)),void 0)},onChange:function(e){this.model.trigger("backgrid:selected",this.model,t(e.target).prop("checked"))},render:function(){return this.$el.empty().append(''),this.delegateEvents(),this}}),l=o.Extension.SelectAllHeaderCell=c.extend({className:"select-all-header-cell",tagName:"th",initialize:function(e){o.requireOptions(e,["column","collection"]),this.column=e.column,this.column instanceof o.Column||(this.column=new o.Column(this.column));var t=this.collection,i=this.selectedModels={};this.listenTo(t,"backgrid:selected",function(e,t){t?i[e.id||e.cid]=e:(delete i[e.id||e.cid],this.$el.find(":checkbox").prop("checked",!1))}),this.listenTo(t,"remove",function(e){delete i[e.cid]}),this.listenTo(t,"backgrid:refresh",function(){this.$el.find(":checkbox").prop("checked",!1);for(var e=0;ei;i++){var o=t[i];if(o instanceof l){e=o;break}}var c=[];if(e)for(var s in e.selectedModels)c.push(this.collection.get(s));return c}})(window,jQuery,_,Backbone,Backgrid); \ No newline at end of file diff --git a/static/js/libs/backgrid/extensions/select2-cell/backgrid-select2-cell.css b/static/js/libs/backgrid/extensions/select2-cell/backgrid-select2-cell.css new file mode 100644 index 0000000..4c9bbcb --- /dev/null +++ b/static/js/libs/backgrid/extensions/select2-cell/backgrid-select2-cell.css @@ -0,0 +1,19 @@ +/* + backgrid-select2-cell + http://github.com/wyuenho/backgrid + + Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors + Licensed under the MIT @license. +*/ + +.backgrid .select2-cell { + text-align: center; +} + +.backgrid .select2-cell.editor { + padding: 0; +} + +.backgrid .select2-cell.editor .select2-container { + width: 100%; +} diff --git a/static/js/libs/backgrid/extensions/select2-cell/backgrid-select2-cell.js b/static/js/libs/backgrid/extensions/select2-cell/backgrid-select2-cell.js new file mode 100644 index 0000000..d763ee8 --- /dev/null +++ b/static/js/libs/backgrid/extensions/select2-cell/backgrid-select2-cell.js @@ -0,0 +1,121 @@ +/* + backgrid-select2-cell + http://github.com/wyuenho/backgrid + + Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors + Licensed under the MIT @license. +*/ + +(function (window, $, _, Backbone, Backgrid) { + + /** + Select2CellEditor is a cell editor that renders a `select2` select box + instead of the default `` HTML + element using the supplied options from #select2Options. + + @chainable + */ + render: function () { + Backgrid.SelectCellEditor.prototype.render.apply(this, arguments); + this.$el.select2(this.select2Options); + this.delegateEvents(); + return this; + }, + + /** + Attach event handlers to the select2 box and focus it. + */ + postRender: function () { + var self = this; + this.$el.parent() + .find("." + this.select2Options.containerCssClass) + .on("blur", function (e) { + if (!e.relatedTarget) self.close(e); + }) + .on("keydown", this.close) + .attr("tabindex", -1).focus(); + }, + + remove: function () { + this.$el.select2("destroy"); + return Backgrid.SelectCellEditor.prototype.remove.apply(this, arguments); + } + + }); + + /** + Select2Cell is a cell class that renders a `select2` select box during edit + mode. + + @class Backgrid.Extension.Select2Cell + @extends Backgrid.SelectCell + */ + Backgrid.Extension.Select2Cell = Backgrid.SelectCell.extend({ + + /** @property */ + className: "select2-cell", + + /** @property */ + editor: Select2CellEditor, + + /** @property */ + select2Options: null, + + /** + Initializer. + + @param {Object} options + @param {Backbone.Model} options.model + @param {Backgrid.Column} options.column + @param {Object} [options.select2Options] + + @throws {TypeError} If `optionsValues` is undefined. + */ + initialize: function (options) { + Backgrid.SelectCell.prototype.initialize.apply(this, arguments); + this.select2Options = options.select2Options || this.select2Options; + this.listenTo(this.model, "backgrid:edit", function (model, column, cell, editor) { + if (column.get("name") == this.column.get("name")) { + editor.setSelect2Options(this.select2Options); + } + }); + } + + }); + +}(window, jQuery, _, Backbone, Backgrid)); diff --git a/static/js/libs/backgrid/extensions/select2-cell/backgrid-select2-cell.min.css b/static/js/libs/backgrid/extensions/select2-cell/backgrid-select2-cell.min.css new file mode 100644 index 0000000..5abcaef --- /dev/null +++ b/static/js/libs/backgrid/extensions/select2-cell/backgrid-select2-cell.min.css @@ -0,0 +1,8 @@ +/* + backgrid-select2-cell + http://github.com/wyuenho/backgrid + + Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors + Licensed under the MIT @license. +*/ +.backgrid .select2-cell{text-align:center}.backgrid .select2-cell.editor{padding:0}.backgrid .select2-cell.editor .select2-container{width:100%} diff --git a/static/js/libs/backgrid/extensions/select2-cell/backgrid-select2-cell.min.js b/static/js/libs/backgrid/extensions/select2-cell/backgrid-select2-cell.min.js new file mode 100644 index 0000000..46c449e --- /dev/null +++ b/static/js/libs/backgrid/extensions/select2-cell/backgrid-select2-cell.min.js @@ -0,0 +1,8 @@ +/* + backgrid-select2-cell + http://github.com/wyuenho/backgrid + + Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors + Licensed under the MIT @license. +*/ +(function(e,t,i,s,n){var l=n.Extension.Select2CellEditor=n.SelectCellEditor.extend({events:{close:"save",change:"save"},select2Options:null,initialize:function(){n.SelectCellEditor.prototype.initialize.apply(this,arguments),this.close=i.bind(this.close,this)},setSelect2Options:function(e){this.select2Options=i.extend({containerCssClass:"select2-container"},e||{})},render:function(){return n.SelectCellEditor.prototype.render.apply(this,arguments),this.$el.select2(this.select2Options),this.delegateEvents(),this},postRender:function(){var e=this;this.$el.parent().find("."+this.select2Options.containerCssClass).on("blur",function(t){t.relatedTarget||e.close(t)}).on("keydown",this.close).attr("tabindex",-1).focus()},remove:function(){return this.$el.select2("destroy"),n.SelectCellEditor.prototype.remove.apply(this,arguments)}});n.Extension.Select2Cell=n.SelectCell.extend({className:"select2-cell",editor:l,select2Options:null,initialize:function(e){n.SelectCell.prototype.initialize.apply(this,arguments),this.select2Options=e.select2Options||this.select2Options,this.listenTo(this.model,"backgrid:edit",function(e,t,i,s){t.get("name")==this.column.get("name")&&s.setSelect2Options(this.select2Options)})}})})(window,jQuery,_,Backbone,Backgrid); \ No newline at end of file diff --git a/static/js/libs/backgrid/extensions/text-cell/backgrid-text-cell.css b/static/js/libs/backgrid/extensions/text-cell/backgrid-text-cell.css new file mode 100644 index 0000000..a1283ea --- /dev/null +++ b/static/js/libs/backgrid/extensions/text-cell/backgrid-text-cell.css @@ -0,0 +1,31 @@ +/* + backgrid-text-cell + http://github.com/wyuenho/backgrid + + Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors + Licensed under the MIT @license. +*/ + +.backgrid .text-cell { + max-width: 150px; + overflow: hidden; + text-align: left; + -o-text-overflow: ellipsis; + text-overflow: ellipsis; + white-space: nowrap; +} + +.backgrid .text-cell.editor { + max-width: 100%; + height: 28px; +} + +.backgrid .text-cell.editor * { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.backgrid .text-cell.editor textarea { + width: 100%; +} diff --git a/static/js/libs/backgrid/extensions/text-cell/backgrid-text-cell.js b/static/js/libs/backgrid/extensions/text-cell/backgrid-text-cell.js new file mode 100644 index 0000000..78187fb --- /dev/null +++ b/static/js/libs/backgrid/extensions/text-cell/backgrid-text-cell.js @@ -0,0 +1,159 @@ +/* + backgrid-text-cell + http://github.com/wyuenho/backgrid + + Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors + Licensed under the MIT @license. +*/ + +(function (window, $, _, Backbone, Backgrid) { + + /** + Renders a form with a text area and a save button in a modal dialog. + + @class Backgrid.Extension.TextareaEditor + @extends Backgrid.CellEditor + */ + var TextareaEditor = Backgrid.Extension.TextareaEditor = Backgrid.CellEditor.extend({ + + /** @property */ + tagName: "div", + + /** @property */ + className: "modal hide fade", + + /** @property {function(Object, ?Object=): string} template */ + template: _.template('
'), + + /** @property */ + cols: 80, + + /** @property */ + rows: 10, + + /** @property */ + events: { + "keydown textarea": "clearError", + "submit": "saveOrCancel", + "hide": "saveOrCancel", + "hidden": "close", + "shown": "focus" + }, + + /** + @property {Object} modalOptions The options passed to Bootstrap's modal + plugin. + */ + modalOptions: { + backdrop: false + }, + + /** + Renders a modal form dialog with a textarea, submit button and a close button. + */ + render: function () { + this.$el.html($(this.template({ + column: this.column, + cols: this.cols, + rows: this.rows, + content: this.formatter.fromRaw(this.model.get(this.column.get("name"))) + }))); + + this.delegateEvents(); + + this.$el.modal(this.modalOptions); + + return this; + }, + + /** + Event handler. Saves the text in the text area to the model when + submitting. When cancelling, if the text area is dirty, a confirmation + dialog will pop up. If the user clicks confirm, the text will be saved to + the model. + + Triggers a Backbone `backgrid:error` event from the model along with the + model, column and the existing value as the parameters if the value + cannot be converted. + + @param {Event} e + */ + saveOrCancel: function (e) { + if (e && e.type == "submit") { + e.preventDefault(); + e.stopPropagation(); + } + + var model = this.model; + var column = this.column; + var val = this.$el.find("textarea").val(); + var newValue = this.formatter.toRaw(val); + + if (_.isUndefined(newValue)) { + model.trigger("backgrid:error", model, column, val); + + if (e) { + e.preventDefault(); + e.stopPropagation(); + } + } + else if (!e || e.type == "submit" || + (e.type == "hide" && + newValue !== (this.model.get(this.column.get("name")) || '').replace(/\r/g, '') && + window.confirm("Would you like to save your changes?"))) { + + model.set(column.get("name"), newValue); + this.$el.modal("hide"); + } + else if (e.type != "hide") this.$el.modal("hide"); + }, + + /** + Clears the error class on the parent cell. + */ + clearError: _.debounce(function () { + if (!_.isUndefined(this.formatter.toRaw(this.$el.find("textarea").val()))) { + this.$el.parent().removeClass("error"); + } + }, 150), + + /** + Triggers a `backgrid:edited` event along with the cell editor as the + parameter after the modal is hidden. + + @param {Event} e + */ + close: function (e) { + var model = this.model; + model.trigger("backgrid:edited", model, this.column, + new Backgrid.Command(e)); + }, + + /** + Focuses the textarea when the modal is shown. + */ + focus: function () { + this.$el.find("textarea").focus(); + } + + }); + + /** + TextCell is a string cell type that renders a form with a text area in a + modal dialog instead of an input box editor. It is best suited for entering + a large body of text. + + @class Backgrid.Extension.TextCell + @extends Backgrid.StringCell + */ + var TextCell = Backgrid.Extension.TextCell = Backgrid.StringCell.extend({ + + /** @property */ + className: "text-cell", + + /** @property */ + editor: TextareaEditor + + }); + +}(window, jQuery, _, Backbone, Backgrid)); diff --git a/static/js/libs/backgrid/extensions/text-cell/backgrid-text-cell.min.css b/static/js/libs/backgrid/extensions/text-cell/backgrid-text-cell.min.css new file mode 100644 index 0000000..7b50342 --- /dev/null +++ b/static/js/libs/backgrid/extensions/text-cell/backgrid-text-cell.min.css @@ -0,0 +1,8 @@ +/* + backgrid-text-cell + http://github.com/wyuenho/backgrid + + Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors + Licensed under the MIT @license. +*/ +.backgrid .text-cell{max-width:150px;overflow:hidden;text-align:left;-o-text-overflow:ellipsis;text-overflow:ellipsis;white-space:nowrap}.backgrid .text-cell.editor{height:28px;max-width:100%}.backgrid .text-cell.editor *{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.backgrid .text-cell.editor textarea{width:100%} diff --git a/static/js/libs/backgrid/extensions/text-cell/backgrid-text-cell.min.js b/static/js/libs/backgrid/extensions/text-cell/backgrid-text-cell.min.js new file mode 100644 index 0000000..60ba936 --- /dev/null +++ b/static/js/libs/backgrid/extensions/text-cell/backgrid-text-cell.min.js @@ -0,0 +1,8 @@ +/* + backgrid-text-cell + http://github.com/wyuenho/backgrid + + Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors + Licensed under the MIT @license. +*/ +(function(e,t,o,a,s){var i=s.Extension.TextareaEditor=s.CellEditor.extend({tagName:"div",className:"modal hide fade",template:o.template('
'),cols:80,rows:10,events:{"keydown textarea":"clearError",submit:"saveOrCancel",hide:"saveOrCancel",hidden:"close",shown:"focus"},modalOptions:{backdrop:!1},render:function(){return this.$el.html(t(this.template({column:this.column,cols:this.cols,rows:this.rows,content:this.formatter.fromRaw(this.model.get(this.column.get("name")))}))),this.delegateEvents(),this.$el.modal(this.modalOptions),this},saveOrCancel:function(t){t&&t.type=="submit"&&(t.preventDefault(),t.stopPropagation());var a=this.model,s=this.column,i=this.$el.find("textarea").val(),l=this.formatter.toRaw(i);o.isUndefined(l)?(a.trigger("backgrid:error",a,s,i),t&&(t.preventDefault(),t.stopPropagation())):!t||t.type=="submit"||t.type=="hide"&&l!==(this.model.get(this.column.get("name"))||"").replace(/\r/g,"")&&e.confirm("Would you like to save your changes?")?(a.set(s.get("name"),l),this.$el.modal("hide")):t.type!="hide"&&this.$el.modal("hide")},clearError:o.debounce(function(){o.isUndefined(this.formatter.toRaw(this.$el.find("textarea").val()))||this.$el.parent().removeClass("error")},150),close:function(e){var t=this.model;t.trigger("backgrid:edited",t,this.column,new s.Command(e))},focus:function(){this.$el.find("textarea").focus()}});s.Extension.TextCell=s.StringCell.extend({className:"text-cell",editor:i})})(window,jQuery,_,Backbone,Backgrid); \ No newline at end of file