tableling-rails 0.0.23 → 0.0.24

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.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Tableling v0.0.23
2
+ * Tableling v0.0.24
3
3
  * Copyright (c) 2012-2014 Simon Oulevay (Alpha Hydrae) <hydrae.alpha@gmail.com>
4
4
  * Distributed under MIT license
5
5
  * https://github.com/AlphaHydrae/tableling
@@ -7,729 +7,752 @@
7
7
  Backbone.Tableling = Tableling = (function(Backbone, _, $){
8
8
 
9
9
  var Tableling = {
10
- version : "0.0.23"
10
+ version : "0.0.24"
11
11
  };
12
12
 
13
- // Tableling
14
- // ---------
15
- //
16
- // A tableling table is a Marionette layout which fetches data
17
- // from a Backbone collection. It is controlled with an EventAggregator.
18
- Tableling.Table = Backbone.Marionette.Layout.extend({
19
-
20
- className: 'tableling',
21
-
22
- // Default table options can be overriden by subclasses.
23
- config : {
24
- page : 1
25
- },
26
-
27
- initialize : function(options) {
28
- options = options || {};
29
-
30
- this.collection = options.collection;
31
-
32
- // Table options can also be overriden for each instance at construction.
33
- this.config = _.extend(_.clone(this.config || {}), _.result(options, 'config') || {});
34
-
35
- // We use an event aggregator to manage the layout and its components.
36
- // You can use your own by passing a `vent` option.
37
- this.vent = options.vent || new Backbone.Wreqr.EventAggregator();
38
-
39
- this.fetchOptions = _.extend(_.clone(this.fetchOptions || {}), _.result(options, 'fetchOptions') || {});
40
- this.autoUpdate = typeof(options.autoUpdate) != 'undefined' ? options.autoUpdate : true;
41
-
42
- // Components should trigger the `table:update` event to update
43
- // the table (e.g. change page size, sort) and fetch the new data.
44
- this.vent.on('table:update', this.onUpdate, this);
45
-
46
- this.on('item:rendered', this.setup, this);
47
- },
48
-
49
- // Called once rendering is complete. By default, it updates the table.
50
- setup : function() {
51
- this.ventTrigger('table:setup', this.config);
52
- if (this.autoUpdate) {
53
- this.ventTrigger('table:update');
54
- }
55
- },
56
-
57
- // Subclasses must return the Backbone.Collection used to fetch data.
58
- getCollection : function() {
59
- return this.collection;
60
- },
61
-
62
- // ### Refreshing the table
63
- update : function(config, options) {
64
- this.ventTrigger('table:update', config, options);
65
- },
66
-
67
- onUpdate : function(config, options) {
68
-
69
- _.each(config || {}, _.bind(this.updateValue, this));
70
-
71
- // Set the `refresh` option to false to update the table configuration
72
- // without refreshing.
73
- if (!options || typeof(options.refresh) == 'undefined' || options.refresh) {
74
- this.refresh();
75
- }
76
- },
77
-
78
- updateValue : function(value, key) {
79
- if (value && value.toString().length) {
80
- this.config[key] = value;
81
- } else {
82
- // Blank values are deleted to avoid sending them in ajax requests.
83
- delete this.config[key];
84
- }
85
- },
86
-
87
- refresh : function() {
88
-
89
- // You can provide `fetchOptions` to add properties to the
90
- // fetch request.
91
- //
92
- // var MyTable = Tableling.Table.extend({
93
- // fetchOptions : {
94
- // type : 'POST' // fetch data with POST
95
- // }
96
- // });
97
- //
98
- // // You can also override for each instance.
99
- // new MyTable({
100
- // fetchOptions : {
101
- // type : 'GET'
102
- // }
103
- // });
104
- var options = _.clone(this.fetchOptions);
105
- options.data = this.requestData();
106
- options.success = _.bind(this.processResponse, this);
107
- options.reset = true;
108
-
109
- // `table:refreshing` is triggered every time new data is being fetched.
110
- // The first argument is the request data.
111
- this.ventTrigger('table:refreshing', options.data);
112
-
113
- this.getCollection().fetch(options);
114
- },
115
-
116
- // ### Request
117
- requestData : function() {
118
- return this.config;
119
- },
120
-
121
- // ### Response
122
- processResponse : function(collection, response) {
123
-
124
- this.config.length = collection.length;
125
-
126
- // Tableling expects the response from a fetch to have a `total` property
127
- // which is the total number of items (not just in the current page).
128
- this.config.total = response.total;
129
-
130
- // The server may override the `page` property, for example if the
131
- // requested page was outside the range of available pages.
132
- if (response.page) {
133
- this.config.page = response.page;
134
- }
135
-
136
- // `tableling:refreshed` is triggered after every refresh. The first argument
137
- // is the current table configuration with the following additional meta data:
138
- //
139
- // * `total` - the total number of items
140
- // * `length` - the number of items in the current page
141
- this.ventTrigger('table:refreshed', this.config);
142
- },
143
-
144
- // Triggers an event in the event aggregator. If `Tableling.debug` is set, it also
145
- // logs the event and its arguments.
146
- ventTrigger : function() {
147
-
148
- var args = Array.prototype.slice.call(arguments);
149
- if (Tableling.debug) {
150
- console.log(_.first(args) + ' - ' + JSON.stringify(args.slice(1)));
151
- }
152
-
153
- this.vent.trigger.apply(this.vent, args);
13
+ // Tableling
14
+ // ---------
15
+ //
16
+ // A tableling table is a Marionette layout which fetches data
17
+ // from a Backbone collection. It is controlled with an EventAggregator.
18
+ Tableling.Table = Backbone.Marionette.Layout.extend({
19
+
20
+ className: 'tableling',
21
+
22
+ // Default table options can be overriden by subclasses.
23
+ config: {
24
+ page: 1
25
+ },
26
+
27
+ initialize: function(options) {
28
+ options = options || {};
29
+
30
+ this.collection = options.collection;
31
+
32
+ // Table options can also be overriden for each instance at construction.
33
+ this.config = _.extend(_.clone(this.config || {}), _.result(options, 'config') || {});
34
+
35
+ // We use an event aggregator to manage the layout and its components.
36
+ // You can use your own by passing a `vent` option.
37
+ this.vent = options.vent || new Backbone.Wreqr.EventAggregator();
38
+
39
+ this.fetchOptions = _.extend(_.clone(this.fetchOptions || {}), _.result(options, 'fetchOptions') || {});
40
+
41
+ if (typeof(options.autoUpdate) != 'undefined') {
42
+ this.autoUpdate = options.autoUpdate;
154
43
  }
155
- });
156
-
157
- // Tableling.Collection
158
- // --------------------
159
- //
160
- // Tableling expects fetch responses to have a `total` property in addition
161
- // to the model data. You can extend this Backbone.Collection subclass which
162
- // expects the following response format:
163
- //
164
- // {
165
- // "total": 12,
166
- // "data": [
167
- // { /* ... model data ... */ },
168
- // { /* ... model data ... */ }
169
- // ]
170
- // }
171
- Tableling.Collection = Backbone.Collection.extend({
172
-
173
- parse : function(response) {
174
- return response.data;
44
+
45
+ if (typeof(this.autoUpdate) == 'undefined') {
46
+ this.autoUpdate = true;
175
47
  }
176
- });
177
-
178
- // Implementations
179
- // ---------------
180
- //
181
- // <a href="tableling.bootstrap.html">tableling.bootstrap</a> provides views styled
182
- // with [Twitter Bootstrap](http://twitter.github.com/bootstrap/) classes.
183
-
184
- // Tableling.Modular
185
- // -----------------
186
- //
187
- // Tableling subclass which splits functionality into *modules*
188
- // and handles rendering.
189
- Tableling.Modular = Tableling.Table.extend({
190
-
191
- // The list of module names must be specified by subclasses.
192
- modules : [],
193
-
194
- // Modules are set up after rendering, before refreshing.
195
- setup : function() {
196
-
197
- this.moduleViews = {};
198
- _.each(this.modules, _.bind(this.setupModule, this));
199
-
200
- Tableling.Table.prototype.setup.call(this);
201
- },
202
-
203
- // ### Modules
204
- // Each module is identified by a name, for example `pageSize`.
205
- setupModule : function(name) {
206
-
207
- // The layout must have a region named after the module, e.g. `pageSizeRegion`.
208
- var region = name + 'Region';
209
-
210
- // It must have a view class, e.g. `pageSizeView`, which will be shown into
211
- // the region.
212
- var viewClass = this[name + 'View'];
213
-
214
- // When instantiated, the view class will be passed the event
215
- // aggregator as the `vent` option. Additional options can be
216
- // given named after the view class, e.g. `pageSizeViewOptions`.
217
- var options = _.extend(this.getModuleOptions(name), { vent: this.vent });
218
-
219
- var view = new viewClass(options);
220
-
221
- // Module view instances are stored by name in the `moduleViews` property
222
- // for future reference.
223
- this.moduleViews[name] = view;
224
-
225
- this[region].show(view);
226
- return view;
227
- },
228
-
229
- // By default the collection is the one given at construction.
230
- // Otherwise, a modular table expects a `table` module which
231
- // should have a collection (e.g. a Marionette CompositeView or
232
- // CollectionView). If your subclass does not have either, it
233
- // should override this method to return the Backbone.Collection
234
- // used to fetch table data.
235
- getCollection : function() {
236
- return this.collection || (this.moduleViews && this.moduleViews.table ? this.moduleViews.table.collection : undefined);
237
- },
238
-
239
- getModuleOptions : function(name) {
240
- var options = this[name + 'ViewOptions'] || {};
241
- options = typeof(options) == 'function' ? options.call(this) : options;
242
- return name == 'table' ? _.defaults(options, { collection : this.collection }) : options;
48
+
49
+ // Components should trigger the `table:update` event to update
50
+ // the table (e.g. change page size, sort) and fetch the new data.
51
+ this.vent.on('table:update', this.onUpdate, this);
52
+
53
+ this.on('item:rendered', this.setup, this);
54
+
55
+ if (typeof(this.initializeTable) == 'function') {
56
+ this.initializeTable(options);
243
57
  }
244
- });
245
-
246
- // ### Example
247
- // This is how a `PageSizeView` module might be registered in a subclass:
248
- //
249
- // var MyTable = Tableling.Modular.extend({
250
- //
251
- // modules : [ 'pageSize' ],
252
- //
253
- // pageSizeView : PageSizeView,
254
- // pageSizeViewOptions : {
255
- // itemView : PageSizeItem
256
- // },
257
- //
258
- // regions : {
259
- // pageSizeRegion : '.pageSize'
260
- // }
261
- // });
262
-
263
- // Tableling.Module
264
- // ----------------
265
- //
266
- // A module is an item view that is automatically bound to the table's
267
- // event aggregator.
268
- Tableling.Module = Backbone.Marionette.ItemView.extend({
269
-
270
- i18n : {},
271
- templateHelpers : function() {
272
- return this.i18n;
273
- },
274
-
275
- initialize : function(options) {
276
-
277
- this.vent = options.vent;
278
-
279
- // The `setup` method of the view is called when the table
280
- // is first set up.
281
- this.vent.on('table:setup', this.setup, this);
282
-
283
- // The `refresh` method of the view is called every time the table
284
- // is refreshed.
285
- this.vent.on('table:refreshed', this.refresh, this);
286
-
287
- this.i18n = _.clone(options.i18n || this.i18n);
288
- },
289
-
290
- // Call `update` to trigger an update of the table.
291
- update : function() {
292
- this.vent.trigger('table:update', this.config());
293
- },
294
-
295
- // Implementations should override this to set initial values.
296
- setup : function(config) {
297
- },
298
-
299
- // Implementations should override this to stay up to date with
300
- // the table state.
301
- refresh : function(config) {
302
- },
303
-
304
- // New table configuration to be sent on updates. For example,
305
- // a page size view might update the `pageSize` property.
306
- config : function() {
307
- return {};
58
+ },
59
+
60
+ // Called once rendering is complete. By default, it updates the table.
61
+ setup: function() {
62
+ this.ventTrigger('table:setup', this.config);
63
+ if (this.autoUpdate) {
64
+ this.ventTrigger('table:update');
308
65
  }
309
- });
310
-
311
- // Tableling.FieldModule
312
- // ---------------------
313
- //
314
- // A basic module with a single form field. It comes with sensible
315
- // defaults and only requires a `name` and a `template` parameter.
316
- Tableling.FieldModule = Tableling.Module.extend({
317
-
318
- // TODO: check name
319
-
320
- initialize : function(options) {
321
-
322
- Tableling.Module.prototype.initialize.call(this, options);
323
-
324
- if (!this.ui) {
325
- this.ui = {};
326
- }
327
- // The name attribute of the form field is the same as the
328
- // module's, e.g. `pageSize`.
329
- this.ui.field = '[name="' + this.name + '"]';
330
-
331
- if (!this.events) {
332
- this.events = {};
333
- }
334
- this.events.submit = 'onSubmit';
335
- this.events['change [name="' + this.name + '"]'] = 'update';
336
- },
337
-
338
- setup : function(config) {
339
- this.setupValue(config[this.name]);
340
- this.vent.trigger('table:update', this.config(), { refresh : false });
341
- },
342
-
343
- setupValue : function(value) {
344
- this.ui.field.val(value);
345
- },
346
-
347
- // The table property updated is the one with the same name as the module.
348
- config : function() {
349
- var config = {};
350
- config[this.name] = this.ui.field.val();
351
- return config;
352
- },
353
-
354
- onSubmit : function(e) {
355
- e.preventDefault();
356
- return false;
66
+ },
67
+
68
+ // Subclasses must return the Backbone.Collection used to fetch data.
69
+ getCollection: function() {
70
+ return this.collection;
71
+ },
72
+
73
+ // ### Refreshing the table
74
+ update: function(config, options) {
75
+ this.ventTrigger('table:update', config, options);
76
+ },
77
+
78
+ onUpdate: function(config, options) {
79
+
80
+ _.each(config || {}, _.bind(this.updateValue, this));
81
+
82
+ // Set the `refresh` option to false to update the table configuration
83
+ // without refreshing.
84
+ if (!options || typeof(options.refresh) == 'undefined' || options.refresh) {
85
+ this.refresh();
357
86
  }
358
- });
359
-
360
- // This is how a `PageSizeView` module might be implemented:
361
- //
362
- // var html = '<input type="text" name="pageSize" />';
363
- //
364
- // var PageSizeView = Tableling.FieldModule.extend({
365
- // name : 'pageSize'
366
- // template : _.template(html)
367
- // });
368
- //
369
- // When the value of the input field changes, the event aggregator will
370
- // receive a `tableling:update` event with the `pageSize` property set
371
- // to that value.
372
-
373
- Tableling.Plain = {};
374
-
375
- Tableling.Plain.Table = Tableling.Modular.extend({
376
-
377
- className: 'tableling',
378
- modules : [ 'table', 'pageSize', 'quickSearch', 'info', 'page' ],
379
- template : _.template('<div class="header"><div class="pageSize" /><div class="quickSearch" /></div><div class="table" /><div class="footer"><div class="info" /><div class="page" /></div>'),
380
-
381
- regions : {
382
- tableRegion : '.table',
383
- pageSizeRegion : '.pageSize',
384
- quickSearchRegion : '.quickSearch',
385
- infoRegion : '.info',
386
- pageRegion : '.page'
87
+ },
88
+
89
+ updateValue: function(value, key) {
90
+ if (value && value.toString().length) {
91
+ this.config[key] = value;
92
+ } else {
93
+ // Blank values are deleted to avoid sending them in ajax requests.
94
+ delete this.config[key];
387
95
  }
388
- });
389
-
390
- Tableling.Plain.TableView = Backbone.Marionette.CompositeView.extend({
391
-
392
- events : {
393
- 'click thead th.sorting' : 'updateSort',
394
- 'click thead th.sorting-asc' : 'updateSort',
395
- 'click thead th.sorting-desc' : 'updateSort'
396
- },
397
-
398
- initialize : function(options) {
399
- // TODO: add auto-sort
400
- this.vent = options.vent;
401
- this.sort = [];
402
- this.vent.on('table:setup', this.setSort, this);
403
- this.vent.on('table:refreshed', this.setSort, this);
404
- },
405
-
406
- updateSort : function(ev) {
407
-
408
- var el = $(ev.currentTarget);
409
- if (!(el.hasClass('sorting') || el.hasClass('sorting-asc') || el.hasClass('sorting-desc'))) {
410
- return;
411
- }
412
-
413
- var field = this.fieldName(el);
414
-
415
- if (ev.shiftKey || this.sort.length == 1) {
416
-
417
- var index = -1;
418
- _.find(this.sort, function(item, i) {
419
- if (item.split(' ')[0] == field) {
420
- index = i;
421
- }
422
- });
423
-
424
- if (index >= 0) {
425
-
426
- var parts = this.sort[index].split(' ');
427
- this.sort[index] = parts[0] + ' ' + (parts[1] == 'asc' ? 'desc' : 'asc');
428
- this.showSort();
429
- return this.vent.trigger('table:update', this.config());
430
- }
431
- }
432
-
433
- if (!ev.shiftKey) {
434
- this.sort.length = 0;
435
- }
436
-
437
- this.sort.push(field + ' asc');
438
-
439
- this.showSort();
440
-
441
- this.vent.trigger('table:update', this.config());
442
- },
443
-
444
- setSort : function(config) {
445
- if (config && config.sort) {
446
- this.sort = config.sort.slice(0);
447
- this.showSort();
448
- }
449
- },
450
-
451
- showSort : function() {
452
-
453
- this.$el.find('thead th.sorting, thead th.sorting-asc, thead th.sorting-desc').removeClass('sorting sorting-asc sorting-desc').addClass('sorting');
454
-
455
- for (var i = 0; i < this.sort.length; i++) {
456
-
457
- var parts = this.sort[i].split(' ');
458
- var name = parts[0];
459
- var direction = parts[1];
460
-
461
- field = this.$el.find('thead [data-field="' + name + '"]');
462
- if (!field.length) {
463
- field = this.$el.find('thead th:contains("' + name + '")');
464
- }
465
-
466
- if (field.length) {
467
- field.removeClass('sorting').addClass(direction == 'desc' ? 'sorting-desc' : 'sorting-asc');
468
- }
469
- }
470
- },
471
-
472
- config : function() {
473
- return {
474
- page : 1,
475
- sort : this.sortConfig()
476
- };
477
- },
478
-
479
- sortConfig : function() {
480
- return this.sort.length ? this.sort : null;
481
- },
482
-
483
- fieldName : function(el) {
484
- return el.data('field') || el.text();
96
+ },
97
+
98
+ refresh: function() {
99
+
100
+ // You can provide `fetchOptions` to add properties to the
101
+ // fetch request.
102
+ //
103
+ // var MyTable = Tableling.Table.extend({
104
+ // fetchOptions: {
105
+ // type: 'POST' // fetch data with POST
106
+ // }
107
+ // });
108
+ //
109
+ // // You can also override for each instance.
110
+ // new MyTable({
111
+ // fetchOptions: {
112
+ // type: 'GET'
113
+ // }
114
+ // });
115
+ var options = _.clone(this.fetchOptions);
116
+ options.data = this.requestData();
117
+ options.success = _.bind(this.processResponse, this);
118
+ options.reset = true;
119
+
120
+ // `table:refreshing` is triggered every time new data is being fetched.
121
+ // The first argument is the request data.
122
+ this.ventTrigger('table:refreshing', options.data);
123
+
124
+ this.getCollection().fetch(options);
125
+ },
126
+
127
+ // ### Request
128
+ requestData: function() {
129
+ return this.config;
130
+ },
131
+
132
+ // ### Response
133
+ processResponse: function(collection, response) {
134
+
135
+ this.config.length = collection.length;
136
+
137
+ // Tableling expects the response from a fetch to have a `total` property
138
+ // which is the total number of items (not just in the current page).
139
+ this.config.total = response.total;
140
+
141
+ // The server may override the `page` property, for example if the
142
+ // requested page was outside the range of available pages.
143
+ if (response.page) {
144
+ this.config.page = response.page;
485
145
  }
486
- });
487
-
488
- Tableling.Plain.PageSizeView = Tableling.Plain.Table.prototype.pageSizeView = Tableling.FieldModule.extend({
489
-
490
- // TODO: update current page intelligently
491
- name : 'pageSize',
492
- template : function(data) {
493
- return _.template('<select name="pageSize" /> <%- entries %>', data);
494
- },
495
-
496
- i18n : {
497
- entries : 'entries per page'
498
- },
499
- sizes : [ 10, 15, 20, 25, 50 ],
500
-
501
- ui : {
502
- field : 'select'
503
- },
504
-
505
- initialize : function(options) {
506
- Tableling.FieldModule.prototype.initialize.call(this, options);
507
- this.sizes = _.clone(options.sizes || this.sizes);
508
- },
509
-
510
- onRender : function() {
511
- this.ui.field.empty();
512
- _.each(this.sizes, _.bind(this.addSize, this));
513
- },
514
-
515
- addSize : function(size) {
516
- $('<option />').text(size).appendTo(this.ui.field);
517
- },
518
-
519
- setupValue : function(value) {
520
- if (value) {
521
- Tableling.FieldModule.prototype.setupValue.apply(this, Array.prototype.slice.call(arguments));
522
- }
523
- },
524
-
525
- config : function() {
526
- var config = Tableling.FieldModule.prototype.config.call(this);
527
- config.page = 1;
528
- return config;
146
+
147
+ // `tableling:refreshed` is triggered after every refresh. The first argument
148
+ // is the current table configuration with the following additional meta data:
149
+ //
150
+ // * `total` - the total number of items
151
+ // * `length` - the number of items in the current page
152
+ this.ventTrigger('table:refreshed', this.config);
153
+ },
154
+
155
+ // Triggers an event in the event aggregator. If `Tableling.debug` is set, it also
156
+ // logs the event and its arguments.
157
+ ventTrigger: function() {
158
+
159
+ var args = Array.prototype.slice.call(arguments);
160
+ if (Tableling.debug) {
161
+ console.log(_.first(args) + ' - ' + JSON.stringify(args.slice(1)));
529
162
  }
530
- });
531
-
532
- Tableling.Plain.QuickSearchView = Tableling.Plain.Table.prototype.quickSearchView = Tableling.FieldModule.extend({
533
-
534
- name : 'quickSearch',
535
- template : function(data) {
536
- return _.template('<input type="text" name="quickSearch" placeholder="<%- quickSearch %>" />', data);
537
- },
538
-
539
- i18n : {
540
- quickSearch : 'Quick search...'
541
- },
542
-
543
- config : function() {
544
- var config = Tableling.FieldModule.prototype.config.call(this);
545
- config.page = 1;
546
- return config;
163
+
164
+ this.vent.trigger.apply(this.vent, args);
165
+ }
166
+ });
167
+
168
+ // Tableling.Collection
169
+ // --------------------
170
+ //
171
+ // Tableling expects fetch responses to have a `total` property in addition
172
+ // to the model data. You can extend this Backbone.Collection subclass which
173
+ // expects the following response format:
174
+ //
175
+ // {
176
+ // "total": 12,
177
+ // "data": [
178
+ // { /* ... model data ... */ },
179
+ // { /* ... model data ... */ }
180
+ // ]
181
+ // }
182
+ Tableling.Collection = Backbone.Collection.extend({
183
+
184
+ parse: function(response) {
185
+ return response.data;
186
+ }
187
+ });
188
+
189
+ // Implementations
190
+ // ---------------
191
+ //
192
+ // <a href="tableling.bootstrap.html">tableling.bootstrap</a> provides views styled
193
+ // with [Twitter Bootstrap](http://twitter.github.com/bootstrap/) classes.
194
+
195
+ // Tableling.Modular
196
+ // -----------------
197
+ //
198
+ // Tableling subclass which splits functionality into *modules*
199
+ // and handles rendering.
200
+ Tableling.Modular = Tableling.Table.extend({
201
+
202
+ // The list of module names must be specified by subclasses.
203
+ modules: [],
204
+
205
+ // Modules are set up after rendering, before refreshing.
206
+ setup: function() {
207
+
208
+ this.moduleViews = {};
209
+ _.each(this.modules, this.setupModule, this);
210
+
211
+ Tableling.Table.prototype.setup.call(this);
212
+ },
213
+
214
+ // ### Modules
215
+ // Each module is identified by a name, for example `pageSize`.
216
+ setupModule: function(name) {
217
+
218
+ // The layout must have a region named after the module, e.g. `pageSizeRegion`.
219
+ var region = name + 'Region';
220
+
221
+ // It must have a view class, e.g. `pageSizeView`, which will be shown into
222
+ // the region.
223
+ var viewClass = this[name + 'View'];
224
+
225
+ // When instantiated, the view class will be passed the event
226
+ // aggregator as the `vent` option. Additional options can be
227
+ // given named after the view class, e.g. `pageSizeViewOptions`.
228
+ var options = _.extend(this.getModuleOptions(name), { vent: this.vent });
229
+
230
+ // The collection is also passed to view classes.
231
+ _.defaults(options, { collection: this.getCollection() });
232
+
233
+ var view = new viewClass(options);
234
+
235
+ // Module view instances are stored by name in the `moduleViews` property
236
+ // for future reference.
237
+ this.moduleViews[name] = view;
238
+
239
+ this[region].show(view);
240
+ return view;
241
+ },
242
+
243
+ // By default the collection is the one given at construction.
244
+ // Otherwise, a modular table expects a `table` module which
245
+ // should have a collection (e.g. a Marionette CompositeView or
246
+ // CollectionView). If your subclass does not have either, it
247
+ // should override this method to return the Backbone.Collection
248
+ // used to fetch table data.
249
+ getCollection: function() {
250
+ return this.collection || (this.moduleViews && this.moduleViews.table ? this.moduleViews.table.collection : undefined);
251
+ },
252
+
253
+ getModuleOptions: function(name) {
254
+ return _.result(this, name + 'ViewOptions') || {};
255
+ }
256
+ });
257
+
258
+ // ### Example
259
+ // This is how a `PageSizeView` module might be registered in a subclass:
260
+ //
261
+ // var MyTable = Tableling.Modular.extend({
262
+ //
263
+ // modules: [ 'pageSize' ],
264
+ //
265
+ // pageSizeView: PageSizeView,
266
+ // pageSizeViewOptions: {
267
+ // itemView: PageSizeItem
268
+ // },
269
+ //
270
+ // regions: {
271
+ // pageSizeRegion: '.pageSize'
272
+ // }
273
+ // });
274
+
275
+ // Tableling.Module
276
+ // ----------------
277
+ //
278
+ // A module is an item view that is automatically bound to the table's
279
+ // event aggregator.
280
+ Tableling.Module = Backbone.Marionette.ItemView.extend({
281
+
282
+ i18n: {},
283
+ templateHelpers: function() {
284
+ return this.i18n;
285
+ },
286
+
287
+ initialize: function(options) {
288
+
289
+ this.vent = options.vent;
290
+
291
+ // The `setup` method of the view is called when the table
292
+ // is first set up.
293
+ this.vent.on('table:setup', this.setup, this);
294
+
295
+ // The `refresh` method of the view is called every time the table
296
+ // is refreshed.
297
+ this.vent.on('table:refreshed', this.refresh, this);
298
+
299
+ this.i18n = _.clone(options.i18n || this.i18n);
300
+
301
+ if (typeof(this.initializeModule) == 'function') {
302
+ this.initializeModule(options);
547
303
  }
548
- });
549
-
550
- Tableling.Plain.InfoView = Tableling.Plain.Table.prototype.infoView = Tableling.Module.extend({
551
-
552
- template : function(data) {
553
- return _.template(data.template, {
554
- first : '<span class="first">0</span>',
555
- last : '<span class="last">0</span>',
556
- total : '<span class="total">0</span>'
304
+ },
305
+
306
+ // Call `update` to trigger an update of the table.
307
+ update: function() {
308
+ this.vent.trigger('table:update', this.config());
309
+ },
310
+
311
+ // Implementations should override this to set initial values.
312
+ setup: function(config) {
313
+ },
314
+
315
+ // Implementations should override this to stay up to date with
316
+ // the table state.
317
+ refresh: function(config) {
318
+ },
319
+
320
+ // New table configuration to be sent on updates. For example,
321
+ // a page size view might update the `pageSize` property.
322
+ config: function() {
323
+ return {};
324
+ }
325
+ });
326
+
327
+ // Tableling.FieldModule
328
+ // ---------------------
329
+ //
330
+ // A basic module with a single form field. It comes with sensible
331
+ // defaults and only requires a `name` and a `template` parameter.
332
+ Tableling.FieldModule = Tableling.Module.extend({
333
+
334
+ initialize: function(options) {
335
+ if (!_.isString(this.name)) {
336
+ throw new Error("Tableling module must have a name property.");
337
+ }
338
+
339
+ if (!this.ui) {
340
+ this.ui = {};
341
+ }
342
+ // The name attribute of the form field is the same as the
343
+ // module's, e.g. `pageSize`.
344
+ this.ui.field = '[name="' + this.name + '"]';
345
+
346
+ if (!this.events) {
347
+ this.events = {};
348
+ }
349
+ this.events.submit = 'onSubmit';
350
+ this.events['change [name="' + this.name + '"]'] = 'update';
351
+
352
+ Tableling.Module.prototype.initialize.call(this, options);
353
+ },
354
+
355
+ setup: function(config) {
356
+ this.setupValue(config[this.name]);
357
+ this.vent.trigger('table:update', this.config(), { refresh: false });
358
+ },
359
+
360
+ setupValue: function(value) {
361
+ this.ui.field.val(value);
362
+ },
363
+
364
+ // The table property updated is the one with the same name as the module.
365
+ config: function() {
366
+ var config = {};
367
+ config[this.name] = this.ui.field.val();
368
+ return config;
369
+ },
370
+
371
+ onSubmit: function(e) {
372
+ e.preventDefault();
373
+ return false;
374
+ }
375
+ });
376
+
377
+ // This is how a `PageSizeView` module might be implemented:
378
+ //
379
+ // var html = '<input type="text" name="pageSize" />';
380
+ //
381
+ // var PageSizeView = Tableling.FieldModule.extend({
382
+ // name: 'pageSize'
383
+ // template: _.template(html)
384
+ // });
385
+ //
386
+ // When the value of the input field changes, the event aggregator will
387
+ // receive a `tableling:update` event with the `pageSize` property set
388
+ // to that value.
389
+
390
+ Tableling.Plain = {};
391
+
392
+ Tableling.Plain.Table = Tableling.Modular.extend({
393
+
394
+ className: 'tableling',
395
+ modules: [ 'table', 'pageSize', 'quickSearch', 'info', 'page' ],
396
+ template: _.template('<div class="header"><div class="pageSize" /><div class="quickSearch" /></div><div class="table" /><div class="footer"><div class="info" /><div class="page" /></div>'),
397
+
398
+ regions: {
399
+ tableRegion: '.table',
400
+ pageSizeRegion: '.pageSize',
401
+ quickSearchRegion: '.quickSearch',
402
+ infoRegion: '.info',
403
+ pageRegion: '.page'
404
+ }
405
+ });
406
+
407
+ // TODO: make table view a module
408
+ Tableling.Plain.TableView = Backbone.Marionette.CompositeView.extend({
409
+
410
+ moduleEvents: {
411
+ 'click thead th.sorting': 'updateSort',
412
+ 'click thead th.sorting-asc': 'updateSort',
413
+ 'click thead th.sorting-desc': 'updateSort'
414
+ },
415
+
416
+ // TODO: add auto-sort
417
+ initialize: function(options) {
418
+
419
+ this.vent = options.vent;
420
+ this.sort = [];
421
+ this.vent.on('table:setup', this.setSort, this);
422
+ this.vent.on('table:refreshed', this.setSort, this);
423
+ this.events = _.extend({}, this.events || {}, this.moduleEvents);
424
+
425
+ if (typeof(this.initializeModule) == 'function') {
426
+ this.initializeModule(options);
427
+ }
428
+ },
429
+
430
+ updateSort: function(ev) {
431
+
432
+ var el = $(ev.currentTarget);
433
+ if (!(el.hasClass('sorting') || el.hasClass('sorting-asc') || el.hasClass('sorting-desc'))) {
434
+ return;
435
+ }
436
+
437
+ var field = this.fieldName(el);
438
+
439
+ if (ev.shiftKey || this.sort.length == 1) {
440
+
441
+ var index = -1;
442
+ _.find(this.sort, function(item, i) {
443
+ if (item.split(' ')[0] == field) {
444
+ index = i;
445
+ }
557
446
  });
558
- },
559
-
560
- i18n : {
561
- template : 'Showing <%= first %> to <%= last %> of <%= total %> entries'
562
- },
563
-
564
- ui : {
565
- first: '.first',
566
- last: '.last',
567
- total: '.total'
568
- },
569
-
570
- refresh : function(data) {
571
- if (data) {
572
- this.ui.first.text(this.firstRecord(data));
573
- this.ui.last.text(this.lastRecord(data));
574
- this.ui.total.text(data.total);
447
+
448
+ if (index >= 0) {
449
+
450
+ var parts = this.sort[index].split(' ');
451
+ this.sort[index] = parts[0] + ' ' + (parts[1] == 'asc' ? 'desc' : 'asc');
452
+ this.showSort();
453
+ return this.vent.trigger('table:update', this.config());
575
454
  }
576
- },
577
-
578
- firstRecord : function(data) {
579
- return data.length ? ((data.page || 1) - 1) * data.pageSize + 1 : 0;
580
- },
581
-
582
- lastRecord : function(data) {
583
- return data.length ? this.firstRecord(data) + data.length - 1 : 0;
584
455
  }
585
- });
586
-
587
- Tableling.Plain.PageView = Tableling.Plain.Table.prototype.pageView = Tableling.Module.extend({
456
+
457
+ if (!ev.shiftKey) {
458
+ this.sort.length = 0;
459
+ }
460
+
461
+ this.sort.push(field + ' asc');
462
+
463
+ this.showSort();
464
+
465
+ this.vent.trigger('table:update', this.config());
466
+ },
467
+
468
+ setSort: function(config) {
469
+ if (config && config.sort) {
470
+ this.sort = config.sort.slice(0);
471
+ this.showSort();
472
+ }
473
+ },
474
+
475
+ showSort: function() {
476
+
477
+ this.$el.find('thead th.sorting, thead th.sorting-asc, thead th.sorting-desc').removeClass('sorting sorting-asc sorting-desc').addClass('sorting');
478
+
479
+ for (var i = 0; i < this.sort.length; i++) {
480
+
481
+ var parts = this.sort[i].split(' ');
482
+ var name = parts[0];
483
+ var direction = parts[1];
588
484
 
589
- template : _.template('<ul class="pagination"><li class="first"><a href="#">&lt;&lt;</a></li><li class="previous"><a href="#">&lt;</a></li><li class="next"><a href="#">&gt;</a></li><li class="last"><a href="#">&gt;&gt;</a></li></ul>'),
590
- pageTemplate : _.template('<li class="page"><a href="#"><%- number %></a></li>'),
591
-
592
- ui : {
593
- first : '.first',
594
- previous : '.previous',
595
- next : '.next',
596
- last : '.last'
597
- },
598
-
599
- events : {
600
- 'click .first:not(.disabled)' : 'goToFirstPage',
601
- 'click .previous:not(.disabled)' : 'goToPreviousPage',
602
- 'click .page:not(.disabled)' : 'goToPage',
603
- 'click .next:not(.disabled)' : 'goToNextPage',
604
- 'click .last:not(.disabled)' : 'goToLastPage'
605
- },
606
-
607
- refresh : function(data) {
608
- this.$el.find('.page').remove();
609
- if (!data || !data.length) {
610
- this.ui.first.addClass('disabled');
611
- this.ui.previous.addClass('disabled');
612
- this.ui.next.addClass('disabled');
613
- this.ui.last.addClass('disabled');
614
- } else {
615
- this.data = data;
616
- this.enable(this.ui.first, this.getPage(data) > 1);
617
- this.enable(this.ui.previous, this.getPage(data) > 1);
618
- this.setupPages();
619
- this.enable(this.ui.next, this.getPage(data) < this.numberOfPages(data));
620
- this.enable(this.ui.last, this.getPage(data) < this.numberOfPages(data));
621
- }
622
- },
623
-
624
- setupPages : function() {
625
-
626
- var page = this.getPage(this.data);
627
- var total = this.numberOfPages();
628
-
629
- var first = page - 2;
630
- if (total - first < 4) {
631
- first = total - 4;
485
+ field = this.$el.find('thead [data-field="' + name + '"]');
486
+ if (!field.length) {
487
+ field = this.$el.find('thead th:contains("' + name + '")');
632
488
  }
633
-
634
- if (first < 1) {
635
- first = 1;
636
- }
637
-
638
- var n = 5;
639
- if (first + n - 1 > total) {
640
- n = total - first + 1;
641
- }
642
-
643
- _.times(n, function(i) {
644
- $(this.pageTemplate({ number : first + i })).insertBefore(this.ui.next);
645
- }, this);
646
-
647
- var i = page - first;
648
- this.$el.find('.page').slice(i, i + 1).addClass('disabled');
649
- },
650
-
651
- enable : function(el, enabled) {
652
- el.removeClass('disabled');
653
- if (!enabled) {
654
- el.addClass('disabled');
489
+
490
+ if (field.length) {
491
+ field.removeClass('sorting').addClass(direction == 'desc' ? 'sorting-desc' : 'sorting-asc');
655
492
  }
656
- },
657
-
658
- numberOfPages : function() {
659
- return Math.ceil(this.data.total / this.data.pageSize);
660
- },
661
-
662
- goToFirstPage : function(e) {
663
- e.preventDefault();
664
- this.goToPageNumber(1);
665
- },
666
-
667
- goToPreviousPage : function(e) {
668
- e.preventDefault();
669
- this.goToPageNumber(this.getPage(this.data) - 1);
670
- },
671
-
672
- goToPage : function(e) {
673
- e.preventDefault();
674
- this.goToPageNumber(parseInt($(e.target).text(), 10));
675
- },
676
-
677
- goToNextPage : function(e) {
678
- e.preventDefault();
679
- this.goToPageNumber(this.getPage(this.data) + 1);
680
- },
681
-
682
- goToLastPage : function(e) {
683
- e.preventDefault();
684
- this.goToPageNumber(this.numberOfPages());
685
- },
686
-
687
- goToPageNumber : function(n) {
688
- this.vent.trigger('table:update', { page : n });
689
- },
690
-
691
- getPage : function(data) {
692
- return data.page || 1;
693
493
  }
694
- });
695
-
696
- Tableling.Bootstrap = {};
697
-
698
- Tableling.Bootstrap.Table = Tableling.Plain.Table.extend({
699
- template : _.template('<div class="header clearfix"><div class="pageSize pull-left" /><div class="quickSearch pull-right" /></div><div class="table" /><div class="footer clearfix"><div class="info pull-left" /><div class="page pull-right" /></div>')
700
- });
701
-
702
- Tableling.Bootstrap.TableView = Tableling.Plain.TableView.extend({});
703
-
704
- Tableling.Bootstrap.PageSizeView = Tableling.Bootstrap.Table.prototype.pageSizeView = Tableling.Plain.PageSizeView.extend({
705
-
706
- tagName : 'form',
707
- className : 'form-inline',
708
- attributes : {
709
- role : 'form'
710
- },
711
- template : function(data) {
712
- return _.template('<div class="formGroup"><select name="pageSize" class="form-control"><option>5</option><option>10</option><option>15</option></select> <%- entries %></div>', data);
494
+ },
495
+
496
+ config: function() {
497
+ return {
498
+ page: 1,
499
+ sort: this.sortConfig()
500
+ };
501
+ },
502
+
503
+ sortConfig: function() {
504
+ return this.sort.length ? this.sort : null;
505
+ },
506
+
507
+ fieldName: function(el) {
508
+ return el.data('field') || el.text();
509
+ }
510
+ });
511
+
512
+ Tableling.Plain.PageSizeView = Tableling.Plain.Table.prototype.pageSizeView = Tableling.FieldModule.extend({
513
+
514
+ // TODO: update current page intelligently
515
+ name: 'pageSize',
516
+ template: function(data) {
517
+ return _.template('<select name="pageSize" /> <%- entries %>', data);
518
+ },
519
+
520
+ i18n: {
521
+ entries: 'entries per page'
522
+ },
523
+ sizes: [ 10, 15, 20, 25, 50 ],
524
+
525
+ ui: {
526
+ field: 'select'
527
+ },
528
+
529
+ initialize: function(options) {
530
+ this.sizes = _.clone(options.sizes || this.sizes);
531
+ Tableling.FieldModule.prototype.initialize.call(this, options);
532
+ },
533
+
534
+ onRender: function() {
535
+ this.ui.field.empty();
536
+ _.each(this.sizes, _.bind(this.addSize, this));
537
+ },
538
+
539
+ addSize: function(size) {
540
+ $('<option />').text(size).appendTo(this.ui.field);
541
+ },
542
+
543
+ setupValue: function(value) {
544
+ if (value) {
545
+ Tableling.FieldModule.prototype.setupValue.apply(this, Array.prototype.slice.call(arguments));
713
546
  }
714
- });
715
-
716
- Tableling.Bootstrap.QuickSearchView = Tableling.Bootstrap.Table.prototype.quickSearchView = Tableling.Plain.QuickSearchView.extend({
717
-
718
- tagName : 'form',
719
- className : 'form-inline',
720
- attributes : {
721
- role : 'form'
722
- },
723
- template : function(data) {
724
- return _.template('<div class="formGroup"><input type="text" name="quickSearch" class="form-control" placeholder="<%- quickSearch %>" /></div>', data);
547
+ },
548
+
549
+ config: function() {
550
+ var config = Tableling.FieldModule.prototype.config.call(this);
551
+ config.page = 1;
552
+ return config;
553
+ }
554
+ });
555
+
556
+ Tableling.Plain.QuickSearchView = Tableling.Plain.Table.prototype.quickSearchView = Tableling.FieldModule.extend({
557
+
558
+ name: 'quickSearch',
559
+ template: function(data) {
560
+ return _.template('<input type="text" name="quickSearch" placeholder="<%- quickSearch %>" />', data);
561
+ },
562
+
563
+ i18n: {
564
+ quickSearch: 'Quick search...'
565
+ },
566
+
567
+ config: function() {
568
+ var config = Tableling.FieldModule.prototype.config.call(this);
569
+ config.page = 1;
570
+ return config;
571
+ }
572
+ });
573
+
574
+ Tableling.Plain.InfoView = Tableling.Plain.Table.prototype.infoView = Tableling.Module.extend({
575
+
576
+ template: function(data) {
577
+ return _.template(data.template, {
578
+ first: '<span class="first">0</span>',
579
+ last: '<span class="last">0</span>',
580
+ total: '<span class="total">0</span>'
581
+ });
582
+ },
583
+
584
+ i18n: {
585
+ template: 'Showing <%= first %> to <%= last %> of <%= total %> entries'
586
+ },
587
+
588
+ ui: {
589
+ first: '.first',
590
+ last: '.last',
591
+ total: '.total'
592
+ },
593
+
594
+ refresh: function(data) {
595
+ if (data) {
596
+ this.ui.first.text(this.firstRecord(data));
597
+ this.ui.last.text(this.lastRecord(data));
598
+ this.ui.total.text(data.total);
599
+ }
600
+ },
601
+
602
+ firstRecord: function(data) {
603
+ return data.length ? ((data.page || 1) - 1) * data.pageSize + 1 : 0;
604
+ },
605
+
606
+ lastRecord: function(data) {
607
+ return data.length ? this.firstRecord(data) + data.length - 1 : 0;
608
+ }
609
+ });
610
+
611
+ Tableling.Plain.PageView = Tableling.Plain.Table.prototype.pageView = Tableling.Module.extend({
612
+
613
+ template: _.template('<ul class="pagination"><li class="first"><a href="#">&lt;&lt;</a></li><li class="previous"><a href="#">&lt;</a></li><li class="next"><a href="#">&gt;</a></li><li class="last"><a href="#">&gt;&gt;</a></li></ul>'),
614
+ pageTemplate: _.template('<li class="page"><a href="#"><%- number %></a></li>'),
615
+
616
+ ui: {
617
+ first: '.first',
618
+ previous: '.previous',
619
+ next: '.next',
620
+ last: '.last'
621
+ },
622
+
623
+ events: {
624
+ 'click .first:not(.disabled)': 'goToFirstPage',
625
+ 'click .previous:not(.disabled)': 'goToPreviousPage',
626
+ 'click .page:not(.disabled)': 'goToPage',
627
+ 'click .next:not(.disabled)': 'goToNextPage',
628
+ 'click .last:not(.disabled)': 'goToLastPage'
629
+ },
630
+
631
+ refresh: function(data) {
632
+ this.$el.find('.page').remove();
633
+ if (!data || !data.length) {
634
+ this.ui.first.addClass('disabled');
635
+ this.ui.previous.addClass('disabled');
636
+ this.ui.next.addClass('disabled');
637
+ this.ui.last.addClass('disabled');
638
+ } else {
639
+ this.data = data;
640
+ this.enable(this.ui.first, this.getPage(data) > 1);
641
+ this.enable(this.ui.previous, this.getPage(data) > 1);
642
+ this.setupPages();
643
+ this.enable(this.ui.next, this.getPage(data) < this.numberOfPages(data));
644
+ this.enable(this.ui.last, this.getPage(data) < this.numberOfPages(data));
645
+ }
646
+ },
647
+
648
+ setupPages: function() {
649
+
650
+ var page = this.getPage(this.data);
651
+ var total = this.numberOfPages();
652
+
653
+ var first = page - 2;
654
+ if (total - first < 4) {
655
+ first = total - 4;
656
+ }
657
+
658
+ if (first < 1) {
659
+ first = 1;
725
660
  }
726
- });
727
-
728
- Tableling.Bootstrap.InfoView = Tableling.Bootstrap.Table.prototype.infoView = Tableling.Plain.InfoView.extend({});
729
-
730
- Tableling.Bootstrap.PageView = Tableling.Bootstrap.Table.prototype.pageView = Tableling.Plain.PageView.extend({});
731
-
661
+
662
+ var n = 5;
663
+ if (first + n - 1 > total) {
664
+ n = total - first + 1;
665
+ }
666
+
667
+ _.times(n, function(i) {
668
+ $(this.pageTemplate({ number: first + i })).insertBefore(this.ui.next);
669
+ }, this);
670
+
671
+ var i = page - first;
672
+ this.$el.find('.page').slice(i, i + 1).addClass('disabled');
673
+ },
674
+
675
+ enable: function(el, enabled) {
676
+ el.removeClass('disabled');
677
+ if (!enabled) {
678
+ el.addClass('disabled');
679
+ }
680
+ },
681
+
682
+ numberOfPages: function() {
683
+ return Math.ceil(this.data.total / this.data.pageSize);
684
+ },
685
+
686
+ goToFirstPage: function(e) {
687
+ e.preventDefault();
688
+ this.goToPageNumber(1);
689
+ },
690
+
691
+ goToPreviousPage: function(e) {
692
+ e.preventDefault();
693
+ this.goToPageNumber(this.getPage(this.data) - 1);
694
+ },
695
+
696
+ goToPage: function(e) {
697
+ e.preventDefault();
698
+ this.goToPageNumber(parseInt($(e.target).text(), 10));
699
+ },
700
+
701
+ goToNextPage: function(e) {
702
+ e.preventDefault();
703
+ this.goToPageNumber(this.getPage(this.data) + 1);
704
+ },
705
+
706
+ goToLastPage: function(e) {
707
+ e.preventDefault();
708
+ this.goToPageNumber(this.numberOfPages());
709
+ },
710
+
711
+ goToPageNumber: function(n) {
712
+ this.vent.trigger('table:update', { page: n });
713
+ },
714
+
715
+ getPage: function(data) {
716
+ return data.page || 1;
717
+ }
718
+ });
719
+
720
+ Tableling.Bootstrap = {};
721
+
722
+ Tableling.Bootstrap.Table = Tableling.Plain.Table.extend({
723
+ template: _.template('<div class="header clearfix"><div class="pageSize pull-left" /><div class="quickSearch pull-right" /></div><div class="table" /><div class="footer clearfix"><div class="info pull-left" /><div class="page pull-right" /></div>')
724
+ });
725
+
726
+ Tableling.Bootstrap.TableView = Tableling.Plain.TableView.extend({});
727
+
728
+ Tableling.Bootstrap.PageSizeView = Tableling.Bootstrap.Table.prototype.pageSizeView = Tableling.Plain.PageSizeView.extend({
729
+
730
+ tagName: 'form',
731
+ className: 'form-inline',
732
+ attributes: {
733
+ role: 'form'
734
+ },
735
+ template: function(data) {
736
+ return _.template('<div class="formGroup"><select name="pageSize" class="form-control"><option>5</option><option>10</option><option>15</option></select> <%- entries %></div>', data);
737
+ }
738
+ });
739
+
740
+ Tableling.Bootstrap.QuickSearchView = Tableling.Bootstrap.Table.prototype.quickSearchView = Tableling.Plain.QuickSearchView.extend({
741
+
742
+ tagName: 'form',
743
+ className: 'form-inline',
744
+ attributes: {
745
+ role: 'form'
746
+ },
747
+ template: function(data) {
748
+ return _.template('<div class="formGroup"><input type="text" name="quickSearch" class="form-control" placeholder="<%- quickSearch %>" /></div>', data);
749
+ }
750
+ });
751
+
752
+ Tableling.Bootstrap.InfoView = Tableling.Bootstrap.Table.prototype.infoView = Tableling.Plain.InfoView.extend({});
753
+
754
+ Tableling.Bootstrap.PageView = Tableling.Bootstrap.Table.prototype.pageView = Tableling.Plain.PageView.extend({});
732
755
 
733
756
  return Tableling;
734
757
 
735
- })(Backbone, _, $ || window.jQuery || window.Zepto || window.ender);
758
+ })(Backbone, _, $ || window.jQuery || window.Zepto || window.ender);