tableling-rails 0.0.21 → 0.0.22

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