backgridjs-rails 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. checksums.yaml +15 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +25 -0
  4. data/Rakefile +27 -0
  5. data/lib/backgridjs-rails.rb +5 -0
  6. data/lib/backgridjs-rails.rb~ +2 -0
  7. data/lib/backgridjs-rails/engine.rb +8 -0
  8. data/lib/backgridjs-rails/engine.rb~ +8 -0
  9. data/lib/backgridjs-rails/version.rb +3 -0
  10. data/lib/backgridjs-rails/version.rb~ +3 -0
  11. data/lib/tasks/backgridjs-rails_tasks.rake +4 -0
  12. data/vendor/assets/javascripts/backgrid-extensions/backgrid-filter.js +365 -0
  13. data/vendor/assets/javascripts/backgrid-extensions/backgrid-moment-cell.js +175 -0
  14. data/vendor/assets/javascripts/backgrid-extensions/backgrid-paginator.js +198 -0
  15. data/vendor/assets/javascripts/backgrid-extensions/backgrid-select-all.js +215 -0
  16. data/vendor/assets/javascripts/backgrid-extensions/backgrid-select2-cell.js +121 -0
  17. data/vendor/assets/javascripts/backgrid-extensions/backgrid-text-cell.js +159 -0
  18. data/vendor/assets/javascripts/backgrid.js +2531 -0
  19. data/vendor/assets/stylesheets/backgrid-extensions/backgrid-filter.css +19 -0
  20. data/vendor/assets/stylesheets/backgrid-extensions/backgrid-moment-cell.css +14 -0
  21. data/vendor/assets/stylesheets/backgrid-extensions/backgrid-paginator.css +58 -0
  22. data/vendor/assets/stylesheets/backgrid-extensions/backgrid-select-all.css +11 -0
  23. data/vendor/assets/stylesheets/backgrid-extensions/backgrid-select2-cell.css +19 -0
  24. data/vendor/assets/stylesheets/backgrid-extensions/backgrid-text-cell.css +31 -0
  25. data/vendor/assets/stylesheets/backgrid.css +215 -0
  26. metadata +82 -0
@@ -0,0 +1,121 @@
1
+ /*
2
+ backgrid-select2-cell
3
+ http://github.com/wyuenho/backgrid
4
+
5
+ Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
6
+ Licensed under the MIT @license.
7
+ */
8
+
9
+ (function (window, $, _, Backbone, Backgrid) {
10
+
11
+ /**
12
+ Select2CellEditor is a cell editor that renders a `select2` select box
13
+ instead of the default `<select>` HTML element.
14
+
15
+ See:
16
+
17
+ - [Select2](http://ivaynberg.github.com/select2/)
18
+
19
+ @class Backgrid.Extension.Select2CellEditor
20
+ @extends Backgrid.SelectCellEditor
21
+ */
22
+ var Select2CellEditor = Backgrid.Extension.Select2CellEditor = Backgrid.SelectCellEditor.extend({
23
+
24
+ /** @property */
25
+ events: {
26
+ "close": "save",
27
+ "change": "save"
28
+ },
29
+
30
+ /** @property */
31
+ select2Options: null,
32
+
33
+ initialize: function () {
34
+ Backgrid.SelectCellEditor.prototype.initialize.apply(this, arguments);
35
+ this.close = _.bind(this.close, this);
36
+ },
37
+
38
+ /**
39
+ Sets the options for `select2`. Called by the parent Select2Cell during
40
+ edit mode.
41
+ */
42
+ setSelect2Options: function (options) {
43
+ this.select2Options = _.extend({containerCssClass: "select2-container"},
44
+ options || {});
45
+ },
46
+
47
+ /**
48
+ Renders a `select2` select box instead of the default `<select>` HTML
49
+ element using the supplied options from #select2Options.
50
+
51
+ @chainable
52
+ */
53
+ render: function () {
54
+ Backgrid.SelectCellEditor.prototype.render.apply(this, arguments);
55
+ this.$el.select2(this.select2Options);
56
+ this.delegateEvents();
57
+ return this;
58
+ },
59
+
60
+ /**
61
+ Attach event handlers to the select2 box and focus it.
62
+ */
63
+ postRender: function () {
64
+ var self = this;
65
+ this.$el.parent()
66
+ .find("." + this.select2Options.containerCssClass)
67
+ .on("blur", function (e) {
68
+ if (!e.relatedTarget) self.close(e);
69
+ })
70
+ .on("keydown", this.close)
71
+ .attr("tabindex", -1).focus();
72
+ },
73
+
74
+ remove: function () {
75
+ this.$el.select2("destroy");
76
+ return Backgrid.SelectCellEditor.prototype.remove.apply(this, arguments);
77
+ }
78
+
79
+ });
80
+
81
+ /**
82
+ Select2Cell is a cell class that renders a `select2` select box during edit
83
+ mode.
84
+
85
+ @class Backgrid.Extension.Select2Cell
86
+ @extends Backgrid.SelectCell
87
+ */
88
+ Backgrid.Extension.Select2Cell = Backgrid.SelectCell.extend({
89
+
90
+ /** @property */
91
+ className: "select2-cell",
92
+
93
+ /** @property */
94
+ editor: Select2CellEditor,
95
+
96
+ /** @property */
97
+ select2Options: null,
98
+
99
+ /**
100
+ Initializer.
101
+
102
+ @param {Object} options
103
+ @param {Backbone.Model} options.model
104
+ @param {Backgrid.Column} options.column
105
+ @param {Object} [options.select2Options]
106
+
107
+ @throws {TypeError} If `optionsValues` is undefined.
108
+ */
109
+ initialize: function (options) {
110
+ Backgrid.SelectCell.prototype.initialize.apply(this, arguments);
111
+ this.select2Options = options.select2Options || this.select2Options;
112
+ this.listenTo(this.model, "backgrid:edit", function (model, column, cell, editor) {
113
+ if (column.get("name") == this.column.get("name")) {
114
+ editor.setSelect2Options(this.select2Options);
115
+ }
116
+ });
117
+ }
118
+
119
+ });
120
+
121
+ }(window, jQuery, _, Backbone, Backgrid));
@@ -0,0 +1,159 @@
1
+ /*
2
+ backgrid-text-cell
3
+ http://github.com/wyuenho/backgrid
4
+
5
+ Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
6
+ Licensed under the MIT @license.
7
+ */
8
+
9
+ (function (window, $, _, Backbone, Backgrid) {
10
+
11
+ /**
12
+ Renders a form with a text area and a save button in a modal dialog.
13
+
14
+ @class Backgrid.Extension.TextareaEditor
15
+ @extends Backgrid.CellEditor
16
+ */
17
+ var TextareaEditor = Backgrid.Extension.TextareaEditor = Backgrid.CellEditor.extend({
18
+
19
+ /** @property */
20
+ tagName: "div",
21
+
22
+ /** @property */
23
+ className: "modal hide fade",
24
+
25
+ /** @property {function(Object, ?Object=): string} template */
26
+ template: _.template('<form><div class="modal-header"><button type="button" class="close" data-dismiss="modal">&times;</button><h3><%- column.get("label") %></h3></div><div class="modal-body"><textarea cols="<%= cols %>" rows="<%= rows %>"><%- content %></textarea></div><div class="modal-footer"><input class="btn" type="submit" value="Save"/></div></form>'),
27
+
28
+ /** @property */
29
+ cols: 80,
30
+
31
+ /** @property */
32
+ rows: 10,
33
+
34
+ /** @property */
35
+ events: {
36
+ "keydown textarea": "clearError",
37
+ "submit": "saveOrCancel",
38
+ "hide": "saveOrCancel",
39
+ "hidden": "close",
40
+ "shown": "focus"
41
+ },
42
+
43
+ /**
44
+ @property {Object} modalOptions The options passed to Bootstrap's modal
45
+ plugin.
46
+ */
47
+ modalOptions: {
48
+ backdrop: false
49
+ },
50
+
51
+ /**
52
+ Renders a modal form dialog with a textarea, submit button and a close button.
53
+ */
54
+ render: function () {
55
+ this.$el.html($(this.template({
56
+ column: this.column,
57
+ cols: this.cols,
58
+ rows: this.rows,
59
+ content: this.formatter.fromRaw(this.model.get(this.column.get("name")))
60
+ })));
61
+
62
+ this.delegateEvents();
63
+
64
+ this.$el.modal(this.modalOptions);
65
+
66
+ return this;
67
+ },
68
+
69
+ /**
70
+ Event handler. Saves the text in the text area to the model when
71
+ submitting. When cancelling, if the text area is dirty, a confirmation
72
+ dialog will pop up. If the user clicks confirm, the text will be saved to
73
+ the model.
74
+
75
+ Triggers a Backbone `backgrid:error` event from the model along with the
76
+ model, column and the existing value as the parameters if the value
77
+ cannot be converted.
78
+
79
+ @param {Event} e
80
+ */
81
+ saveOrCancel: function (e) {
82
+ if (e && e.type == "submit") {
83
+ e.preventDefault();
84
+ e.stopPropagation();
85
+ }
86
+
87
+ var model = this.model;
88
+ var column = this.column;
89
+ var val = this.$el.find("textarea").val();
90
+ var newValue = this.formatter.toRaw(val);
91
+
92
+ if (_.isUndefined(newValue)) {
93
+ model.trigger("backgrid:error", model, column, val);
94
+
95
+ if (e) {
96
+ e.preventDefault();
97
+ e.stopPropagation();
98
+ }
99
+ }
100
+ else if (!e || e.type == "submit" ||
101
+ (e.type == "hide" &&
102
+ newValue !== (this.model.get(this.column.get("name")) || '').replace(/\r/g, '') &&
103
+ window.confirm("Would you like to save your changes?"))) {
104
+
105
+ model.set(column.get("name"), newValue);
106
+ this.$el.modal("hide");
107
+ }
108
+ else if (e.type != "hide") this.$el.modal("hide");
109
+ },
110
+
111
+ /**
112
+ Clears the error class on the parent cell.
113
+ */
114
+ clearError: _.debounce(function () {
115
+ if (!_.isUndefined(this.formatter.toRaw(this.$el.find("textarea").val()))) {
116
+ this.$el.parent().removeClass("error");
117
+ }
118
+ }, 150),
119
+
120
+ /**
121
+ Triggers a `backgrid:edited` event along with the cell editor as the
122
+ parameter after the modal is hidden.
123
+
124
+ @param {Event} e
125
+ */
126
+ close: function (e) {
127
+ var model = this.model;
128
+ model.trigger("backgrid:edited", model, this.column,
129
+ new Backgrid.Command(e));
130
+ },
131
+
132
+ /**
133
+ Focuses the textarea when the modal is shown.
134
+ */
135
+ focus: function () {
136
+ this.$el.find("textarea").focus();
137
+ }
138
+
139
+ });
140
+
141
+ /**
142
+ TextCell is a string cell type that renders a form with a text area in a
143
+ modal dialog instead of an input box editor. It is best suited for entering
144
+ a large body of text.
145
+
146
+ @class Backgrid.Extension.TextCell
147
+ @extends Backgrid.StringCell
148
+ */
149
+ var TextCell = Backgrid.Extension.TextCell = Backgrid.StringCell.extend({
150
+
151
+ /** @property */
152
+ className: "text-cell",
153
+
154
+ /** @property */
155
+ editor: TextareaEditor
156
+
157
+ });
158
+
159
+ }(window, jQuery, _, Backbone, Backgrid));
@@ -0,0 +1,2531 @@
1
+ /*
2
+ backgrid
3
+ http://github.com/wyuenho/backgrid
4
+
5
+ Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
6
+ Licensed under the MIT @license.
7
+ */
8
+ (function (root, $, _, Backbone) {
9
+
10
+ "use strict";
11
+ /*
12
+ backgrid
13
+ http://github.com/wyuenho/backgrid
14
+
15
+ Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
16
+ Licensed under the MIT @license.
17
+ */
18
+
19
+ var window = root;
20
+
21
+ // Copyright 2009, 2010 Kristopher Michael Kowal
22
+ // https://github.com/kriskowal/es5-shim
23
+ // ES5 15.5.4.20
24
+ // http://es5.github.com/#x15.5.4.20
25
+ var ws = "\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003" +
26
+ "\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028" +
27
+ "\u2029\uFEFF";
28
+ if (!String.prototype.trim || ws.trim()) {
29
+ // http://blog.stevenlevithan.com/archives/faster-trim-javascript
30
+ // http://perfectionkills.com/whitespace-deviations/
31
+ ws = "[" + ws + "]";
32
+ var trimBeginRegexp = new RegExp("^" + ws + ws + "*"),
33
+ trimEndRegexp = new RegExp(ws + ws + "*$");
34
+ String.prototype.trim = function trim() {
35
+ if (this === undefined || this === null) {
36
+ throw new TypeError("can't convert " + this + " to object");
37
+ }
38
+ return String(this)
39
+ .replace(trimBeginRegexp, "")
40
+ .replace(trimEndRegexp, "");
41
+ };
42
+ }
43
+
44
+ function capitalize(s) {
45
+ return String.fromCharCode(s.charCodeAt(0) - 32) + s.slice(1);
46
+ }
47
+
48
+ function lpad(str, length, padstr) {
49
+ var paddingLen = length - (str + '').length;
50
+ paddingLen = paddingLen < 0 ? 0 : paddingLen;
51
+ var padding = '';
52
+ for (var i = 0; i < paddingLen; i++) {
53
+ padding = padding + padstr;
54
+ }
55
+ return padding + str;
56
+ }
57
+
58
+ var Backgrid = root.Backgrid = {
59
+
60
+ VERSION: "0.2.6",
61
+
62
+ Extension: {},
63
+
64
+ requireOptions: function (options, requireOptionKeys) {
65
+ for (var i = 0; i < requireOptionKeys.length; i++) {
66
+ var key = requireOptionKeys[i];
67
+ if (_.isUndefined(options[key])) {
68
+ throw new TypeError("'" + key + "' is required");
69
+ }
70
+ }
71
+ },
72
+
73
+ resolveNameToClass: function (name, suffix) {
74
+ if (_.isString(name)) {
75
+ var key = _.map(name.split('-'), function (e) { return capitalize(e); }).join('') + suffix;
76
+ var klass = Backgrid[key] || Backgrid.Extension[key];
77
+ if (_.isUndefined(klass)) {
78
+ throw new ReferenceError("Class '" + key + "' not found");
79
+ }
80
+ return klass;
81
+ }
82
+
83
+ return name;
84
+ }
85
+ };
86
+ _.extend(Backgrid, Backbone.Events);
87
+
88
+ /**
89
+ Command translates a DOM Event into commands that Backgrid
90
+ recognizes. Interested parties can listen on selected Backgrid events that
91
+ come with an instance of this class and act on the commands.
92
+
93
+ It is also possible to globally rebind the keyboard shortcuts by replacing
94
+ the methods in this class' prototype.
95
+
96
+ @class Backgrid.Command
97
+ @constructor
98
+ */
99
+ var Command = Backgrid.Command = function (evt) {
100
+ _.extend(this, {
101
+ altKey: !!evt.altKey,
102
+ char: evt.char,
103
+ charCode: evt.charCode,
104
+ ctrlKey: !!evt.ctrlKey,
105
+ key: evt.key,
106
+ keyCode: evt.keyCode,
107
+ locale: evt.locale,
108
+ location: evt.location,
109
+ metaKey: !!evt.metaKey,
110
+ repeat: !!evt.repeat,
111
+ shiftKey: !!evt.shiftKey,
112
+ which: evt.which
113
+ });
114
+ };
115
+ _.extend(Command.prototype, {
116
+ /**
117
+ Up Arrow
118
+
119
+ @member Backgrid.Command
120
+ */
121
+ moveUp: function () { return this.keyCode == 38; },
122
+ /**
123
+ Down Arrow
124
+
125
+ @member Backgrid.Command
126
+ */
127
+ moveDown: function () { return this.keyCode === 40; },
128
+ /**
129
+ Shift Tab
130
+
131
+ @member Backgrid.Command
132
+ */
133
+ moveLeft: function () { return this.shiftKey && this.keyCode === 9; },
134
+ /**
135
+ Tab
136
+
137
+ @member Backgrid.Command
138
+ */
139
+ moveRight: function () { return !this.shiftKey && this.keyCode === 9; },
140
+ /**
141
+ Enter
142
+
143
+ @member Backgrid.Command
144
+ */
145
+ save: function () { return this.keyCode === 13; },
146
+ /**
147
+ Esc
148
+
149
+ @member Backgrid.Command
150
+ */
151
+ cancel: function () { return this.keyCode === 27; },
152
+ /**
153
+ None of the above.
154
+
155
+ @member Backgrid.Command
156
+ */
157
+ passThru: function () {
158
+ return !(this.moveUp() || this.moveDown() || this.moveLeft() ||
159
+ this.moveRight() || this.save() || this.cancel());
160
+ }
161
+ });
162
+
163
+ /*
164
+ backgrid
165
+ http://github.com/wyuenho/backgrid
166
+
167
+ Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
168
+ Licensed under the MIT @license.
169
+ */
170
+
171
+ /**
172
+ Just a convenient class for interested parties to subclass.
173
+
174
+ The default Cell classes don't require the formatter to be a subclass of
175
+ Formatter as long as the fromRaw(rawData) and toRaw(formattedData) methods
176
+ are defined.
177
+
178
+ @abstract
179
+ @class Backgrid.CellFormatter
180
+ @constructor
181
+ */
182
+ var CellFormatter = Backgrid.CellFormatter = function () {};
183
+ _.extend(CellFormatter.prototype, {
184
+
185
+ /**
186
+ Takes a raw value from a model and returns an optionally formatted string
187
+ for display. The default implementation simply returns the supplied value
188
+ as is without any type conversion.
189
+
190
+ @member Backgrid.CellFormatter
191
+ @param {*} rawData
192
+ @return {*}
193
+ */
194
+ fromRaw: function (rawData) {
195
+ return rawData;
196
+ },
197
+
198
+ /**
199
+ Takes a formatted string, usually from user input, and returns a
200
+ appropriately typed value for persistence in the model.
201
+
202
+ If the user input is invalid or unable to be converted to a raw value
203
+ suitable for persistence in the model, toRaw must return `undefined`.
204
+
205
+ @member Backgrid.CellFormatter
206
+ @param {string} formattedData
207
+ @return {*|undefined}
208
+ */
209
+ toRaw: function (formattedData) {
210
+ return formattedData;
211
+ }
212
+
213
+ });
214
+
215
+ /**
216
+ A floating point number formatter. Doesn't understand notation at the moment.
217
+
218
+ @class Backgrid.NumberFormatter
219
+ @extends Backgrid.CellFormatter
220
+ @constructor
221
+ @throws {RangeError} If decimals < 0 or > 20.
222
+ */
223
+ var NumberFormatter = Backgrid.NumberFormatter = function (options) {
224
+ options = options ? _.clone(options) : {};
225
+ _.extend(this, this.defaults, options);
226
+
227
+ if (this.decimals < 0 || this.decimals > 20) {
228
+ throw new RangeError("decimals must be between 0 and 20");
229
+ }
230
+ };
231
+ NumberFormatter.prototype = new CellFormatter();
232
+ _.extend(NumberFormatter.prototype, {
233
+
234
+ /**
235
+ @member Backgrid.NumberFormatter
236
+ @cfg {Object} options
237
+
238
+ @cfg {number} [options.decimals=2] Number of decimals to display. Must be an integer.
239
+
240
+ @cfg {string} [options.decimalSeparator='.'] The separator to use when
241
+ displaying decimals.
242
+
243
+ @cfg {string} [options.orderSeparator=','] The separator to use to
244
+ separator thousands. May be an empty string.
245
+ */
246
+ defaults: {
247
+ decimals: 2,
248
+ decimalSeparator: '.',
249
+ orderSeparator: ','
250
+ },
251
+
252
+ HUMANIZED_NUM_RE: /(\d)(?=(?:\d{3})+$)/g,
253
+
254
+ /**
255
+ Takes a floating point number and convert it to a formatted string where
256
+ every thousand is separated by `orderSeparator`, with a `decimal` number of
257
+ decimals separated by `decimalSeparator`. The number returned is rounded
258
+ the usual way.
259
+
260
+ @member Backgrid.NumberFormatter
261
+ @param {number} number
262
+ @return {string}
263
+ */
264
+ fromRaw: function (number) {
265
+ if (_.isNull(number) || _.isUndefined(number)) return '';
266
+
267
+ number = number.toFixed(~~this.decimals);
268
+
269
+ var parts = number.split('.');
270
+ var integerPart = parts[0];
271
+ var decimalPart = parts[1] ? (this.decimalSeparator || '.') + parts[1] : '';
272
+
273
+ return integerPart.replace(this.HUMANIZED_NUM_RE, '$1' + this.orderSeparator) + decimalPart;
274
+ },
275
+
276
+ /**
277
+ Takes a string, possibly formatted with `orderSeparator` and/or
278
+ `decimalSeparator`, and convert it back to a number.
279
+
280
+ @member Backgrid.NumberFormatter
281
+ @param {string} formattedData
282
+ @return {number|undefined} Undefined if the string cannot be converted to
283
+ a number.
284
+ */
285
+ toRaw: function (formattedData) {
286
+ var rawData = '';
287
+
288
+ var thousands = formattedData.trim().split(this.orderSeparator);
289
+ for (var i = 0; i < thousands.length; i++) {
290
+ rawData += thousands[i];
291
+ }
292
+
293
+ var decimalParts = rawData.split(this.decimalSeparator);
294
+ rawData = '';
295
+ for (var i = 0; i < decimalParts.length; i++) {
296
+ rawData = rawData + decimalParts[i] + '.';
297
+ }
298
+
299
+ if (rawData[rawData.length - 1] === '.') {
300
+ rawData = rawData.slice(0, rawData.length - 1);
301
+ }
302
+
303
+ var result = (rawData * 1).toFixed(~~this.decimals) * 1;
304
+ if (_.isNumber(result) && !_.isNaN(result)) return result;
305
+ }
306
+
307
+ });
308
+
309
+ /**
310
+ Formatter to converts between various datetime formats.
311
+
312
+ This class only understands ISO-8601 formatted datetime strings and UNIX
313
+ offset (number of milliseconds since UNIX Epoch). See
314
+ Backgrid.Extension.MomentFormatter if you need a much more flexible datetime
315
+ formatter.
316
+
317
+ @class Backgrid.DatetimeFormatter
318
+ @extends Backgrid.CellFormatter
319
+ @constructor
320
+ @throws {Error} If both `includeDate` and `includeTime` are false.
321
+ */
322
+ var DatetimeFormatter = Backgrid.DatetimeFormatter = function (options) {
323
+ options = options ? _.clone(options) : {};
324
+ _.extend(this, this.defaults, options);
325
+
326
+ if (!this.includeDate && !this.includeTime) {
327
+ throw new Error("Either includeDate or includeTime must be true");
328
+ }
329
+ };
330
+ DatetimeFormatter.prototype = new CellFormatter();
331
+ _.extend(DatetimeFormatter.prototype, {
332
+
333
+ /**
334
+ @member Backgrid.DatetimeFormatter
335
+
336
+ @cfg {Object} options
337
+
338
+ @cfg {boolean} [options.includeDate=true] Whether the values include the
339
+ date part.
340
+
341
+ @cfg {boolean} [options.includeTime=true] Whether the values include the
342
+ time part.
343
+
344
+ @cfg {boolean} [options.includeMilli=false] If `includeTime` is true,
345
+ whether to include the millisecond part, if it exists.
346
+ */
347
+ defaults: {
348
+ includeDate: true,
349
+ includeTime: true,
350
+ includeMilli: false
351
+ },
352
+
353
+ DATE_RE: /^([+\-]?\d{4})-(\d{2})-(\d{2})$/,
354
+ TIME_RE: /^(\d{2}):(\d{2}):(\d{2})(\.(\d{3}))?$/,
355
+ ISO_SPLITTER_RE: /T|Z| +/,
356
+
357
+ _convert: function (data, validate) {
358
+ var date, time = null;
359
+ if (_.isNumber(data)) {
360
+ var jsDate = new Date(data);
361
+ date = lpad(jsDate.getUTCFullYear(), 4, 0) + '-' + lpad(jsDate.getUTCMonth() + 1, 2, 0) + '-' + lpad(jsDate.getUTCDate(), 2, 0);
362
+ time = lpad(jsDate.getUTCHours(), 2, 0) + ':' + lpad(jsDate.getUTCMinutes(), 2, 0) + ':' + lpad(jsDate.getUTCSeconds(), 2, 0);
363
+ }
364
+ else {
365
+ data = data.trim();
366
+ var parts = data.split(this.ISO_SPLITTER_RE) || [];
367
+ date = this.DATE_RE.test(parts[0]) ? parts[0] : '';
368
+ time = date && parts[1] ? parts[1] : this.TIME_RE.test(parts[0]) ? parts[0] : '';
369
+ }
370
+
371
+ var YYYYMMDD = this.DATE_RE.exec(date) || [];
372
+ var HHmmssSSS = this.TIME_RE.exec(time) || [];
373
+
374
+ if (validate) {
375
+ if (this.includeDate && _.isUndefined(YYYYMMDD[0])) return;
376
+ if (this.includeTime && _.isUndefined(HHmmssSSS[0])) return;
377
+ if (!this.includeDate && date) return;
378
+ if (!this.includeTime && time) return;
379
+ }
380
+
381
+ var jsDate = new Date(Date.UTC(YYYYMMDD[1] * 1 || 0,
382
+ YYYYMMDD[2] * 1 - 1 || 0,
383
+ YYYYMMDD[3] * 1 || 0,
384
+ HHmmssSSS[1] * 1 || null,
385
+ HHmmssSSS[2] * 1 || null,
386
+ HHmmssSSS[3] * 1 || null,
387
+ HHmmssSSS[5] * 1 || null));
388
+
389
+ var result = '';
390
+
391
+ if (this.includeDate) {
392
+ result = lpad(jsDate.getUTCFullYear(), 4, 0) + '-' + lpad(jsDate.getUTCMonth() + 1, 2, 0) + '-' + lpad(jsDate.getUTCDate(), 2, 0);
393
+ }
394
+
395
+ if (this.includeTime) {
396
+ result = result + (this.includeDate ? 'T' : '') + lpad(jsDate.getUTCHours(), 2, 0) + ':' + lpad(jsDate.getUTCMinutes(), 2, 0) + ':' + lpad(jsDate.getUTCSeconds(), 2, 0);
397
+
398
+ if (this.includeMilli) {
399
+ result = result + '.' + lpad(jsDate.getUTCMilliseconds(), 3, 0);
400
+ }
401
+ }
402
+
403
+ if (this.includeDate && this.includeTime) {
404
+ result += "Z";
405
+ }
406
+
407
+ return result;
408
+ },
409
+
410
+ /**
411
+ Converts an ISO-8601 formatted datetime string to a datetime string, date
412
+ string or a time string. The timezone is ignored if supplied.
413
+
414
+ @member Backgrid.DatetimeFormatter
415
+ @param {string} rawData
416
+ @return {string|null|undefined} ISO-8601 string in UTC. Null and undefined
417
+ values are returned as is.
418
+ */
419
+ fromRaw: function (rawData) {
420
+ if (_.isNull(rawData) || _.isUndefined(rawData)) return '';
421
+ return this._convert(rawData);
422
+ },
423
+
424
+ /**
425
+ Converts an ISO-8601 formatted datetime string to a datetime string, date
426
+ string or a time string. The timezone is ignored if supplied. This method
427
+ parses the input values exactly the same way as
428
+ Backgrid.Extension.MomentFormatter#fromRaw(), in addition to doing some
429
+ sanity checks.
430
+
431
+ @member Backgrid.DatetimeFormatter
432
+ @param {string} formattedData
433
+ @return {string|undefined} ISO-8601 string in UTC. Undefined if a date is
434
+ found when `includeDate` is false, or a time is found when `includeTime` is
435
+ false, or if `includeDate` is true and a date is not found, or if
436
+ `includeTime` is true and a time is not found.
437
+ */
438
+ toRaw: function (formattedData) {
439
+ return this._convert(formattedData, true);
440
+ }
441
+
442
+ });
443
+
444
+ /**
445
+ Formatter to convert any value to string.
446
+
447
+ @class Backgrid.StringFormatter
448
+ @extends Backgrid.CellFormatter
449
+ @constructor
450
+ */
451
+ var StringFormatter = Backgrid.StringFormatter = function () {};
452
+ StringFormatter.prototype = new CellFormatter();
453
+ _.extend(StringFormatter.prototype, {
454
+ /**
455
+ Converts any value to a string using Ecmascript's implicit type
456
+ conversion. If the given value is `null` or `undefined`, an empty string is
457
+ returned instead.
458
+
459
+ @member Backgrid.StringFormatter
460
+ @param {*} rawValue
461
+ @return {string}
462
+ */
463
+ fromRaw: function (rawValue) {
464
+ if (_.isUndefined(rawValue) || _.isNull(rawValue)) return '';
465
+ return rawValue + '';
466
+ }
467
+ });
468
+
469
+ /**
470
+ Simple email validation formatter.
471
+
472
+ @class Backgrid.EmailFormatter
473
+ @extends Backgrid.CellFormatter
474
+ @constructor
475
+ */
476
+ var EmailFormatter = Backgrid.EmailFormatter = function () {};
477
+ EmailFormatter.prototype = new CellFormatter();
478
+ _.extend(EmailFormatter.prototype, {
479
+ /**
480
+ Return the input if it is a string that contains an '@' character and if
481
+ the strings before and after '@' are non-empty. If the input does not
482
+ validate, `undefined` is returned.
483
+
484
+ @member Backgrid.EmailFormatter
485
+ @param {*} formattedData
486
+ @return {string|undefined}
487
+ */
488
+ toRaw: function (formattedData) {
489
+ var parts = formattedData.trim().split("@");
490
+ if (parts.length === 2 && _.all(parts)) {
491
+ return formattedData;
492
+ }
493
+ }
494
+ });
495
+
496
+ /**
497
+ Formatter for SelectCell.
498
+
499
+ @class Backgrid.SelectFormatter
500
+ @extends Backgrid.CellFormatter
501
+ @constructor
502
+ */
503
+ var SelectFormatter = Backgrid.SelectFormatter = function () {};
504
+ SelectFormatter.prototype = new CellFormatter();
505
+ _.extend(SelectFormatter.prototype, {
506
+
507
+ /**
508
+ Normalizes raw scalar or array values to an array.
509
+
510
+ @member Backgrid.SelectFormatter
511
+ @param {*} rawValue
512
+ @return {Array.<*>}
513
+ */
514
+ fromRaw: function (rawValue) {
515
+ return _.isArray(rawValue) ? rawValue : rawValue != null ? [rawValue] : [];
516
+ }
517
+ });
518
+
519
+ /*
520
+ backgrid
521
+ http://github.com/wyuenho/backgrid
522
+
523
+ Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
524
+ Licensed under the MIT @license.
525
+ */
526
+
527
+ /**
528
+ Generic cell editor base class. Only defines an initializer for a number of
529
+ required parameters.
530
+
531
+ @abstract
532
+ @class Backgrid.CellEditor
533
+ @extends Backbone.View
534
+ */
535
+ var CellEditor = Backgrid.CellEditor = Backbone.View.extend({
536
+
537
+ /**
538
+ Initializer.
539
+
540
+ @param {Object} options
541
+ @param {Backgrid.CellFormatter} options.formatter
542
+ @param {Backgrid.Column} options.column
543
+ @param {Backbone.Model} options.model
544
+
545
+ @throws {TypeError} If `formatter` is not a formatter instance, or when
546
+ `model` or `column` are undefined.
547
+ */
548
+ initialize: function (options) {
549
+ Backgrid.requireOptions(options, ["formatter", "column", "model"]);
550
+ this.formatter = options.formatter;
551
+ this.column = options.column;
552
+ if (!(this.column instanceof Column)) {
553
+ this.column = new Column(this.column);
554
+ }
555
+
556
+ this.listenTo(this.model, "backgrid:editing", this.postRender);
557
+ },
558
+
559
+ /**
560
+ Post-rendering setup and initialization. Focuses the cell editor's `el` in
561
+ this default implementation. **Should** be called by Cell classes after
562
+ calling Backgrid.CellEditor#render.
563
+ */
564
+ postRender: function (model, column) {
565
+ if (column == null || column.get("name") == this.column.get("name")) {
566
+ this.$el.focus();
567
+ }
568
+ return this;
569
+ }
570
+
571
+ });
572
+
573
+ /**
574
+ InputCellEditor the cell editor type used by most core cell types. This cell
575
+ editor renders a text input box as its editor. The input will render a
576
+ placeholder if the value is empty on supported browsers.
577
+
578
+ @class Backgrid.InputCellEditor
579
+ @extends Backgrid.CellEditor
580
+ */
581
+ var InputCellEditor = Backgrid.InputCellEditor = CellEditor.extend({
582
+
583
+ /** @property */
584
+ tagName: "input",
585
+
586
+ /** @property */
587
+ attributes: {
588
+ type: "text"
589
+ },
590
+
591
+ /** @property */
592
+ events: {
593
+ "blur": "saveOrCancel",
594
+ "keydown": "saveOrCancel"
595
+ },
596
+
597
+ /**
598
+ Initializer. Removes this `el` from the DOM when a `done` event is
599
+ triggered.
600
+
601
+ @param {Object} options
602
+ @param {Backgrid.CellFormatter} options.formatter
603
+ @param {Backgrid.Column} options.column
604
+ @param {Backbone.Model} options.model
605
+ @param {string} [options.placeholder]
606
+ */
607
+ initialize: function (options) {
608
+ CellEditor.prototype.initialize.apply(this, arguments);
609
+
610
+ if (options.placeholder) {
611
+ this.$el.attr("placeholder", options.placeholder);
612
+ }
613
+ },
614
+
615
+ /**
616
+ Renders a text input with the cell value formatted for display, if it
617
+ exists.
618
+ */
619
+ render: function () {
620
+ this.$el.val(this.formatter.fromRaw(this.model.get(this.column.get("name"))));
621
+ return this;
622
+ },
623
+
624
+ /**
625
+ If the key pressed is `enter`, `tab`, `up`, or `down`, converts the value
626
+ in the editor to a raw value for saving into the model using the formatter.
627
+
628
+ If the key pressed is `esc` the changes are undone.
629
+
630
+ If the editor goes out of focus (`blur`) but the value is invalid, the
631
+ event is intercepted and cancelled so the cell remains in focus pending for
632
+ further action. The changes are saved otherwise.
633
+
634
+ Triggers a Backbone `backgrid:edited` event from the model when successful,
635
+ and `backgrid:error` if the value cannot be converted. Classes listening to
636
+ the `error` event, usually the Cell classes, should respond appropriately,
637
+ usually by rendering some kind of error feedback.
638
+
639
+ @param {Event} e
640
+ */
641
+ saveOrCancel: function (e) {
642
+
643
+ var formatter = this.formatter;
644
+ var model = this.model;
645
+ var column = this.column;
646
+
647
+ var command = new Command(e);
648
+ var blurred = e.type === "blur";
649
+
650
+ if (command.moveUp() || command.moveDown() || command.moveLeft() || command.moveRight() ||
651
+ command.save() || blurred) {
652
+
653
+ e.preventDefault();
654
+ e.stopPropagation();
655
+
656
+ var val = this.$el.val();
657
+ var newValue = formatter.toRaw(val);
658
+ if (_.isUndefined(newValue)) {
659
+ model.trigger("backgrid:error", model, column, val);
660
+ }
661
+ else {
662
+ model.set(column.get("name"), newValue);
663
+ model.trigger("backgrid:edited", model, column, command);
664
+ }
665
+ }
666
+ // esc
667
+ else if (command.cancel()) {
668
+ // undo
669
+ e.stopPropagation();
670
+ model.trigger("backgrid:edited", model, column, command);
671
+ }
672
+ },
673
+
674
+ postRender: function (model, column) {
675
+ if (column == null || column.get("name") == this.column.get("name")) {
676
+ // move the cursor to the end on firefox if text is right aligned
677
+ if (this.$el.css("text-align") === "right") {
678
+ var val = this.$el.val();
679
+ this.$el.focus().val(null).val(val);
680
+ }
681
+ else this.$el.focus();
682
+ }
683
+ return this;
684
+ }
685
+
686
+ });
687
+
688
+ /**
689
+ The super-class for all Cell types. By default, this class renders a plain
690
+ table cell with the model value converted to a string using the
691
+ formatter. The table cell is clickable, upon which the cell will go into
692
+ editor mode, which is rendered by a Backgrid.InputCellEditor instance by
693
+ default. Upon encountering any formatting errors, this class will add an
694
+ `error` CSS class to the table cell.
695
+
696
+ @abstract
697
+ @class Backgrid.Cell
698
+ @extends Backbone.View
699
+ */
700
+ var Cell = Backgrid.Cell = Backbone.View.extend({
701
+
702
+ /** @property */
703
+ tagName: "td",
704
+
705
+ /**
706
+ @property {Backgrid.CellFormatter|Object|string} [formatter=new CellFormatter()]
707
+ */
708
+ formatter: new CellFormatter(),
709
+
710
+ /**
711
+ @property {Backgrid.CellEditor} [editor=Backgrid.InputCellEditor] The
712
+ default editor for all cell instances of this class. This value must be a
713
+ class, it will be automatically instantiated upon entering edit mode.
714
+
715
+ See Backgrid.CellEditor
716
+ */
717
+ editor: InputCellEditor,
718
+
719
+ /** @property */
720
+ events: {
721
+ "click": "enterEditMode"
722
+ },
723
+
724
+ /**
725
+ Initializer.
726
+
727
+ @param {Object} options
728
+ @param {Backbone.Model} options.model
729
+ @param {Backgrid.Column} options.column
730
+
731
+ @throws {ReferenceError} If formatter is a string but a formatter class of
732
+ said name cannot be found in the Backgrid module.
733
+ */
734
+ initialize: function (options) {
735
+ Backgrid.requireOptions(options, ["model", "column"]);
736
+ this.column = options.column;
737
+ if (!(this.column instanceof Column)) {
738
+ this.column = new Column(this.column);
739
+ }
740
+ this.formatter = Backgrid.resolveNameToClass(this.column.get("formatter") || this.formatter, "Formatter");
741
+ this.editor = Backgrid.resolveNameToClass(this.editor, "CellEditor");
742
+ this.listenTo(this.model, "change:" + this.column.get("name"), function () {
743
+ if (!this.$el.hasClass("editor")) this.render();
744
+ });
745
+ this.listenTo(this.model, "backgrid:error", this.renderError);
746
+ },
747
+
748
+ /**
749
+ Render a text string in a table cell. The text is converted from the
750
+ model's raw value for this cell's column.
751
+ */
752
+ render: function () {
753
+ this.$el.empty();
754
+ this.$el.text(this.formatter.fromRaw(this.model.get(this.column.get("name"))));
755
+ this.delegateEvents();
756
+ return this;
757
+ },
758
+
759
+ /**
760
+ If this column is editable, a new CellEditor instance is instantiated with
761
+ its required parameters. An `editor` CSS class is added to the cell upon
762
+ entering edit mode.
763
+
764
+ This method triggers a Backbone `backgrid:edit` event from the model when
765
+ the cell is entering edit mode and an editor instance has been constructed,
766
+ but before it is rendered and inserted into the DOM. The cell and the
767
+ constructed cell editor instance are sent as event parameters when this
768
+ event is triggered.
769
+
770
+ When this cell has finished switching to edit mode, a Backbone
771
+ `backgrid:editing` event is triggered from the model. The cell and the
772
+ constructed cell instance are also sent as parameters in the event.
773
+
774
+ When the model triggers a `backgrid:error` event, it means the editor is
775
+ unable to convert the current user input to an apprpriate value for the
776
+ model's column, and an `error` CSS class is added to the cell accordingly.
777
+ */
778
+ enterEditMode: function () {
779
+ var model = this.model;
780
+ var column = this.column;
781
+
782
+ if (column.get("editable")) {
783
+
784
+ this.currentEditor = new this.editor({
785
+ column: this.column,
786
+ model: this.model,
787
+ formatter: this.formatter
788
+ });
789
+
790
+ model.trigger("backgrid:edit", model, column, this, this.currentEditor);
791
+
792
+ // Need to redundantly undelegate events for Firefox
793
+ this.undelegateEvents();
794
+ this.$el.empty();
795
+ this.$el.append(this.currentEditor.$el);
796
+ this.currentEditor.render();
797
+ this.$el.addClass("editor");
798
+
799
+ model.trigger("backgrid:editing", model, column, this, this.currentEditor);
800
+ }
801
+ },
802
+
803
+ /**
804
+ Put an `error` CSS class on the table cell.
805
+ */
806
+ renderError: function (model, column) {
807
+ if (column == null || column.get("name") == this.column.get("name")) {
808
+ this.$el.addClass("error");
809
+ }
810
+ },
811
+
812
+ /**
813
+ Removes the editor and re-render in display mode.
814
+ */
815
+ exitEditMode: function () {
816
+ this.$el.removeClass("error");
817
+ this.currentEditor.remove();
818
+ this.stopListening(this.currentEditor);
819
+ delete this.currentEditor;
820
+ this.$el.removeClass("editor");
821
+ this.render();
822
+ },
823
+
824
+ /**
825
+ Clean up this cell.
826
+
827
+ @chainable
828
+ */
829
+ remove: function () {
830
+ if (this.currentEditor) {
831
+ this.currentEditor.remove.apply(this, arguments);
832
+ delete this.currentEditor;
833
+ }
834
+ return Backbone.View.prototype.remove.apply(this, arguments);
835
+ }
836
+
837
+ });
838
+
839
+ /**
840
+ StringCell displays HTML escaped strings and accepts anything typed in.
841
+
842
+ @class Backgrid.StringCell
843
+ @extends Backgrid.Cell
844
+ */
845
+ var StringCell = Backgrid.StringCell = Cell.extend({
846
+
847
+ /** @property */
848
+ className: "string-cell",
849
+
850
+ formatter: new StringFormatter()
851
+
852
+ });
853
+
854
+ /**
855
+ UriCell renders an HTML `<a>` anchor for the value and accepts URIs as user
856
+ input values. No type conversion or URL validation is done by the formatter
857
+ of this cell. Users who need URL validation are encourage to subclass UriCell
858
+ to take advantage of the parsing capabilities of the HTMLAnchorElement
859
+ available on HTML5-capable browsers or using a third-party library like
860
+ [URI.js](https://github.com/medialize/URI.js).
861
+
862
+ @class Backgrid.UriCell
863
+ @extends Backgrid.Cell
864
+ */
865
+ var UriCell = Backgrid.UriCell = Cell.extend({
866
+
867
+ /** @property */
868
+ className: "uri-cell",
869
+
870
+ render: function () {
871
+ this.$el.empty();
872
+ var formattedValue = this.formatter.fromRaw(this.model.get(this.column.get("name")));
873
+ this.$el.append($("<a>", {
874
+ tabIndex: -1,
875
+ href: formattedValue,
876
+ title: formattedValue,
877
+ target: "_blank"
878
+ }).text(formattedValue));
879
+ this.delegateEvents();
880
+ return this;
881
+ }
882
+
883
+ });
884
+
885
+ /**
886
+ Like Backgrid.UriCell, EmailCell renders an HTML `<a>` anchor for the
887
+ value. The `href` in the anchor is prefixed with `mailto:`. EmailCell will
888
+ complain if the user enters a string that doesn't contain the `@` sign.
889
+
890
+ @class Backgrid.EmailCell
891
+ @extends Backgrid.StringCell
892
+ */
893
+ var EmailCell = Backgrid.EmailCell = StringCell.extend({
894
+
895
+ /** @property */
896
+ className: "email-cell",
897
+
898
+ formatter: new EmailFormatter(),
899
+
900
+ render: function () {
901
+ this.$el.empty();
902
+ var formattedValue = this.formatter.fromRaw(this.model.get(this.column.get("name")));
903
+ this.$el.append($("<a>", {
904
+ tabIndex: -1,
905
+ href: "mailto:" + formattedValue,
906
+ title: formattedValue
907
+ }).text(formattedValue));
908
+ this.delegateEvents();
909
+ return this;
910
+ }
911
+
912
+ });
913
+
914
+ /**
915
+ NumberCell is a generic cell that renders all numbers. Numbers are formatted
916
+ using a Backgrid.NumberFormatter.
917
+
918
+ @class Backgrid.NumberCell
919
+ @extends Backgrid.Cell
920
+ */
921
+ var NumberCell = Backgrid.NumberCell = Cell.extend({
922
+
923
+ /** @property */
924
+ className: "number-cell",
925
+
926
+ /**
927
+ @property {number} [decimals=2] Must be an integer.
928
+ */
929
+ decimals: NumberFormatter.prototype.defaults.decimals,
930
+
931
+ /** @property {string} [decimalSeparator='.'] */
932
+ decimalSeparator: NumberFormatter.prototype.defaults.decimalSeparator,
933
+
934
+ /** @property {string} [orderSeparator=','] */
935
+ orderSeparator: NumberFormatter.prototype.defaults.orderSeparator,
936
+
937
+ /** @property {Backgrid.CellFormatter} [formatter=Backgrid.NumberFormatter] */
938
+ formatter: NumberFormatter,
939
+
940
+ /**
941
+ Initializes this cell and the number formatter.
942
+
943
+ @param {Object} options
944
+ @param {Backbone.Model} options.model
945
+ @param {Backgrid.Column} options.column
946
+ */
947
+ initialize: function (options) {
948
+ Cell.prototype.initialize.apply(this, arguments);
949
+ this.formatter = new this.formatter({
950
+ decimals: this.decimals,
951
+ decimalSeparator: this.decimalSeparator,
952
+ orderSeparator: this.orderSeparator
953
+ });
954
+ }
955
+
956
+ });
957
+
958
+ /**
959
+ An IntegerCell is just a Backgrid.NumberCell with 0 decimals. If a floating
960
+ point number is supplied, the number is simply rounded the usual way when
961
+ displayed.
962
+
963
+ @class Backgrid.IntegerCell
964
+ @extends Backgrid.NumberCell
965
+ */
966
+ var IntegerCell = Backgrid.IntegerCell = NumberCell.extend({
967
+
968
+ /** @property */
969
+ className: "integer-cell",
970
+
971
+ /**
972
+ @property {number} decimals Must be an integer.
973
+ */
974
+ decimals: 0
975
+ });
976
+
977
+ /**
978
+ DatetimeCell is a basic cell that accepts datetime string values in RFC-2822
979
+ or W3C's subset of ISO-8601 and displays them in ISO-8601 format. For a much
980
+ more sophisticated date time cell with better datetime formatting, take a
981
+ look at the Backgrid.Extension.MomentCell extension.
982
+
983
+ @class Backgrid.DatetimeCell
984
+ @extends Backgrid.Cell
985
+
986
+ See:
987
+
988
+ - Backgrid.Extension.MomentCell
989
+ - Backgrid.DatetimeFormatter
990
+ */
991
+ var DatetimeCell = Backgrid.DatetimeCell = Cell.extend({
992
+
993
+ /** @property */
994
+ className: "datetime-cell",
995
+
996
+ /**
997
+ @property {boolean} [includeDate=true]
998
+ */
999
+ includeDate: DatetimeFormatter.prototype.defaults.includeDate,
1000
+
1001
+ /**
1002
+ @property {boolean} [includeTime=true]
1003
+ */
1004
+ includeTime: DatetimeFormatter.prototype.defaults.includeTime,
1005
+
1006
+ /**
1007
+ @property {boolean} [includeMilli=false]
1008
+ */
1009
+ includeMilli: DatetimeFormatter.prototype.defaults.includeMilli,
1010
+
1011
+ /** @property {Backgrid.CellFormatter} [formatter=Backgrid.DatetimeFormatter] */
1012
+ formatter: DatetimeFormatter,
1013
+
1014
+ /**
1015
+ Initializes this cell and the datetime formatter.
1016
+
1017
+ @param {Object} options
1018
+ @param {Backbone.Model} options.model
1019
+ @param {Backgrid.Column} options.column
1020
+ */
1021
+ initialize: function (options) {
1022
+ Cell.prototype.initialize.apply(this, arguments);
1023
+ this.formatter = new this.formatter({
1024
+ includeDate: this.includeDate,
1025
+ includeTime: this.includeTime,
1026
+ includeMilli: this.includeMilli
1027
+ });
1028
+
1029
+ var placeholder = this.includeDate ? "YYYY-MM-DD" : "";
1030
+ placeholder += (this.includeDate && this.includeTime) ? "T" : "";
1031
+ placeholder += this.includeTime ? "HH:mm:ss" : "";
1032
+ placeholder += (this.includeTime && this.includeMilli) ? ".SSS" : "";
1033
+
1034
+ this.editor = this.editor.extend({
1035
+ attributes: _.extend({}, this.editor.prototype.attributes, this.editor.attributes, {
1036
+ placeholder: placeholder
1037
+ })
1038
+ });
1039
+ }
1040
+
1041
+ });
1042
+
1043
+ /**
1044
+ DateCell is a Backgrid.DatetimeCell without the time part.
1045
+
1046
+ @class Backgrid.DateCell
1047
+ @extends Backgrid.DatetimeCell
1048
+ */
1049
+ var DateCell = Backgrid.DateCell = DatetimeCell.extend({
1050
+
1051
+ /** @property */
1052
+ className: "date-cell",
1053
+
1054
+ /** @property */
1055
+ includeTime: false
1056
+
1057
+ });
1058
+
1059
+ /**
1060
+ TimeCell is a Backgrid.DatetimeCell without the date part.
1061
+
1062
+ @class Backgrid.TimeCell
1063
+ @extends Backgrid.DatetimeCell
1064
+ */
1065
+ var TimeCell = Backgrid.TimeCell = DatetimeCell.extend({
1066
+
1067
+ /** @property */
1068
+ className: "time-cell",
1069
+
1070
+ /** @property */
1071
+ includeDate: false
1072
+
1073
+ });
1074
+
1075
+ /**
1076
+ BooleanCellEditor renders a checkbox as its editor.
1077
+
1078
+ @class Backgrid.BooleanCellEditor
1079
+ @extends Backgrid.CellEditor
1080
+ */
1081
+ var BooleanCellEditor = Backgrid.BooleanCellEditor = CellEditor.extend({
1082
+
1083
+ /** @property */
1084
+ tagName: "input",
1085
+
1086
+ /** @property */
1087
+ attributes: {
1088
+ tabIndex: -1,
1089
+ type: "checkbox"
1090
+ },
1091
+
1092
+ /** @property */
1093
+ events: {
1094
+ "mousedown": function () {
1095
+ this.mouseDown = true;
1096
+ },
1097
+ "blur": "enterOrExitEditMode",
1098
+ "mouseup": function () {
1099
+ this.mouseDown = false;
1100
+ },
1101
+ "change": "saveOrCancel",
1102
+ "keydown": "saveOrCancel"
1103
+ },
1104
+
1105
+ /**
1106
+ Renders a checkbox and check it if the model value of this column is true,
1107
+ uncheck otherwise.
1108
+ */
1109
+ render: function () {
1110
+ var val = this.formatter.fromRaw(this.model.get(this.column.get("name")));
1111
+ this.$el.prop("checked", val);
1112
+ return this;
1113
+ },
1114
+
1115
+ /**
1116
+ Event handler. Hack to deal with the case where `blur` is fired before
1117
+ `change` and `click` on a checkbox.
1118
+ */
1119
+ enterOrExitEditMode: function (e) {
1120
+ if (!this.mouseDown) {
1121
+ var model = this.model;
1122
+ model.trigger("backgrid:edited", model, this.column, new Command(e));
1123
+ }
1124
+ },
1125
+
1126
+ /**
1127
+ Event handler. Save the value into the model if the event is `change` or
1128
+ one of the keyboard navigation key presses. Exit edit mode without saving
1129
+ if `escape` was pressed.
1130
+ */
1131
+ saveOrCancel: function (e) {
1132
+ var model = this.model;
1133
+ var column = this.column;
1134
+ var formatter = this.formatter;
1135
+ var command = new Command(e);
1136
+ // skip ahead to `change` when space is pressed
1137
+ if (command.passThru() && e.type != "change") return true;
1138
+ if (command.cancel()) {
1139
+ e.stopPropagation();
1140
+ model.trigger("backgrid:edited", model, column, command);
1141
+ }
1142
+
1143
+ var $el = this.$el;
1144
+ if (command.save() || command.moveLeft() || command.moveRight() || command.moveUp() ||
1145
+ command.moveDown()) {
1146
+ e.preventDefault();
1147
+ e.stopPropagation();
1148
+ var val = formatter.toRaw($el.prop("checked"));
1149
+ model.set(column.get("name"), val);
1150
+ model.trigger("backgrid:edited", model, column, command);
1151
+ }
1152
+ else if (e.type == "change") {
1153
+ var val = formatter.toRaw($el.prop("checked"));
1154
+ model.set(column.get("name"), val);
1155
+ $el.focus();
1156
+ }
1157
+ }
1158
+
1159
+ });
1160
+
1161
+ /**
1162
+ BooleanCell renders a checkbox both during display mode and edit mode. The
1163
+ checkbox is checked if the model value is true, unchecked otherwise.
1164
+
1165
+ @class Backgrid.BooleanCell
1166
+ @extends Backgrid.Cell
1167
+ */
1168
+ var BooleanCell = Backgrid.BooleanCell = Cell.extend({
1169
+
1170
+ /** @property */
1171
+ className: "boolean-cell",
1172
+
1173
+ /** @property */
1174
+ editor: BooleanCellEditor,
1175
+
1176
+ /** @property */
1177
+ events: {
1178
+ "click": "enterEditMode"
1179
+ },
1180
+
1181
+ /**
1182
+ Renders a checkbox and check it if the model value of this column is true,
1183
+ uncheck otherwise.
1184
+ */
1185
+ render: function () {
1186
+ this.$el.empty();
1187
+ this.$el.append($("<input>", {
1188
+ tabIndex: -1,
1189
+ type: "checkbox",
1190
+ checked: this.formatter.fromRaw(this.model.get(this.column.get("name")))
1191
+ }));
1192
+ this.delegateEvents();
1193
+ return this;
1194
+ }
1195
+
1196
+ });
1197
+
1198
+ /**
1199
+ SelectCellEditor renders an HTML `<select>` fragment as the editor.
1200
+
1201
+ @class Backgrid.SelectCellEditor
1202
+ @extends Backgrid.CellEditor
1203
+ */
1204
+ var SelectCellEditor = Backgrid.SelectCellEditor = CellEditor.extend({
1205
+
1206
+ /** @property */
1207
+ tagName: "select",
1208
+
1209
+ /** @property */
1210
+ events: {
1211
+ "change": "save",
1212
+ "blur": "close",
1213
+ "keydown": "close"
1214
+ },
1215
+
1216
+ /** @property {function(Object, ?Object=): string} template */
1217
+ template: _.template('<option value="<%- value %>" <%= selected ? \'selected="selected"\' : "" %>><%- text %></option>'),
1218
+
1219
+ setOptionValues: function (optionValues) {
1220
+ this.optionValues = optionValues;
1221
+ },
1222
+
1223
+ setMultiple: function (multiple) {
1224
+ this.multiple = multiple;
1225
+ this.$el.prop("multiple", multiple);
1226
+ },
1227
+
1228
+ _renderOptions: function (nvps, selectedValues) {
1229
+ var options = '';
1230
+ for (var i = 0; i < nvps.length; i++) {
1231
+ options = options + this.template({
1232
+ text: nvps[i][0],
1233
+ value: nvps[i][1],
1234
+ selected: selectedValues.indexOf(nvps[i][1]) > -1
1235
+ });
1236
+ }
1237
+ return options;
1238
+ },
1239
+
1240
+ /**
1241
+ Renders the options if `optionValues` is a list of name-value pairs. The
1242
+ options are contained inside option groups if `optionValues` is a list of
1243
+ object hashes. The name is rendered at the option text and the value is the
1244
+ option value. If `optionValues` is a function, it is called without a
1245
+ parameter.
1246
+ */
1247
+ render: function () {
1248
+ this.$el.empty();
1249
+
1250
+ var optionValues = _.result(this, "optionValues");
1251
+ var selectedValues = this.formatter.fromRaw(this.model.get(this.column.get("name")));
1252
+
1253
+ if (!_.isArray(optionValues)) throw TypeError("optionValues must be an array");
1254
+
1255
+ var optionValue = null;
1256
+ var optionText = null;
1257
+ var optionValue = null;
1258
+ var optgroupName = null;
1259
+ var optgroup = null;
1260
+
1261
+ for (var i = 0; i < optionValues.length; i++) {
1262
+ var optionValue = optionValues[i];
1263
+
1264
+ if (_.isArray(optionValue)) {
1265
+ optionText = optionValue[0];
1266
+ optionValue = optionValue[1];
1267
+
1268
+ this.$el.append(this.template({
1269
+ text: optionText,
1270
+ value: optionValue,
1271
+ selected: selectedValues.indexOf(optionValue) > -1
1272
+ }));
1273
+ }
1274
+ else if (_.isObject(optionValue)) {
1275
+ optgroupName = optionValue.name;
1276
+ optgroup = $("<optgroup></optgroup>", { label: optgroupName });
1277
+ optgroup.append(this._renderOptions(optionValue.values, selectedValues));
1278
+ this.$el.append(optgroup);
1279
+ }
1280
+ else {
1281
+ throw TypeError("optionValues elements must be a name-value pair or an object hash of { name: 'optgroup label', value: [option name-value pairs] }");
1282
+ }
1283
+ }
1284
+
1285
+ this.delegateEvents();
1286
+
1287
+ return this;
1288
+ },
1289
+
1290
+ /**
1291
+ Saves the value of the selected option to the model attribute. Triggers a
1292
+ `backgrid:edited` Backbone event from the model.
1293
+ */
1294
+ save: function (e) {
1295
+ var model = this.model;
1296
+ var column = this.column;
1297
+ model.set(column.get("name"), this.formatter.toRaw(this.$el.val()));
1298
+ model.trigger("backgrid:edited", model, column, new Command(e));
1299
+ },
1300
+
1301
+ /**
1302
+ Triggers a `backgrid:edited` event from the model so the body can close
1303
+ this editor.
1304
+ */
1305
+ close: function (e) {
1306
+ var model = this.model;
1307
+ var column = this.column;
1308
+ var command = new Command(e);
1309
+ if (command.cancel()) {
1310
+ e.stopPropagation();
1311
+ model.trigger("backgrid:edited", model, column, new Command(e));
1312
+ }
1313
+ else if (command.save() || command.moveLeft() || command.moveRight() ||
1314
+ command.moveUp() || command.moveDown() || e.type == "blur") {
1315
+ e.preventDefault();
1316
+ e.stopPropagation();
1317
+ if (e.type == "blur" && this.$el.find("option").length === 1) {
1318
+ model.set(column.get("name"), this.formatter.toRaw(this.$el.val()));
1319
+ }
1320
+ model.trigger("backgrid:edited", model, column, new Command(e));
1321
+ }
1322
+ }
1323
+
1324
+ });
1325
+
1326
+ /**
1327
+ SelectCell is also a different kind of cell in that upon going into edit mode
1328
+ the cell renders a list of options to pick from, as opposed to an input box.
1329
+
1330
+ SelectCell cannot be referenced by its string name when used in a column
1331
+ definition because it requires an `optionValues` class attribute to be
1332
+ defined. `optionValues` can either be a list of name-value pairs, to be
1333
+ rendered as options, or a list of object hashes which consist of a key *name*
1334
+ which is the option group name, and a key *values* which is a list of
1335
+ name-value pairs to be rendered as options under that option group.
1336
+
1337
+ In addition, `optionValues` can also be a parameter-less function that
1338
+ returns one of the above. If the options are static, it is recommended the
1339
+ returned values to be memoized. `_.memoize()` is a good function to help with
1340
+ that.
1341
+
1342
+ During display mode, the default formatter will normalize the raw model value
1343
+ to an array of values whether the raw model value is a scalar or an
1344
+ array. Each value is compared with the `optionValues` values using
1345
+ Ecmascript's implicit type conversion rules. When exiting edit mode, no type
1346
+ conversion is performed when saving into the model. This behavior is not
1347
+ always desirable when the value type is anything other than string. To
1348
+ control type conversion on the client-side, you should subclass SelectCell to
1349
+ provide a custom formatter or provide the formatter to your column
1350
+ definition.
1351
+
1352
+ See:
1353
+ [$.fn.val()](http://api.jquery.com/val/)
1354
+
1355
+ @class Backgrid.SelectCell
1356
+ @extends Backgrid.Cell
1357
+ */
1358
+ var SelectCell = Backgrid.SelectCell = Cell.extend({
1359
+
1360
+ /** @property */
1361
+ className: "select-cell",
1362
+
1363
+ /** @property */
1364
+ editor: SelectCellEditor,
1365
+
1366
+ /** @property */
1367
+ multiple: false,
1368
+
1369
+ /** @property */
1370
+ formatter: new SelectFormatter(),
1371
+
1372
+ /**
1373
+ @property {Array.<Array>|Array.<{name: string, values: Array.<Array>}>} optionValues
1374
+ */
1375
+ optionValues: undefined,
1376
+
1377
+ /** @property */
1378
+ delimiter: ', ',
1379
+
1380
+ /**
1381
+ Initializer.
1382
+
1383
+ @param {Object} options
1384
+ @param {Backbone.Model} options.model
1385
+ @param {Backgrid.Column} options.column
1386
+
1387
+ @throws {TypeError} If `optionsValues` is undefined.
1388
+ */
1389
+ initialize: function (options) {
1390
+ Cell.prototype.initialize.apply(this, arguments);
1391
+ Backgrid.requireOptions(this, ["optionValues"]);
1392
+ this.listenTo(this.model, "backgrid:edit", function (model, column, cell, editor) {
1393
+ if (column.get("name") == this.column.get("name")) {
1394
+ editor.setOptionValues(this.optionValues);
1395
+ editor.setMultiple(this.multiple);
1396
+ }
1397
+ });
1398
+ },
1399
+
1400
+ /**
1401
+ Renders the label using the raw value as key to look up from `optionValues`.
1402
+
1403
+ @throws {TypeError} If `optionValues` is malformed.
1404
+ */
1405
+ render: function () {
1406
+ this.$el.empty();
1407
+
1408
+ var optionValues = this.optionValues;
1409
+ var rawData = this.formatter.fromRaw(this.model.get(this.column.get("name")));
1410
+
1411
+ var selectedText = [];
1412
+
1413
+ try {
1414
+ if (!_.isArray(optionValues) || _.isEmpty(optionValues)) throw new TypeError;
1415
+
1416
+ for (var k = 0; k < rawData.length; k++) {
1417
+ var rawDatum = rawData[k];
1418
+
1419
+ for (var i = 0; i < optionValues.length; i++) {
1420
+ var optionValue = optionValues[i];
1421
+
1422
+ if (_.isArray(optionValue)) {
1423
+ var optionText = optionValue[0];
1424
+ var optionValue = optionValue[1];
1425
+
1426
+ if (optionValue == rawDatum) selectedText.push(optionText);
1427
+ }
1428
+ else if (_.isObject(optionValue)) {
1429
+ var optionGroupValues = optionValue.values;
1430
+
1431
+ for (var j = 0; j < optionGroupValues.length; j++) {
1432
+ var optionGroupValue = optionGroupValues[j];
1433
+ if (optionGroupValue[1] == rawDatum) {
1434
+ selectedText.push(optionGroupValue[0]);
1435
+ }
1436
+ }
1437
+ }
1438
+ else {
1439
+ throw new TypeError;
1440
+ }
1441
+ }
1442
+ }
1443
+
1444
+ this.$el.append(selectedText.join(this.delimiter));
1445
+ }
1446
+ catch (ex) {
1447
+ if (ex instanceof TypeError) {
1448
+ throw TypeError("'optionValues' must be of type {Array.<Array>|Array.<{name: string, values: Array.<Array>}>}");
1449
+ }
1450
+ throw ex;
1451
+ }
1452
+
1453
+ this.delegateEvents();
1454
+
1455
+ return this;
1456
+ }
1457
+
1458
+ });
1459
+ /*
1460
+ backgrid
1461
+ http://github.com/wyuenho/backgrid
1462
+
1463
+ Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
1464
+ Licensed under the MIT @license.
1465
+ */
1466
+
1467
+ /**
1468
+ A Column is a placeholder for column metadata.
1469
+
1470
+ You usually don't need to create an instance of this class yourself as a
1471
+ collection of column instances will be created for you from a list of column
1472
+ attributes in the Backgrid.js view class constructors.
1473
+
1474
+ @class Backgrid.Column
1475
+ @extends Backbone.Model
1476
+ */
1477
+ var Column = Backgrid.Column = Backbone.Model.extend({
1478
+
1479
+ defaults: {
1480
+ name: undefined,
1481
+ label: undefined,
1482
+ sortable: true,
1483
+ editable: true,
1484
+ renderable: true,
1485
+ formatter: undefined,
1486
+ cell: undefined,
1487
+ headerCell: undefined
1488
+ },
1489
+
1490
+ /**
1491
+ Initializes this Column instance.
1492
+
1493
+ @param {Object} attrs Column attributes.
1494
+ @param {string} attrs.name The name of the model attribute.
1495
+ @param {string|Backgrid.Cell} attrs.cell The cell type.
1496
+ If this is a string, the capitalized form will be used to look up a
1497
+ cell class in Backbone, i.e.: string => StringCell. If a Cell subclass
1498
+ is supplied, it is initialized with a hash of parameters. If a Cell
1499
+ instance is supplied, it is used directly.
1500
+ @param {string|Backgrid.HeaderCell} [attrs.headerCell] The header cell type.
1501
+ @param {string} [attrs.label] The label to show in the header.
1502
+ @param {boolean} [attrs.sortable=true]
1503
+ @param {boolean} [attrs.editable=true]
1504
+ @param {boolean} [attrs.renderable=true]
1505
+ @param {Backgrid.CellFormatter|Object|string} [attrs.formatter] The
1506
+ formatter to use to convert between raw model values and user input.
1507
+
1508
+ @throws {TypeError} If attrs.cell or attrs.options are not supplied.
1509
+ @throws {ReferenceError} If attrs.cell is a string but a cell class of
1510
+ said name cannot be found in the Backgrid module.
1511
+
1512
+ See:
1513
+
1514
+ - Backgrid.Cell
1515
+ - Backgrid.CellFormatter
1516
+ */
1517
+ initialize: function (attrs) {
1518
+ Backgrid.requireOptions(attrs, ["cell", "name"]);
1519
+
1520
+ if (!this.has("label")) {
1521
+ this.set({ label: this.get("name") }, { silent: true });
1522
+ }
1523
+
1524
+ var headerCell = Backgrid.resolveNameToClass(this.get("headerCell"), "HeaderCell");
1525
+ var cell = Backgrid.resolveNameToClass(this.get("cell"), "Cell");
1526
+ this.set({ cell: cell, headerCell: headerCell }, { silent: true });
1527
+ }
1528
+
1529
+ });
1530
+
1531
+ /**
1532
+ A Backbone collection of Column instances.
1533
+
1534
+ @class Backgrid.Columns
1535
+ @extends Backbone.Collection
1536
+ */
1537
+ var Columns = Backgrid.Columns = Backbone.Collection.extend({
1538
+
1539
+ /**
1540
+ @property {Backgrid.Column} model
1541
+ */
1542
+ model: Column
1543
+ });
1544
+ /*
1545
+ backgrid
1546
+ http://github.com/wyuenho/backgrid
1547
+
1548
+ Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
1549
+ Licensed under the MIT @license.
1550
+ */
1551
+
1552
+ /**
1553
+ Row is a simple container view that takes a model instance and a list of
1554
+ column metadata describing how each of the model's attribute is to be
1555
+ rendered, and apply the appropriate cell to each attribute.
1556
+
1557
+ @class Backgrid.Row
1558
+ @extends Backbone.View
1559
+ */
1560
+ var Row = Backgrid.Row = Backbone.View.extend({
1561
+
1562
+ /** @property */
1563
+ tagName: "tr",
1564
+
1565
+ requiredOptions: ["columns", "model"],
1566
+
1567
+ /**
1568
+ Initializes a row view instance.
1569
+
1570
+ @param {Object} options
1571
+ @param {Backbone.Collection.<Backgrid.Column>|Array.<Backgrid.Column>|Array.<Object>} options.columns Column metadata.
1572
+ @param {Backbone.Model} options.model The model instance to render.
1573
+
1574
+ @throws {TypeError} If options.columns or options.model is undefined.
1575
+ */
1576
+ initialize: function (options) {
1577
+
1578
+ Backgrid.requireOptions(options, this.requiredOptions);
1579
+
1580
+ var columns = this.columns = options.columns;
1581
+ if (!(columns instanceof Backbone.Collection)) {
1582
+ columns = this.columns = new Columns(columns);
1583
+ }
1584
+
1585
+ var cells = this.cells = [];
1586
+ for (var i = 0; i < columns.length; i++) {
1587
+ cells.push(this.makeCell(columns.at(i), options));
1588
+ }
1589
+
1590
+ this.listenTo(columns, "change:renderable", function (column, renderable) {
1591
+ for (var i = 0; i < cells.length; i++) {
1592
+ var cell = cells[i];
1593
+ if (cell.column.get("name") == column.get("name")) {
1594
+ if (renderable) cell.$el.show(); else cell.$el.hide();
1595
+ }
1596
+ }
1597
+ });
1598
+
1599
+ this.listenTo(columns, "add", function (column, columns) {
1600
+ var i = columns.indexOf(column);
1601
+ var cell = this.makeCell(column, options);
1602
+ cells.splice(i, 0, cell);
1603
+
1604
+ if (!cell.column.get("renderable")) cell.$el.hide();
1605
+
1606
+ var $el = this.$el;
1607
+ if (i === 0) {
1608
+ $el.prepend(cell.render().$el);
1609
+ }
1610
+ else if (i === columns.length - 1) {
1611
+ $el.append(cell.render().$el);
1612
+ }
1613
+ else {
1614
+ $el.children().eq(i).before(cell.render().$el);
1615
+ }
1616
+ });
1617
+
1618
+ this.listenTo(columns, "remove", function (column, columns, opts) {
1619
+ cells[opts.index].remove();
1620
+ cells.splice(opts.index, 1);
1621
+ });
1622
+ },
1623
+
1624
+ /**
1625
+ Factory method for making a cell. Used by #initialize internally. Override
1626
+ this to provide an appropriate cell instance for a custom Row subclass.
1627
+
1628
+ @protected
1629
+
1630
+ @param {Backgrid.Column} column
1631
+ @param {Object} options The options passed to #initialize.
1632
+
1633
+ @return {Backgrid.Cell}
1634
+ */
1635
+ makeCell: function (column) {
1636
+ return new (column.get("cell"))({
1637
+ column: column,
1638
+ model: this.model
1639
+ });
1640
+ },
1641
+
1642
+ /**
1643
+ Renders a row of cells for this row's model.
1644
+ */
1645
+ render: function () {
1646
+ this.$el.empty();
1647
+
1648
+ var fragment = document.createDocumentFragment();
1649
+
1650
+ for (var i = 0; i < this.cells.length; i++) {
1651
+ var cell = this.cells[i];
1652
+ fragment.appendChild(cell.render().el);
1653
+ if (!cell.column.get("renderable")) cell.$el.hide();
1654
+ }
1655
+
1656
+ this.el.appendChild(fragment);
1657
+
1658
+ this.delegateEvents();
1659
+
1660
+ return this;
1661
+ },
1662
+
1663
+ /**
1664
+ Clean up this row and its cells.
1665
+
1666
+ @chainable
1667
+ */
1668
+ remove: function () {
1669
+ for (var i = 0; i < this.cells.length; i++) {
1670
+ var cell = this.cells[i];
1671
+ cell.remove.apply(cell, arguments);
1672
+ }
1673
+ return Backbone.View.prototype.remove.apply(this, arguments);
1674
+ }
1675
+
1676
+ });
1677
+
1678
+ /**
1679
+ EmptyRow is a simple container view that takes a list of column and render a
1680
+ row with a single column.
1681
+
1682
+ @class Backgrid.EmptyRow
1683
+ @extends Backbone.View
1684
+ */
1685
+ var EmptyRow = Backgrid.EmptyRow = Backbone.View.extend({
1686
+
1687
+ /** @property */
1688
+ tagName: "tr",
1689
+
1690
+ /** @property */
1691
+ emptyText: null,
1692
+
1693
+ /**
1694
+ Initializer.
1695
+
1696
+ @param {Object} options
1697
+ @param {string} options.emptyText
1698
+ @param {Backbone.Collection.<Backgrid.Column>|Array.<Backgrid.Column>|Array.<Object>} options.columns Column metadata.
1699
+ */
1700
+ initialize: function (options) {
1701
+ Backgrid.requireOptions(options, ["emptyText", "columns"]);
1702
+
1703
+ this.emptyText = options.emptyText;
1704
+ this.columns = options.columns;
1705
+ },
1706
+
1707
+ /**
1708
+ Renders an empty row.
1709
+ */
1710
+ render: function () {
1711
+ this.$el.empty();
1712
+
1713
+ var td = document.createElement("td");
1714
+ td.setAttribute("colspan", this.columns.length);
1715
+ td.textContent = this.emptyText;
1716
+
1717
+ this.el.setAttribute("class", "empty");
1718
+ this.el.appendChild(td);
1719
+
1720
+ return this;
1721
+ }
1722
+ });
1723
+ /*
1724
+ backgrid
1725
+ http://github.com/wyuenho/backgrid
1726
+
1727
+ Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
1728
+ Licensed under the MIT @license.
1729
+ */
1730
+
1731
+ /**
1732
+ HeaderCell is a special cell class that renders a column header cell. If the
1733
+ column is sortable, a sorter is also rendered and will trigger a table
1734
+ refresh after sorting.
1735
+
1736
+ @class Backgrid.HeaderCell
1737
+ @extends Backbone.View
1738
+ */
1739
+ var HeaderCell = Backgrid.HeaderCell = Backbone.View.extend({
1740
+
1741
+ /** @property */
1742
+ tagName: "th",
1743
+
1744
+ /** @property */
1745
+ events: {
1746
+ "click a": "onClick"
1747
+ },
1748
+
1749
+ /**
1750
+ @property {null|"ascending"|"descending"} _direction The current sorting
1751
+ direction of this column.
1752
+ */
1753
+ _direction: null,
1754
+
1755
+ /**
1756
+ Initializer.
1757
+
1758
+ @param {Object} options
1759
+ @param {Backgrid.Column|Object} options.column
1760
+
1761
+ @throws {TypeError} If options.column or options.collection is undefined.
1762
+ */
1763
+ initialize: function (options) {
1764
+ Backgrid.requireOptions(options, ["column", "collection"]);
1765
+ this.column = options.column;
1766
+ if (!(this.column instanceof Column)) {
1767
+ this.column = new Column(this.column);
1768
+ }
1769
+ this.listenTo(this.collection, "backgrid:sort", this._resetCellDirection);
1770
+ },
1771
+
1772
+ /**
1773
+ Gets or sets the direction of this cell. If called directly without
1774
+ parameters, returns the current direction of this cell, otherwise sets
1775
+ it. If a `null` is given, sets this cell back to the default order.
1776
+
1777
+ @param {null|"ascending"|"descending"} dir
1778
+ @return {null|string} The current direction or the changed direction.
1779
+ */
1780
+ direction: function (dir) {
1781
+ if (arguments.length) {
1782
+ if (this._direction) this.$el.removeClass(this._direction);
1783
+ if (dir) this.$el.addClass(dir);
1784
+ this._direction = dir;
1785
+ }
1786
+
1787
+ return this._direction;
1788
+ },
1789
+
1790
+ /**
1791
+ Event handler for the Backbone `backgrid:sort` event. Resets this cell's
1792
+ direction to default if sorting is being done on another column.
1793
+
1794
+ @private
1795
+ */
1796
+ _resetCellDirection: function (sortByColName, direction, comparator, collection) {
1797
+ if (collection == this.collection) {
1798
+ if (sortByColName !== this.column.get("name")) this.direction(null);
1799
+ else this.direction(direction);
1800
+ }
1801
+ },
1802
+
1803
+ /**
1804
+ Event handler for the `click` event on the cell's anchor. If the column is
1805
+ sortable, clicking on the anchor will cycle through 3 sorting orderings -
1806
+ `ascending`, `descending`, and default.
1807
+ */
1808
+ onClick: function (e) {
1809
+ e.preventDefault();
1810
+
1811
+ var columnName = this.column.get("name");
1812
+
1813
+ if (this.column.get("sortable")) {
1814
+ if (this.direction() === "ascending") {
1815
+ this.sort(columnName, "descending", function (left, right) {
1816
+ var leftVal = left.get(columnName);
1817
+ var rightVal = right.get(columnName);
1818
+ if (leftVal === rightVal) {
1819
+ return 0;
1820
+ }
1821
+ else if (leftVal > rightVal) { return -1; }
1822
+ return 1;
1823
+ });
1824
+ }
1825
+ else if (this.direction() === "descending") {
1826
+ this.sort(columnName, null);
1827
+ }
1828
+ else {
1829
+ this.sort(columnName, "ascending", function (left, right) {
1830
+ var leftVal = left.get(columnName);
1831
+ var rightVal = right.get(columnName);
1832
+ if (leftVal === rightVal) {
1833
+ return 0;
1834
+ }
1835
+ else if (leftVal < rightVal) { return -1; }
1836
+ return 1;
1837
+ });
1838
+ }
1839
+ }
1840
+ },
1841
+
1842
+ /**
1843
+ If the underlying collection is a Backbone.PageableCollection in
1844
+ server-mode or infinite-mode, a page of models is fetched after sorting is
1845
+ done on the server.
1846
+
1847
+ If the underlying collection is a Backbone.PageableCollection in
1848
+ client-mode, or any
1849
+ [Backbone.Collection](http://backbonejs.org/#Collection) instance, sorting
1850
+ is done on the client side. If the collection is an instance of a
1851
+ Backbone.PageableCollection, sorting will be done globally on all the pages
1852
+ and the current page will then be returned.
1853
+
1854
+ Triggers a Backbone `backgrid:sort` event from the collection when done
1855
+ with the column name, direction, comparator and a reference to the
1856
+ collection.
1857
+
1858
+ @param {string} columnName
1859
+ @param {null|"ascending"|"descending"} direction
1860
+ @param {function(*, *): number} [comparator]
1861
+
1862
+ See [Backbone.Collection#comparator](http://backbonejs.org/#Collection-comparator)
1863
+ */
1864
+ sort: function (columnName, direction, comparator) {
1865
+
1866
+ comparator = comparator || this._cidComparator;
1867
+
1868
+ var collection = this.collection;
1869
+
1870
+ if (Backbone.PageableCollection && collection instanceof Backbone.PageableCollection) {
1871
+ var order;
1872
+ if (direction === "ascending") order = -1;
1873
+ else if (direction === "descending") order = 1;
1874
+ else order = null;
1875
+
1876
+ collection.setSorting(order ? columnName : null, order);
1877
+
1878
+ if (collection.mode == "client") {
1879
+ if (!collection.fullCollection.comparator) {
1880
+ collection.fullCollection.comparator = comparator;
1881
+ }
1882
+ collection.fullCollection.sort();
1883
+ }
1884
+ else collection.fetch({reset: true});
1885
+ }
1886
+ else {
1887
+ collection.comparator = comparator;
1888
+ collection.sort();
1889
+ }
1890
+
1891
+ this.collection.trigger("backgrid:sort", columnName, direction, comparator, this.collection);
1892
+ },
1893
+
1894
+ /**
1895
+ Default comparator for Backbone.Collections. Sorts cids in ascending
1896
+ order. The cids of the models are assumed to be in insertion order.
1897
+
1898
+ @private
1899
+ @param {*} left
1900
+ @param {*} right
1901
+ */
1902
+ _cidComparator: function (left, right) {
1903
+ var lcid = left.cid, rcid = right.cid;
1904
+ if (!_.isUndefined(lcid) && !_.isUndefined(rcid)) {
1905
+ lcid = lcid.slice(1) * 1, rcid = rcid.slice(1) * 1;
1906
+ if (lcid < rcid) return -1;
1907
+ else if (lcid > rcid) return 1;
1908
+ }
1909
+
1910
+ return 0;
1911
+ },
1912
+
1913
+ /**
1914
+ Renders a header cell with a sorter and a label.
1915
+ */
1916
+ render: function () {
1917
+ this.$el.empty();
1918
+ var $label = $("<a>").text(this.column.get("label")).append("<b class='sort-caret'></b>");
1919
+ this.$el.append($label);
1920
+ this.delegateEvents();
1921
+ return this;
1922
+ }
1923
+
1924
+ });
1925
+
1926
+ /**
1927
+ HeaderRow is a controller for a row of header cells.
1928
+
1929
+ @class Backgrid.HeaderRow
1930
+ @extends Backgrid.Row
1931
+ */
1932
+ var HeaderRow = Backgrid.HeaderRow = Backgrid.Row.extend({
1933
+
1934
+ requiredOptions: ["columns", "collection"],
1935
+
1936
+ /**
1937
+ Initializer.
1938
+
1939
+ @param {Object} options
1940
+ @param {Backbone.Collection.<Backgrid.Column>|Array.<Backgrid.Column>|Array.<Object>} options.columns
1941
+ @param {Backgrid.HeaderCell} [options.headerCell] Customized default
1942
+ HeaderCell for all the columns. Supply a HeaderCell class or instance to a
1943
+ the `headerCell` key in a column definition for column-specific header
1944
+ rendering.
1945
+
1946
+ @throws {TypeError} If options.columns or options.collection is undefined.
1947
+ */
1948
+ initialize: function () {
1949
+ Backgrid.Row.prototype.initialize.apply(this, arguments);
1950
+ },
1951
+
1952
+ makeCell: function (column, options) {
1953
+ var headerCell = column.get("headerCell") || options.headerCell || HeaderCell;
1954
+ headerCell = new headerCell({
1955
+ column: column,
1956
+ collection: this.collection
1957
+ });
1958
+ return headerCell;
1959
+ }
1960
+
1961
+ });
1962
+
1963
+ /**
1964
+ Header is a special structural view class that renders a table head with a
1965
+ single row of header cells.
1966
+
1967
+ @class Backgrid.Header
1968
+ @extends Backbone.View
1969
+ */
1970
+ var Header = Backgrid.Header = Backbone.View.extend({
1971
+
1972
+ /** @property */
1973
+ tagName: "thead",
1974
+
1975
+ /**
1976
+ Initializer. Initializes this table head view to contain a single header
1977
+ row view.
1978
+
1979
+ @param {Object} options
1980
+ @param {Backbone.Collection.<Backgrid.Column>|Array.<Backgrid.Column>|Array.<Object>} options.columns Column metadata.
1981
+ @param {Backbone.Model} options.model The model instance to render.
1982
+
1983
+ @throws {TypeError} If options.columns or options.model is undefined.
1984
+ */
1985
+ initialize: function (options) {
1986
+ Backgrid.requireOptions(options, ["columns", "collection"]);
1987
+
1988
+ this.columns = options.columns;
1989
+ if (!(this.columns instanceof Backbone.Collection)) {
1990
+ this.columns = new Columns(this.columns);
1991
+ }
1992
+
1993
+ this.row = new Backgrid.HeaderRow({
1994
+ columns: this.columns,
1995
+ collection: this.collection
1996
+ });
1997
+ },
1998
+
1999
+ /**
2000
+ Renders this table head with a single row of header cells.
2001
+ */
2002
+ render: function () {
2003
+ this.$el.append(this.row.render().$el);
2004
+ this.delegateEvents();
2005
+ return this;
2006
+ },
2007
+
2008
+ /**
2009
+ Clean up this header and its row.
2010
+
2011
+ @chainable
2012
+ */
2013
+ remove: function () {
2014
+ this.row.remove.apply(this.row, arguments);
2015
+ return Backbone.View.prototype.remove.apply(this, arguments);
2016
+ }
2017
+
2018
+ });
2019
+ /*
2020
+ backgrid
2021
+ http://github.com/wyuenho/backgrid
2022
+
2023
+ Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
2024
+ Licensed under the MIT @license.
2025
+ */
2026
+
2027
+ /**
2028
+ Body is the table body which contains the rows inside a table. Body is
2029
+ responsible for refreshing the rows after sorting, insertion and removal.
2030
+
2031
+ @class Backgrid.Body
2032
+ @extends Backbone.View
2033
+ */
2034
+ var Body = Backgrid.Body = Backbone.View.extend({
2035
+
2036
+ /** @property */
2037
+ tagName: "tbody",
2038
+
2039
+ /**
2040
+ Initializer.
2041
+
2042
+ @param {Object} options
2043
+ @param {Backbone.Collection} options.collection
2044
+ @param {Backbone.Collection.<Backgrid.Column>|Array.<Backgrid.Column>|Array.<Object>} options.columns
2045
+ Column metadata.
2046
+ @param {Backgrid.Row} [options.row=Backgrid.Row] The Row class to use.
2047
+ @param {string} [options.emptyText] The text to display in the empty row.
2048
+
2049
+ @throws {TypeError} If options.columns or options.collection is undefined.
2050
+
2051
+ See Backgrid.Row.
2052
+ */
2053
+ initialize: function (options) {
2054
+ Backgrid.requireOptions(options, ["columns", "collection"]);
2055
+
2056
+ this.columns = options.columns;
2057
+ if (!(this.columns instanceof Backbone.Collection)) {
2058
+ this.columns = new Columns(this.columns);
2059
+ }
2060
+
2061
+ this.row = options.row || Row;
2062
+ this.rows = this.collection.map(function (model) {
2063
+ var row = new this.row({
2064
+ columns: this.columns,
2065
+ model: model
2066
+ });
2067
+
2068
+ return row;
2069
+ }, this);
2070
+
2071
+ this.emptyText = options.emptyText;
2072
+ this._unshiftEmptyRowMayBe();
2073
+
2074
+ var collection = this.collection;
2075
+ this.listenTo(collection, "add", this.insertRow);
2076
+ this.listenTo(collection, "remove", this.removeRow);
2077
+ this.listenTo(collection, "sort", this.refresh);
2078
+ this.listenTo(collection, "reset", this.refresh);
2079
+ this.listenTo(collection, "backgrid:edited", this.moveToNextCell);
2080
+ },
2081
+
2082
+ _unshiftEmptyRowMayBe: function () {
2083
+ if (this.rows.length === 0 && this.emptyText != null) {
2084
+ this.rows.unshift(new EmptyRow({
2085
+ emptyText: this.emptyText,
2086
+ columns: this.columns
2087
+ }));
2088
+ }
2089
+ },
2090
+
2091
+ /**
2092
+ This method can be called either directly or as a callback to a
2093
+ [Backbone.Collecton#add](http://backbonejs.org/#Collection-add) event.
2094
+
2095
+ When called directly, it accepts a model or an array of models and an
2096
+ option hash just like
2097
+ [Backbone.Collection#add](http://backbonejs.org/#Collection-add) and
2098
+ delegates to it. Once the model is added, a new row is inserted into the
2099
+ body and automatically rendered.
2100
+
2101
+ When called as a callback of an `add` event, splices a new row into the
2102
+ body and renders it.
2103
+
2104
+ @param {Backbone.Model} model The model to render as a row.
2105
+ @param {Backbone.Collection} collection When called directly, this
2106
+ parameter is actually the options to
2107
+ [Backbone.Collection#add](http://backbonejs.org/#Collection-add).
2108
+ @param {Object} options When called directly, this must be null.
2109
+
2110
+ See:
2111
+
2112
+ - [Backbone.Collection#add](http://backbonejs.org/#Collection-add)
2113
+ */
2114
+ insertRow: function (model, collection, options) {
2115
+
2116
+ if (this.rows[0] instanceof EmptyRow) this.rows.pop().remove();
2117
+
2118
+ // insertRow() is called directly
2119
+ if (!(collection instanceof Backbone.Collection) && !options) {
2120
+ this.collection.add(model, (options = collection));
2121
+ return;
2122
+ }
2123
+
2124
+ options = _.extend({render: true}, options || {});
2125
+
2126
+ var row = new this.row({
2127
+ columns: this.columns,
2128
+ model: model
2129
+ });
2130
+
2131
+ var index = collection.indexOf(model);
2132
+ this.rows.splice(index, 0, row);
2133
+
2134
+ var $el = this.$el;
2135
+ var $children = $el.children();
2136
+ var $rowEl = row.render().$el;
2137
+
2138
+ if (options.render) {
2139
+ if (index >= $children.length) {
2140
+ $el.append($rowEl);
2141
+ }
2142
+ else {
2143
+ $children.eq(index).before($rowEl);
2144
+ }
2145
+ }
2146
+ },
2147
+
2148
+ /**
2149
+ The method can be called either directly or as a callback to a
2150
+ [Backbone.Collection#remove](http://backbonejs.org/#Collection-remove)
2151
+ event.
2152
+
2153
+ When called directly, it accepts a model or an array of models and an
2154
+ option hash just like
2155
+ [Backbone.Collection#remove](http://backbonejs.org/#Collection-remove) and
2156
+ delegates to it. Once the model is removed, a corresponding row is removed
2157
+ from the body.
2158
+
2159
+ When called as a callback of a `remove` event, splices into the rows and
2160
+ removes the row responsible for rendering the model.
2161
+
2162
+ @param {Backbone.Model} model The model to remove from the body.
2163
+ @param {Backbone.Collection} collection When called directly, this
2164
+ parameter is actually the options to
2165
+ [Backbone.Collection#remove](http://backbonejs.org/#Collection-remove).
2166
+ @param {Object} options When called directly, this must be null.
2167
+
2168
+ See:
2169
+
2170
+ - [Backbone.Collection#remove](http://backbonejs.org/#Collection-remove)
2171
+ */
2172
+ removeRow: function (model, collection, options) {
2173
+
2174
+ // removeRow() is called directly
2175
+ if (!options) {
2176
+ this.collection.remove(model, (options = collection));
2177
+ this._unshiftEmptyRowMayBe();
2178
+ return;
2179
+ }
2180
+
2181
+ if (_.isUndefined(options.render) || options.render) {
2182
+ this.rows[options.index].remove();
2183
+ }
2184
+
2185
+ this.rows.splice(options.index, 1);
2186
+ this._unshiftEmptyRowMayBe();
2187
+ },
2188
+
2189
+ /**
2190
+ Reinitialize all the rows inside the body and re-render them. Triggers a
2191
+ Backbone `backgrid:refresh` event from the collection along with the body
2192
+ instance as its sole parameter when done.
2193
+ */
2194
+ refresh: function () {
2195
+ for (var i = 0; i < this.rows.length; i++) {
2196
+ this.rows[i].remove();
2197
+ }
2198
+
2199
+ this.rows = this.collection.map(function (model) {
2200
+ var row = new this.row({
2201
+ columns: this.columns,
2202
+ model: model
2203
+ });
2204
+
2205
+ return row;
2206
+ }, this);
2207
+ this._unshiftEmptyRowMayBe();
2208
+
2209
+ this.render();
2210
+
2211
+ this.collection.trigger("backgrid:refresh", this);
2212
+
2213
+ return this;
2214
+ },
2215
+
2216
+ /**
2217
+ Renders all the rows inside this body. If the collection is empty and
2218
+ `options.emptyText` is defined and not null in the constructor, an empty
2219
+ row is rendered, otherwise no row is rendered.
2220
+ */
2221
+ render: function () {
2222
+ this.$el.empty();
2223
+
2224
+ var fragment = document.createDocumentFragment();
2225
+ for (var i = 0; i < this.rows.length; i++) {
2226
+ var row = this.rows[i];
2227
+ fragment.appendChild(row.render().el);
2228
+ }
2229
+
2230
+ this.el.appendChild(fragment);
2231
+
2232
+ this.delegateEvents();
2233
+
2234
+ return this;
2235
+ },
2236
+
2237
+ /**
2238
+ Clean up this body and it's rows.
2239
+
2240
+ @chainable
2241
+ */
2242
+ remove: function () {
2243
+ for (var i = 0; i < this.rows.length; i++) {
2244
+ var row = this.rows[i];
2245
+ row.remove.apply(row, arguments);
2246
+ }
2247
+ return Backbone.View.prototype.remove.apply(this, arguments);
2248
+ },
2249
+
2250
+ /**
2251
+ Moves focus to the next renderable and editable cell and return the
2252
+ currently editing cell to display mode.
2253
+
2254
+ @param {Backbone.Model} model The originating model
2255
+ @param {Backgrid.Column} column The originating model column
2256
+ @param {Backgrid.Command} command The Command object constructed from a DOM
2257
+ Event
2258
+ */
2259
+ moveToNextCell: function (model, column, command) {
2260
+ var i = this.collection.indexOf(model);
2261
+ var j = this.columns.indexOf(column);
2262
+
2263
+ if (command.moveUp() || command.moveDown() || command.moveLeft() ||
2264
+ command.moveRight() || command.save()) {
2265
+ var l = this.columns.length;
2266
+ var maxOffset = l * this.collection.length;
2267
+
2268
+ if (command.moveUp() || command.moveDown()) {
2269
+ var row = this.rows[i + (command.moveUp() ? -1 : 1)];
2270
+ if (row) row.cells[j].enterEditMode();
2271
+ }
2272
+ else if (command.moveLeft() || command.moveRight()) {
2273
+ var right = command.moveRight();
2274
+ for (var offset = i * l + j + (right ? 1 : -1);
2275
+ offset >= 0 && offset < maxOffset;
2276
+ right ? offset++ : offset--) {
2277
+ var m = ~~(offset / l);
2278
+ var n = offset - m * l;
2279
+ var cell = this.rows[m].cells[n];
2280
+ if (cell.column.get("renderable") && cell.column.get("editable")) {
2281
+ cell.enterEditMode();
2282
+ break;
2283
+ }
2284
+ }
2285
+ }
2286
+ }
2287
+
2288
+ this.rows[i].cells[j].exitEditMode();
2289
+ }
2290
+ });
2291
+ /*
2292
+ backgrid
2293
+ http://github.com/wyuenho/backgrid
2294
+
2295
+ Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
2296
+ Licensed under the MIT @license.
2297
+ */
2298
+
2299
+ /**
2300
+ A Footer is a generic class that only defines a default tag `tfoot` and
2301
+ number of required parameters in the initializer.
2302
+
2303
+ @abstract
2304
+ @class Backgrid.Footer
2305
+ @extends Backbone.View
2306
+ */
2307
+ var Footer = Backgrid.Footer = Backbone.View.extend({
2308
+
2309
+ /** @property */
2310
+ tagName: "tfoot",
2311
+
2312
+ /**
2313
+ Initializer.
2314
+
2315
+ @param {Object} options
2316
+ @param {*} options.parent The parent view class of this footer.
2317
+ @param {Backbone.Collection.<Backgrid.Column>|Array.<Backgrid.Column>|Array.<Object>} options.columns
2318
+ Column metadata.
2319
+ @param {Backbone.Collection} options.collection
2320
+
2321
+ @throws {TypeError} If options.columns or options.collection is undefined.
2322
+ */
2323
+ initialize: function (options) {
2324
+ Backgrid.requireOptions(options, ["columns", "collection"]);
2325
+ this.columns = options.columns;
2326
+ if (!(this.columns instanceof Backbone.Collection)) {
2327
+ this.columns = new Backgrid.Columns(this.columns);
2328
+ }
2329
+ }
2330
+
2331
+ });
2332
+ /*
2333
+ backgrid
2334
+ http://github.com/wyuenho/backgrid
2335
+
2336
+ Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
2337
+ Licensed under the MIT @license.
2338
+ */
2339
+
2340
+ /**
2341
+ Grid represents a data grid that has a header, body and an optional footer.
2342
+
2343
+ By default, a Grid treats each model in a collection as a row, and each
2344
+ attribute in a model as a column. To render a grid you must provide a list of
2345
+ column metadata and a collection to the Grid constructor. Just like any
2346
+ Backbone.View class, the grid is rendered as a DOM node fragment when you
2347
+ call render().
2348
+
2349
+ var grid = Backgrid.Grid({
2350
+ columns: [{ name: "id", label: "ID", type: "string" },
2351
+ // ...
2352
+ ],
2353
+ collections: books
2354
+ });
2355
+
2356
+ $("#table-container").append(grid.render().el);
2357
+
2358
+ Optionally, if you want to customize the rendering of the grid's header and
2359
+ footer, you may choose to extend Backgrid.Header and Backgrid.Footer, and
2360
+ then supply that class or an instance of that class to the Grid constructor.
2361
+ See the documentation for Header and Footer for further details.
2362
+
2363
+ var grid = Backgrid.Grid({
2364
+ columns: [{ name: "id", label: "ID", type: "string" }],
2365
+ collections: books,
2366
+ header: Backgrid.Header.extend({
2367
+ //...
2368
+ }),
2369
+ footer: Backgrid.Paginator
2370
+ });
2371
+
2372
+ Finally, if you want to override how the rows are rendered in the table body,
2373
+ you can supply a Body subclass as the `body` attribute that uses a different
2374
+ Row class.
2375
+
2376
+ @class Backgrid.Grid
2377
+ @extends Backbone.View
2378
+
2379
+ See:
2380
+
2381
+ - Backgrid.Column
2382
+ - Backgrid.Header
2383
+ - Backgrid.Body
2384
+ - Backgrid.Row
2385
+ - Backgrid.Footer
2386
+ */
2387
+ var Grid = Backgrid.Grid = Backbone.View.extend({
2388
+
2389
+ /** @property */
2390
+ tagName: "table",
2391
+
2392
+ /** @property */
2393
+ className: "backgrid",
2394
+
2395
+ /** @property */
2396
+ header: Header,
2397
+
2398
+ /** @property */
2399
+ body: Body,
2400
+
2401
+ /** @property */
2402
+ footer: null,
2403
+
2404
+ /**
2405
+ Initializes a Grid instance.
2406
+
2407
+ @param {Object} options
2408
+ @param {Backbone.Collection.<Backgrid.Column>|Array.<Backgrid.Column>|Array.<Object>} options.columns Column metadata.
2409
+ @param {Backbone.Collection} options.collection The collection of tabular model data to display.
2410
+ @param {Backgrid.Header} [options.header=Backgrid.Header] An optional Header class to override the default.
2411
+ @param {Backgrid.Body} [options.body=Backgrid.Body] An optional Body class to override the default.
2412
+ @param {Backgrid.Row} [options.row=Backgrid.Row] An optional Row class to override the default.
2413
+ @param {Backgrid.Footer} [options.footer=Backgrid.Footer] An optional Footer class.
2414
+ */
2415
+ initialize: function (options) {
2416
+ Backgrid.requireOptions(options, ["columns", "collection"]);
2417
+
2418
+ // Convert the list of column objects here first so the subviews don't have
2419
+ // to.
2420
+ if (!(options.columns instanceof Backbone.Collection)) {
2421
+ options.columns = new Columns(options.columns);
2422
+ }
2423
+ this.columns = options.columns;
2424
+
2425
+ var passedThruOptions = _.omit(options, ["el", "id", "attributes",
2426
+ "className", "tagName", "events"]);
2427
+
2428
+ this.header = options.header || this.header;
2429
+ this.header = new this.header(passedThruOptions);
2430
+
2431
+ this.body = options.body || this.body;
2432
+ this.body = new this.body(passedThruOptions);
2433
+
2434
+ this.footer = options.footer || this.footer;
2435
+ if (this.footer) {
2436
+ this.footer = new this.footer(passedThruOptions);
2437
+ }
2438
+
2439
+ this.listenTo(this.columns, "reset", function () {
2440
+ this.header = new (this.header.remove().constructor)(passedThruOptions);
2441
+ this.body = new (this.body.remove().constructor)(passedThruOptions);
2442
+ if (this.footer) {
2443
+ this.footer = new (this.footer.remove().constructor)(passedThruOptions);
2444
+ }
2445
+ this.render();
2446
+ });
2447
+ },
2448
+
2449
+ /**
2450
+ Delegates to Backgrid.Body#insertRow.
2451
+ */
2452
+ insertRow: function (model, collection, options) {
2453
+ return this.body.insertRow(model, collection, options);
2454
+ },
2455
+
2456
+ /**
2457
+ Delegates to Backgrid.Body#removeRow.
2458
+ */
2459
+ removeRow: function (model, collection, options) {
2460
+ return this.body.removeRow(model, collection, options);
2461
+ },
2462
+
2463
+ /**
2464
+ Delegates to Backgrid.Columns#add for adding a column. Subviews can listen
2465
+ to the `add` event from their internal `columns` if rerendering needs to
2466
+ happen.
2467
+
2468
+ @param {Object} [options] Options for `Backgrid.Columns#add`.
2469
+ @param {boolean} [options.render=true] Whether to render the column
2470
+ immediately after insertion.
2471
+
2472
+ @chainable
2473
+ */
2474
+ insertColumn: function (column, options) {
2475
+ options = options || {render: true};
2476
+ this.columns.add(column, options);
2477
+ return this;
2478
+ },
2479
+
2480
+ /**
2481
+ Delegates to Backgrid.Columns#remove for removing a column. Subviews can
2482
+ listen to the `remove` event from the internal `columns` if rerendering
2483
+ needs to happen.
2484
+
2485
+ @param {Object} [options] Options for `Backgrid.Columns#remove`.
2486
+
2487
+ @chainable
2488
+ */
2489
+ removeColumn: function (column, options) {
2490
+ this.columns.remove(column, options);
2491
+ return this;
2492
+ },
2493
+
2494
+ /**
2495
+ Renders the grid's header, then footer, then finally the body. Triggers a
2496
+ Backbone `backgrid:rendered` event along with a reference to the grid when
2497
+ the it has successfully been rendered.
2498
+ */
2499
+ render: function () {
2500
+ this.$el.empty();
2501
+
2502
+ this.$el.append(this.header.render().$el);
2503
+
2504
+ if (this.footer) {
2505
+ this.$el.append(this.footer.render().$el);
2506
+ }
2507
+
2508
+ this.$el.append(this.body.render().$el);
2509
+
2510
+ this.delegateEvents();
2511
+
2512
+ this.trigger("backgrid:rendered", this);
2513
+
2514
+ return this;
2515
+ },
2516
+
2517
+ /**
2518
+ Clean up this grid and its subviews.
2519
+
2520
+ @chainable
2521
+ */
2522
+ remove: function () {
2523
+ this.header.remove.apply(this.header, arguments);
2524
+ this.body.remove.apply(this.body, arguments);
2525
+ this.footer && this.footer.remove.apply(this.footer, arguments);
2526
+ return Backbone.View.prototype.remove.apply(this, arguments);
2527
+ }
2528
+
2529
+ });
2530
+
2531
+ }(this, jQuery, _, Backbone));