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.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- YjRhZjkyZGZjNDlmM2FkY2Q0YTRiYzUyMGFiNmZmNDJjZTExZGFlZA==
4
+ ZTUxMzMyYjBkZDk5OGVhMTQwZTljNzNjNDE2ZTNkOGE1ZDllMzU2NA==
5
5
  data.tar.gz: !binary |-
6
- ODhiOWY0MzEyOTU2ZDc2YTg2ZmE5NDhhZGQ1MmZmNjgyOGYzMzI3Mg==
6
+ ZGMzZjA0ODRiZTEzZTZlNjVjMWQzYmQyOWZmNGI2NDgwY2ZmMzY0YQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- Yjk3NGE3NTMyNjlhMTVmNmM1NzQ3NjYzOTVhMDIyMjA4ZDgyYmVmZmE4YTA1
10
- ODE3MzI5MGU3YjhkYTQyMjcwZWRkMzMxNGEwNjRkM2FkMWVhNDY1YWQzMDI1
11
- OTYzNWRiNDgxOWY3NmZiZDJkYTk3MjRhNjkyOTdiZGMxMzFhNmE=
9
+ NTE4NGRjZjVhMzBhMGM4NWE5MmZkZGI3NTMwYmExNTA3NDg2ZDNkN2JlYzBj
10
+ YzNhNmY3M2ZlNWY0ZmVkNzljYzg1YjgwNzZmMzA3OWMyYWMzZWRkYzZlMDM3
11
+ NDUxMzliNDM4Nzk0Y2U2ODRhMjI1Zjk0ZmYzZWI5MTgxODI1MDI=
12
12
  data.tar.gz: !binary |-
13
- NzM1ZjYzOWY3N2ViYWVmMjVmMzViYzE1ZWI2NmJmYmFjNGQzYzE3NmFhZjUy
14
- NWM3MGZhOTY4MWRlMDZkODdhODA5OGRhNGYwYTk5NjY4OTQ0NGNjMThiMjIy
15
- YTk0YWE1MzVhZDlmZGYzOGEyYjU0YmQwOTIxOWU5N2UwMmEwY2Q=
13
+ YTMwYjEwNTQwYzY4ZTQ4ZWVmZmVmZmU2NmI4OWRmMWNhZmI5OTA3NmI4ZGI0
14
+ OGQzMmJkY2YxZjM0YzMyYjNmOTBiN2U3NTAxZGE1MTZhM2ZiNWI0Y2IwMWU4
15
+ MjNmOGQ0M2FkNmIzNTE0MmYxNGE5MWZlY2U5MDc4ODJiMzIyY2U=
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012 Simon Oulevay (Alpha Hydrae)
1
+ Copyright (c) 2012-2014 Simon Oulevay (Alpha Hydrae)
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.23
1
+ 0.0.24
@@ -1,3 +1,3 @@
1
1
  module Tableling
2
- VERSION = "0.0.23"
2
+ VERSION = "0.0.24"
3
3
  end
@@ -1582,9 +1582,9 @@
1582
1582
 
1583
1583
  // MarionetteJS (Backbone.Marionette)
1584
1584
  // ----------------------------------
1585
- // v1.4.1
1585
+ // v1.5.1
1586
1586
  //
1587
- // Copyright (c)2013 Derick Bailey, Muted Solutions, LLC.
1587
+ // Copyright (c)2014 Derick Bailey, Muted Solutions, LLC.
1588
1588
  // Distributed under MIT license
1589
1589
  //
1590
1590
  // http://marionettejs.com
@@ -2078,11 +2078,11 @@ Marionette.getOption = function(target, optionName){
2078
2078
  // `this.triggerMethod("foo")` will trigger the "foo" event and
2079
2079
  // call the "onFoo" method.
2080
2080
  //
2081
- // `this.triggerMethod("foo:bar") will trigger the "foo:bar" event and
2081
+ // `this.triggerMethod("foo:bar")` will trigger the "foo:bar" event and
2082
2082
  // call the "onFooBar" method.
2083
2083
  Marionette.triggerMethod = (function(){
2084
2084
 
2085
- // split the event name on the :
2085
+ // split the event name on the ":"
2086
2086
  var splitter = /(^|:)(\w)/gi;
2087
2087
 
2088
2088
  // take the event section ("section1:section2:section3")
@@ -2091,7 +2091,7 @@ Marionette.triggerMethod = (function(){
2091
2091
  return eventName.toUpperCase();
2092
2092
  }
2093
2093
 
2094
- // actual triggerMethod name
2094
+ // actual triggerMethod implementation
2095
2095
  var triggerMethod = function(event) {
2096
2096
  // get the method name from the event name
2097
2097
  var methodName = 'on' + event.replace(splitter, getEventName);
@@ -2119,7 +2119,7 @@ Marionette.triggerMethod = (function(){
2119
2119
  // in the DOM, trigger a "dom:refresh" event every time it is
2120
2120
  // re-rendered.
2121
2121
 
2122
- Marionette.MonitorDOMRefresh = (function(){
2122
+ Marionette.MonitorDOMRefresh = (function(documentElement){
2123
2123
  // track when the view has been shown in the DOM,
2124
2124
  // using a Marionette.Region (or by other means of triggering "show")
2125
2125
  function handleShow(view){
@@ -2135,13 +2135,17 @@ Marionette.MonitorDOMRefresh = (function(){
2135
2135
 
2136
2136
  // Trigger the "dom:refresh" event and corresponding "onDomRefresh" method
2137
2137
  function triggerDOMRefresh(view){
2138
- if (view._isShown && view._isRendered){
2138
+ if (view._isShown && view._isRendered && isInDOM(view)){
2139
2139
  if (_.isFunction(view.triggerMethod)){
2140
2140
  view.triggerMethod("dom:refresh");
2141
2141
  }
2142
2142
  }
2143
2143
  }
2144
2144
 
2145
+ function isInDOM(view) {
2146
+ return documentElement.contains(view.el);
2147
+ }
2148
+
2145
2149
  // Export public API
2146
2150
  return function(view){
2147
2151
  view.listenTo(view, "show", function(){
@@ -2152,7 +2156,7 @@ Marionette.MonitorDOMRefresh = (function(){
2152
2156
  handleRender(view);
2153
2157
  });
2154
2158
  };
2155
- })();
2159
+ })(document.documentElement);
2156
2160
 
2157
2161
 
2158
2162
  // Marionette.bindEntityEvents & unbindEntityEvents
@@ -2386,6 +2390,7 @@ _.extend(Marionette.Region, {
2386
2390
 
2387
2391
  if (regionConfig.selector) {
2388
2392
  selector = regionConfig.selector;
2393
+ delete regionConfig.selector;
2389
2394
  }
2390
2395
 
2391
2396
  // get the type for the region
@@ -2400,12 +2405,17 @@ _.extend(Marionette.Region, {
2400
2405
 
2401
2406
  if (regionConfig.regionType) {
2402
2407
  RegionType = regionConfig.regionType;
2408
+ delete regionConfig.regionType;
2403
2409
  }
2404
2410
 
2411
+ if (regionIsString || regionIsType) {
2412
+ regionConfig = {};
2413
+ }
2414
+
2415
+ regionConfig.el = selector;
2416
+
2405
2417
  // build the region instance
2406
- var region = new RegionType({
2407
- el: selector
2408
- });
2418
+ var region = new RegionType(regionConfig);
2409
2419
 
2410
2420
  // override the `getEl` function if we have a parentEl
2411
2421
  // this must be overridden to ensure the selector is found
@@ -2491,7 +2501,7 @@ _.extend(Marionette.Region.prototype, Backbone.Events, {
2491
2501
  if (view.close) { view.close(); }
2492
2502
  else if (view.remove) { view.remove(); }
2493
2503
 
2494
- Marionette.triggerMethod.call(this, "close");
2504
+ Marionette.triggerMethod.call(this, "close", view);
2495
2505
 
2496
2506
  delete this.currentView;
2497
2507
  },
@@ -2634,9 +2644,9 @@ Marionette.RegionManager = (function(Marionette){
2634
2644
  //
2635
2645
  // Mix in methods from Underscore, for iteration, and other
2636
2646
  // collection related features.
2637
- var methods = ['forEach', 'each', 'map', 'find', 'detect', 'filter',
2638
- 'select', 'reject', 'every', 'all', 'some', 'any', 'include',
2639
- 'contains', 'invoke', 'toArray', 'first', 'initial', 'rest',
2647
+ var methods = ['forEach', 'each', 'map', 'find', 'detect', 'filter',
2648
+ 'select', 'reject', 'every', 'all', 'some', 'any', 'include',
2649
+ 'contains', 'invoke', 'toArray', 'first', 'initial', 'rest',
2640
2650
  'last', 'without', 'isEmpty', 'pluck'];
2641
2651
 
2642
2652
  _.each(methods, function(method) {
@@ -2661,7 +2671,7 @@ Marionette.TemplateCache = function(templateId){
2661
2671
  };
2662
2672
 
2663
2673
  // TemplateCache object-level methods. Manage the template
2664
- // caches from these method calls instead of creating
2674
+ // caches from these method calls instead of creating
2665
2675
  // your own TemplateCache instances
2666
2676
  _.extend(Marionette.TemplateCache, {
2667
2677
  templateCaches: {},
@@ -2684,7 +2694,7 @@ _.extend(Marionette.TemplateCache, {
2684
2694
  // are specified, clears all templates:
2685
2695
  // `clear()`
2686
2696
  //
2687
- // If arguments are specified, clears each of the
2697
+ // If arguments are specified, clears each of the
2688
2698
  // specified templates from the cache:
2689
2699
  // `clear("#t1", "#t2", "...")`
2690
2700
  clear: function(){
@@ -2724,7 +2734,7 @@ _.extend(Marionette.TemplateCache.prototype, {
2724
2734
  // Load a template from the DOM, by default. Override
2725
2735
  // this method to provide your own template retrieval
2726
2736
  // For asynchronous loading with AMD/RequireJS, consider
2727
- // using a template-loader plugin as described here:
2737
+ // using a template-loader plugin as described here:
2728
2738
  // https://github.com/marionettejs/backbone.marionette/wiki/Using-marionette-with-requirejs
2729
2739
  loadTemplate: function(templateId){
2730
2740
  var template = Marionette.$(templateId).html();
@@ -2793,7 +2803,7 @@ Marionette.View = Backbone.View.extend({
2793
2803
  // this is a backfill since backbone removed the assignment
2794
2804
  // of this.options
2795
2805
  // at some point however this may be removed
2796
- this.options = _.extend({}, this.options, options);
2806
+ this.options = _.extend({}, _.result(this, 'options'), _.isFunction(options) ? options.call(this) : options);
2797
2807
 
2798
2808
  // parses out the @ui DSL for events
2799
2809
  this.events = this.normalizeUIKeys(_.result(this, 'events'));
@@ -3091,7 +3101,8 @@ Marionette.CollectionView = Marionette.View.extend({
3091
3101
  // it's much more performant to insert elements into a document
3092
3102
  // fragment and then insert that document fragment into the page
3093
3103
  initRenderBuffer: function() {
3094
- this.elBuffer = document.createDocumentFragment();
3104
+ this.elBuffer = document.createDocumentFragment();
3105
+ this._bufferedChildren = [];
3095
3106
  },
3096
3107
 
3097
3108
  startBuffering: function() {
@@ -3100,9 +3111,19 @@ Marionette.CollectionView = Marionette.View.extend({
3100
3111
  },
3101
3112
 
3102
3113
  endBuffering: function() {
3114
+ this.isBuffering = false;
3103
3115
  this.appendBuffer(this, this.elBuffer);
3116
+ this._triggerShowBufferedChildren();
3104
3117
  this.initRenderBuffer();
3105
- this.isBuffering = false;
3118
+ },
3119
+
3120
+ _triggerShowBufferedChildren: function () {
3121
+ if (this._isShown) {
3122
+ _.each(this._bufferedChildren, function (child) {
3123
+ Marionette.triggerMethod.call(child, "show");
3124
+ });
3125
+ this._bufferedChildren = [];
3126
+ }
3106
3127
  },
3107
3128
 
3108
3129
  // Configured the initial events that the collection view
@@ -3253,12 +3274,14 @@ Marionette.CollectionView = Marionette.View.extend({
3253
3274
 
3254
3275
  // call the "show" method if the collection view
3255
3276
  // has already been shown
3256
- if (this._isShown){
3277
+ if (this._isShown && !this.isBuffering){
3257
3278
  Marionette.triggerMethod.call(view, "show");
3258
3279
  }
3259
3280
 
3260
3281
  // this view was added
3261
3282
  this.triggerMethod("after:item:added", view);
3283
+
3284
+ return view;
3262
3285
  },
3263
3286
 
3264
3287
  // Set up the child view event forwarding. Uses an "itemview:"
@@ -3270,13 +3293,30 @@ Marionette.CollectionView = Marionette.View.extend({
3270
3293
  // prepending "itemview:" to the event name
3271
3294
  this.listenTo(view, "all", function(){
3272
3295
  var args = slice(arguments);
3273
- args[0] = prefix + ":" + args[0];
3296
+ var rootEvent = args[0];
3297
+ var itemEvents = this.getItemEvents();
3298
+
3299
+ args[0] = prefix + ":" + rootEvent;
3274
3300
  args.splice(1, 0, view);
3275
3301
 
3302
+ // call collectionView itemEvent if defined
3303
+ if (typeof itemEvents !== "undefined" && _.isFunction(itemEvents[rootEvent])) {
3304
+ itemEvents[rootEvent].apply(this, args);
3305
+ }
3306
+
3276
3307
  Marionette.triggerMethod.apply(this, args);
3277
3308
  }, this);
3278
3309
  },
3279
3310
 
3311
+ // returns the value of itemEvents depending on if a function
3312
+ getItemEvents: function() {
3313
+ if (_.isFunction(this.itemEvents)) {
3314
+ return this.itemEvents.call(this);
3315
+ }
3316
+
3317
+ return this.itemEvents;
3318
+ },
3319
+
3280
3320
  // render the item view
3281
3321
  renderItemView: function(view, index) {
3282
3322
  view.render();
@@ -3337,6 +3377,7 @@ Marionette.CollectionView = Marionette.View.extend({
3337
3377
  // in order to reduce the number of inserts into the
3338
3378
  // document, which are expensive.
3339
3379
  collectionView.elBuffer.appendChild(itemView.el);
3380
+ collectionView._bufferedChildren.push(itemView);
3340
3381
  }
3341
3382
  else {
3342
3383
  // If we've already rendered the main collection, just
@@ -3489,6 +3530,7 @@ Marionette.CompositeView = Marionette.CollectionView.extend({
3489
3530
  appendHtml: function(compositeView, itemView, index){
3490
3531
  if (compositeView.isBuffering) {
3491
3532
  compositeView.elBuffer.appendChild(itemView.el);
3533
+ compositeView._bufferedChildren.push(itemView);
3492
3534
  }
3493
3535
  else {
3494
3536
  // If we've already rendered the main collection, just
@@ -3510,7 +3552,7 @@ Marionette.CompositeView = Marionette.CollectionView.extend({
3510
3552
  var itemViewContainer = Marionette.getOption(containerView, "itemViewContainer");
3511
3553
  if (itemViewContainer){
3512
3554
 
3513
- var selector = _.isFunction(itemViewContainer) ? itemViewContainer() : itemViewContainer;
3555
+ var selector = _.isFunction(itemViewContainer) ? itemViewContainer.call(this) : itemViewContainer;
3514
3556
  container = containerView.$(selector);
3515
3557
  if (container.length <= 0) {
3516
3558
  throwError("The specified `itemViewContainer` was not found: " + containerView.itemViewContainer, "ItemViewContainerMissingError");
@@ -3674,7 +3716,7 @@ Marionette.Layout = Marionette.ItemView.extend({
3674
3716
  //
3675
3717
  // Configure an AppRouter with `appRoutes`.
3676
3718
  //
3677
- // App routers can only take one `controller` object.
3719
+ // App routers can only take one `controller` object.
3678
3720
  // It is recommended that you divide your controller
3679
3721
  // objects in to smaller pieces of related functionality
3680
3722
  // and have multiple routers / controllers, instead of
@@ -3686,7 +3728,7 @@ Marionette.AppRouter = Backbone.Router.extend({
3686
3728
 
3687
3729
  constructor: function(options){
3688
3730
  Backbone.Router.prototype.constructor.apply(this, slice(arguments));
3689
-
3731
+
3690
3732
  this.options = options || {};
3691
3733
 
3692
3734
  var appRoutes = Marionette.getOption(this, "appRoutes");
@@ -4048,7 +4090,7 @@ _.extend(Marionette.Module, {
4048
4090
  })(this, Backbone, _);
4049
4091
 
4050
4092
  /*!
4051
- * Tableling v0.0.23
4093
+ * Tableling v0.0.24
4052
4094
  * Copyright (c) 2012-2014 Simon Oulevay (Alpha Hydrae) <hydrae.alpha@gmail.com>
4053
4095
  * Distributed under MIT license
4054
4096
  * https://github.com/AlphaHydrae/tableling
@@ -4056,729 +4098,752 @@ _.extend(Marionette.Module, {
4056
4098
  Backbone.Tableling = Tableling = (function(Backbone, _, $){
4057
4099
 
4058
4100
  var Tableling = {
4059
- version : "0.0.23"
4101
+ version : "0.0.24"
4060
4102
  };
4061
4103
 
4062
- // Tableling
4063
- // ---------
4064
- //
4065
- // A tableling table is a Marionette layout which fetches data
4066
- // from a Backbone collection. It is controlled with an EventAggregator.
4067
- Tableling.Table = Backbone.Marionette.Layout.extend({
4068
-
4069
- className: 'tableling',
4070
-
4071
- // Default table options can be overriden by subclasses.
4072
- config : {
4073
- page : 1
4074
- },
4075
-
4076
- initialize : function(options) {
4077
- options = options || {};
4078
-
4079
- this.collection = options.collection;
4080
-
4081
- // Table options can also be overriden for each instance at construction.
4082
- this.config = _.extend(_.clone(this.config || {}), _.result(options, 'config') || {});
4083
-
4084
- // We use an event aggregator to manage the layout and its components.
4085
- // You can use your own by passing a `vent` option.
4086
- this.vent = options.vent || new Backbone.Wreqr.EventAggregator();
4087
-
4088
- this.fetchOptions = _.extend(_.clone(this.fetchOptions || {}), _.result(options, 'fetchOptions') || {});
4089
- this.autoUpdate = typeof(options.autoUpdate) != 'undefined' ? options.autoUpdate : true;
4090
-
4091
- // Components should trigger the `table:update` event to update
4092
- // the table (e.g. change page size, sort) and fetch the new data.
4093
- this.vent.on('table:update', this.onUpdate, this);
4094
-
4095
- this.on('item:rendered', this.setup, this);
4096
- },
4097
-
4098
- // Called once rendering is complete. By default, it updates the table.
4099
- setup : function() {
4100
- this.ventTrigger('table:setup', this.config);
4101
- if (this.autoUpdate) {
4102
- this.ventTrigger('table:update');
4103
- }
4104
- },
4105
-
4106
- // Subclasses must return the Backbone.Collection used to fetch data.
4107
- getCollection : function() {
4108
- return this.collection;
4109
- },
4110
-
4111
- // ### Refreshing the table
4112
- update : function(config, options) {
4113
- this.ventTrigger('table:update', config, options);
4114
- },
4115
-
4116
- onUpdate : function(config, options) {
4117
-
4118
- _.each(config || {}, _.bind(this.updateValue, this));
4119
-
4120
- // Set the `refresh` option to false to update the table configuration
4121
- // without refreshing.
4122
- if (!options || typeof(options.refresh) == 'undefined' || options.refresh) {
4123
- this.refresh();
4124
- }
4125
- },
4126
-
4127
- updateValue : function(value, key) {
4128
- if (value && value.toString().length) {
4129
- this.config[key] = value;
4130
- } else {
4131
- // Blank values are deleted to avoid sending them in ajax requests.
4132
- delete this.config[key];
4133
- }
4134
- },
4135
-
4136
- refresh : function() {
4137
-
4138
- // You can provide `fetchOptions` to add properties to the
4139
- // fetch request.
4140
- //
4141
- // var MyTable = Tableling.Table.extend({
4142
- // fetchOptions : {
4143
- // type : 'POST' // fetch data with POST
4144
- // }
4145
- // });
4146
- //
4147
- // // You can also override for each instance.
4148
- // new MyTable({
4149
- // fetchOptions : {
4150
- // type : 'GET'
4151
- // }
4152
- // });
4153
- var options = _.clone(this.fetchOptions);
4154
- options.data = this.requestData();
4155
- options.success = _.bind(this.processResponse, this);
4156
- options.reset = true;
4157
-
4158
- // `table:refreshing` is triggered every time new data is being fetched.
4159
- // The first argument is the request data.
4160
- this.ventTrigger('table:refreshing', options.data);
4161
-
4162
- this.getCollection().fetch(options);
4163
- },
4164
-
4165
- // ### Request
4166
- requestData : function() {
4167
- return this.config;
4168
- },
4169
-
4170
- // ### Response
4171
- processResponse : function(collection, response) {
4172
-
4173
- this.config.length = collection.length;
4174
-
4175
- // Tableling expects the response from a fetch to have a `total` property
4176
- // which is the total number of items (not just in the current page).
4177
- this.config.total = response.total;
4178
-
4179
- // The server may override the `page` property, for example if the
4180
- // requested page was outside the range of available pages.
4181
- if (response.page) {
4182
- this.config.page = response.page;
4183
- }
4184
-
4185
- // `tableling:refreshed` is triggered after every refresh. The first argument
4186
- // is the current table configuration with the following additional meta data:
4187
- //
4188
- // * `total` - the total number of items
4189
- // * `length` - the number of items in the current page
4190
- this.ventTrigger('table:refreshed', this.config);
4191
- },
4192
-
4193
- // Triggers an event in the event aggregator. If `Tableling.debug` is set, it also
4194
- // logs the event and its arguments.
4195
- ventTrigger : function() {
4196
-
4197
- var args = Array.prototype.slice.call(arguments);
4198
- if (Tableling.debug) {
4199
- console.log(_.first(args) + ' - ' + JSON.stringify(args.slice(1)));
4200
- }
4201
-
4202
- this.vent.trigger.apply(this.vent, args);
4104
+ // Tableling
4105
+ // ---------
4106
+ //
4107
+ // A tableling table is a Marionette layout which fetches data
4108
+ // from a Backbone collection. It is controlled with an EventAggregator.
4109
+ Tableling.Table = Backbone.Marionette.Layout.extend({
4110
+
4111
+ className: 'tableling',
4112
+
4113
+ // Default table options can be overriden by subclasses.
4114
+ config: {
4115
+ page: 1
4116
+ },
4117
+
4118
+ initialize: function(options) {
4119
+ options = options || {};
4120
+
4121
+ this.collection = options.collection;
4122
+
4123
+ // Table options can also be overriden for each instance at construction.
4124
+ this.config = _.extend(_.clone(this.config || {}), _.result(options, 'config') || {});
4125
+
4126
+ // We use an event aggregator to manage the layout and its components.
4127
+ // You can use your own by passing a `vent` option.
4128
+ this.vent = options.vent || new Backbone.Wreqr.EventAggregator();
4129
+
4130
+ this.fetchOptions = _.extend(_.clone(this.fetchOptions || {}), _.result(options, 'fetchOptions') || {});
4131
+
4132
+ if (typeof(options.autoUpdate) != 'undefined') {
4133
+ this.autoUpdate = options.autoUpdate;
4203
4134
  }
4204
- });
4205
-
4206
- // Tableling.Collection
4207
- // --------------------
4208
- //
4209
- // Tableling expects fetch responses to have a `total` property in addition
4210
- // to the model data. You can extend this Backbone.Collection subclass which
4211
- // expects the following response format:
4212
- //
4213
- // {
4214
- // "total": 12,
4215
- // "data": [
4216
- // { /* ... model data ... */ },
4217
- // { /* ... model data ... */ }
4218
- // ]
4219
- // }
4220
- Tableling.Collection = Backbone.Collection.extend({
4221
-
4222
- parse : function(response) {
4223
- return response.data;
4135
+
4136
+ if (typeof(this.autoUpdate) == 'undefined') {
4137
+ this.autoUpdate = true;
4224
4138
  }
4225
- });
4226
-
4227
- // Implementations
4228
- // ---------------
4229
- //
4230
- // <a href="tableling.bootstrap.html">tableling.bootstrap</a> provides views styled
4231
- // with [Twitter Bootstrap](http://twitter.github.com/bootstrap/) classes.
4232
-
4233
- // Tableling.Modular
4234
- // -----------------
4235
- //
4236
- // Tableling subclass which splits functionality into *modules*
4237
- // and handles rendering.
4238
- Tableling.Modular = Tableling.Table.extend({
4239
-
4240
- // The list of module names must be specified by subclasses.
4241
- modules : [],
4242
-
4243
- // Modules are set up after rendering, before refreshing.
4244
- setup : function() {
4245
-
4246
- this.moduleViews = {};
4247
- _.each(this.modules, _.bind(this.setupModule, this));
4248
-
4249
- Tableling.Table.prototype.setup.call(this);
4250
- },
4251
-
4252
- // ### Modules
4253
- // Each module is identified by a name, for example `pageSize`.
4254
- setupModule : function(name) {
4255
-
4256
- // The layout must have a region named after the module, e.g. `pageSizeRegion`.
4257
- var region = name + 'Region';
4258
-
4259
- // It must have a view class, e.g. `pageSizeView`, which will be shown into
4260
- // the region.
4261
- var viewClass = this[name + 'View'];
4262
-
4263
- // When instantiated, the view class will be passed the event
4264
- // aggregator as the `vent` option. Additional options can be
4265
- // given named after the view class, e.g. `pageSizeViewOptions`.
4266
- var options = _.extend(this.getModuleOptions(name), { vent: this.vent });
4267
-
4268
- var view = new viewClass(options);
4269
-
4270
- // Module view instances are stored by name in the `moduleViews` property
4271
- // for future reference.
4272
- this.moduleViews[name] = view;
4273
-
4274
- this[region].show(view);
4275
- return view;
4276
- },
4277
-
4278
- // By default the collection is the one given at construction.
4279
- // Otherwise, a modular table expects a `table` module which
4280
- // should have a collection (e.g. a Marionette CompositeView or
4281
- // CollectionView). If your subclass does not have either, it
4282
- // should override this method to return the Backbone.Collection
4283
- // used to fetch table data.
4284
- getCollection : function() {
4285
- return this.collection || (this.moduleViews && this.moduleViews.table ? this.moduleViews.table.collection : undefined);
4286
- },
4287
-
4288
- getModuleOptions : function(name) {
4289
- var options = this[name + 'ViewOptions'] || {};
4290
- options = typeof(options) == 'function' ? options.call(this) : options;
4291
- return name == 'table' ? _.defaults(options, { collection : this.collection }) : options;
4139
+
4140
+ // Components should trigger the `table:update` event to update
4141
+ // the table (e.g. change page size, sort) and fetch the new data.
4142
+ this.vent.on('table:update', this.onUpdate, this);
4143
+
4144
+ this.on('item:rendered', this.setup, this);
4145
+
4146
+ if (typeof(this.initializeTable) == 'function') {
4147
+ this.initializeTable(options);
4292
4148
  }
4293
- });
4294
-
4295
- // ### Example
4296
- // This is how a `PageSizeView` module might be registered in a subclass:
4297
- //
4298
- // var MyTable = Tableling.Modular.extend({
4299
- //
4300
- // modules : [ 'pageSize' ],
4301
- //
4302
- // pageSizeView : PageSizeView,
4303
- // pageSizeViewOptions : {
4304
- // itemView : PageSizeItem
4305
- // },
4306
- //
4307
- // regions : {
4308
- // pageSizeRegion : '.pageSize'
4309
- // }
4310
- // });
4311
-
4312
- // Tableling.Module
4313
- // ----------------
4314
- //
4315
- // A module is an item view that is automatically bound to the table's
4316
- // event aggregator.
4317
- Tableling.Module = Backbone.Marionette.ItemView.extend({
4318
-
4319
- i18n : {},
4320
- templateHelpers : function() {
4321
- return this.i18n;
4322
- },
4323
-
4324
- initialize : function(options) {
4325
-
4326
- this.vent = options.vent;
4327
-
4328
- // The `setup` method of the view is called when the table
4329
- // is first set up.
4330
- this.vent.on('table:setup', this.setup, this);
4331
-
4332
- // The `refresh` method of the view is called every time the table
4333
- // is refreshed.
4334
- this.vent.on('table:refreshed', this.refresh, this);
4335
-
4336
- this.i18n = _.clone(options.i18n || this.i18n);
4337
- },
4338
-
4339
- // Call `update` to trigger an update of the table.
4340
- update : function() {
4341
- this.vent.trigger('table:update', this.config());
4342
- },
4343
-
4344
- // Implementations should override this to set initial values.
4345
- setup : function(config) {
4346
- },
4347
-
4348
- // Implementations should override this to stay up to date with
4349
- // the table state.
4350
- refresh : function(config) {
4351
- },
4352
-
4353
- // New table configuration to be sent on updates. For example,
4354
- // a page size view might update the `pageSize` property.
4355
- config : function() {
4356
- return {};
4149
+ },
4150
+
4151
+ // Called once rendering is complete. By default, it updates the table.
4152
+ setup: function() {
4153
+ this.ventTrigger('table:setup', this.config);
4154
+ if (this.autoUpdate) {
4155
+ this.ventTrigger('table:update');
4357
4156
  }
4358
- });
4359
-
4360
- // Tableling.FieldModule
4361
- // ---------------------
4362
- //
4363
- // A basic module with a single form field. It comes with sensible
4364
- // defaults and only requires a `name` and a `template` parameter.
4365
- Tableling.FieldModule = Tableling.Module.extend({
4366
-
4367
- // TODO: check name
4368
-
4369
- initialize : function(options) {
4370
-
4371
- Tableling.Module.prototype.initialize.call(this, options);
4372
-
4373
- if (!this.ui) {
4374
- this.ui = {};
4375
- }
4376
- // The name attribute of the form field is the same as the
4377
- // module's, e.g. `pageSize`.
4378
- this.ui.field = '[name="' + this.name + '"]';
4379
-
4380
- if (!this.events) {
4381
- this.events = {};
4382
- }
4383
- this.events.submit = 'onSubmit';
4384
- this.events['change [name="' + this.name + '"]'] = 'update';
4385
- },
4386
-
4387
- setup : function(config) {
4388
- this.setupValue(config[this.name]);
4389
- this.vent.trigger('table:update', this.config(), { refresh : false });
4390
- },
4391
-
4392
- setupValue : function(value) {
4393
- this.ui.field.val(value);
4394
- },
4395
-
4396
- // The table property updated is the one with the same name as the module.
4397
- config : function() {
4398
- var config = {};
4399
- config[this.name] = this.ui.field.val();
4400
- return config;
4401
- },
4402
-
4403
- onSubmit : function(e) {
4404
- e.preventDefault();
4405
- return false;
4157
+ },
4158
+
4159
+ // Subclasses must return the Backbone.Collection used to fetch data.
4160
+ getCollection: function() {
4161
+ return this.collection;
4162
+ },
4163
+
4164
+ // ### Refreshing the table
4165
+ update: function(config, options) {
4166
+ this.ventTrigger('table:update', config, options);
4167
+ },
4168
+
4169
+ onUpdate: function(config, options) {
4170
+
4171
+ _.each(config || {}, _.bind(this.updateValue, this));
4172
+
4173
+ // Set the `refresh` option to false to update the table configuration
4174
+ // without refreshing.
4175
+ if (!options || typeof(options.refresh) == 'undefined' || options.refresh) {
4176
+ this.refresh();
4406
4177
  }
4407
- });
4408
-
4409
- // This is how a `PageSizeView` module might be implemented:
4410
- //
4411
- // var html = '<input type="text" name="pageSize" />';
4412
- //
4413
- // var PageSizeView = Tableling.FieldModule.extend({
4414
- // name : 'pageSize'
4415
- // template : _.template(html)
4416
- // });
4417
- //
4418
- // When the value of the input field changes, the event aggregator will
4419
- // receive a `tableling:update` event with the `pageSize` property set
4420
- // to that value.
4421
-
4422
- Tableling.Plain = {};
4423
-
4424
- Tableling.Plain.Table = Tableling.Modular.extend({
4425
-
4426
- className: 'tableling',
4427
- modules : [ 'table', 'pageSize', 'quickSearch', 'info', 'page' ],
4428
- 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>'),
4429
-
4430
- regions : {
4431
- tableRegion : '.table',
4432
- pageSizeRegion : '.pageSize',
4433
- quickSearchRegion : '.quickSearch',
4434
- infoRegion : '.info',
4435
- pageRegion : '.page'
4178
+ },
4179
+
4180
+ updateValue: function(value, key) {
4181
+ if (value && value.toString().length) {
4182
+ this.config[key] = value;
4183
+ } else {
4184
+ // Blank values are deleted to avoid sending them in ajax requests.
4185
+ delete this.config[key];
4436
4186
  }
4437
- });
4438
-
4439
- Tableling.Plain.TableView = Backbone.Marionette.CompositeView.extend({
4440
-
4441
- events : {
4442
- 'click thead th.sorting' : 'updateSort',
4443
- 'click thead th.sorting-asc' : 'updateSort',
4444
- 'click thead th.sorting-desc' : 'updateSort'
4445
- },
4446
-
4447
- initialize : function(options) {
4448
- // TODO: add auto-sort
4449
- this.vent = options.vent;
4450
- this.sort = [];
4451
- this.vent.on('table:setup', this.setSort, this);
4452
- this.vent.on('table:refreshed', this.setSort, this);
4453
- },
4454
-
4455
- updateSort : function(ev) {
4456
-
4457
- var el = $(ev.currentTarget);
4458
- if (!(el.hasClass('sorting') || el.hasClass('sorting-asc') || el.hasClass('sorting-desc'))) {
4459
- return;
4460
- }
4461
-
4462
- var field = this.fieldName(el);
4463
-
4464
- if (ev.shiftKey || this.sort.length == 1) {
4465
-
4466
- var index = -1;
4467
- _.find(this.sort, function(item, i) {
4468
- if (item.split(' ')[0] == field) {
4469
- index = i;
4470
- }
4471
- });
4472
-
4473
- if (index >= 0) {
4474
-
4475
- var parts = this.sort[index].split(' ');
4476
- this.sort[index] = parts[0] + ' ' + (parts[1] == 'asc' ? 'desc' : 'asc');
4477
- this.showSort();
4478
- return this.vent.trigger('table:update', this.config());
4479
- }
4480
- }
4481
-
4482
- if (!ev.shiftKey) {
4483
- this.sort.length = 0;
4484
- }
4485
-
4486
- this.sort.push(field + ' asc');
4487
-
4488
- this.showSort();
4489
-
4490
- this.vent.trigger('table:update', this.config());
4491
- },
4492
-
4493
- setSort : function(config) {
4494
- if (config && config.sort) {
4495
- this.sort = config.sort.slice(0);
4496
- this.showSort();
4497
- }
4498
- },
4499
-
4500
- showSort : function() {
4501
-
4502
- this.$el.find('thead th.sorting, thead th.sorting-asc, thead th.sorting-desc').removeClass('sorting sorting-asc sorting-desc').addClass('sorting');
4503
-
4504
- for (var i = 0; i < this.sort.length; i++) {
4505
-
4506
- var parts = this.sort[i].split(' ');
4507
- var name = parts[0];
4508
- var direction = parts[1];
4509
-
4510
- field = this.$el.find('thead [data-field="' + name + '"]');
4511
- if (!field.length) {
4512
- field = this.$el.find('thead th:contains("' + name + '")');
4513
- }
4514
-
4515
- if (field.length) {
4516
- field.removeClass('sorting').addClass(direction == 'desc' ? 'sorting-desc' : 'sorting-asc');
4517
- }
4518
- }
4519
- },
4520
-
4521
- config : function() {
4522
- return {
4523
- page : 1,
4524
- sort : this.sortConfig()
4525
- };
4526
- },
4527
-
4528
- sortConfig : function() {
4529
- return this.sort.length ? this.sort : null;
4530
- },
4531
-
4532
- fieldName : function(el) {
4533
- return el.data('field') || el.text();
4187
+ },
4188
+
4189
+ refresh: function() {
4190
+
4191
+ // You can provide `fetchOptions` to add properties to the
4192
+ // fetch request.
4193
+ //
4194
+ // var MyTable = Tableling.Table.extend({
4195
+ // fetchOptions: {
4196
+ // type: 'POST' // fetch data with POST
4197
+ // }
4198
+ // });
4199
+ //
4200
+ // // You can also override for each instance.
4201
+ // new MyTable({
4202
+ // fetchOptions: {
4203
+ // type: 'GET'
4204
+ // }
4205
+ // });
4206
+ var options = _.clone(this.fetchOptions);
4207
+ options.data = this.requestData();
4208
+ options.success = _.bind(this.processResponse, this);
4209
+ options.reset = true;
4210
+
4211
+ // `table:refreshing` is triggered every time new data is being fetched.
4212
+ // The first argument is the request data.
4213
+ this.ventTrigger('table:refreshing', options.data);
4214
+
4215
+ this.getCollection().fetch(options);
4216
+ },
4217
+
4218
+ // ### Request
4219
+ requestData: function() {
4220
+ return this.config;
4221
+ },
4222
+
4223
+ // ### Response
4224
+ processResponse: function(collection, response) {
4225
+
4226
+ this.config.length = collection.length;
4227
+
4228
+ // Tableling expects the response from a fetch to have a `total` property
4229
+ // which is the total number of items (not just in the current page).
4230
+ this.config.total = response.total;
4231
+
4232
+ // The server may override the `page` property, for example if the
4233
+ // requested page was outside the range of available pages.
4234
+ if (response.page) {
4235
+ this.config.page = response.page;
4534
4236
  }
4535
- });
4536
-
4537
- Tableling.Plain.PageSizeView = Tableling.Plain.Table.prototype.pageSizeView = Tableling.FieldModule.extend({
4538
-
4539
- // TODO: update current page intelligently
4540
- name : 'pageSize',
4541
- template : function(data) {
4542
- return _.template('<select name="pageSize" /> <%- entries %>', data);
4543
- },
4544
-
4545
- i18n : {
4546
- entries : 'entries per page'
4547
- },
4548
- sizes : [ 10, 15, 20, 25, 50 ],
4549
-
4550
- ui : {
4551
- field : 'select'
4552
- },
4553
-
4554
- initialize : function(options) {
4555
- Tableling.FieldModule.prototype.initialize.call(this, options);
4556
- this.sizes = _.clone(options.sizes || this.sizes);
4557
- },
4558
-
4559
- onRender : function() {
4560
- this.ui.field.empty();
4561
- _.each(this.sizes, _.bind(this.addSize, this));
4562
- },
4563
-
4564
- addSize : function(size) {
4565
- $('<option />').text(size).appendTo(this.ui.field);
4566
- },
4567
-
4568
- setupValue : function(value) {
4569
- if (value) {
4570
- Tableling.FieldModule.prototype.setupValue.apply(this, Array.prototype.slice.call(arguments));
4571
- }
4572
- },
4573
-
4574
- config : function() {
4575
- var config = Tableling.FieldModule.prototype.config.call(this);
4576
- config.page = 1;
4577
- return config;
4237
+
4238
+ // `tableling:refreshed` is triggered after every refresh. The first argument
4239
+ // is the current table configuration with the following additional meta data:
4240
+ //
4241
+ // * `total` - the total number of items
4242
+ // * `length` - the number of items in the current page
4243
+ this.ventTrigger('table:refreshed', this.config);
4244
+ },
4245
+
4246
+ // Triggers an event in the event aggregator. If `Tableling.debug` is set, it also
4247
+ // logs the event and its arguments.
4248
+ ventTrigger: function() {
4249
+
4250
+ var args = Array.prototype.slice.call(arguments);
4251
+ if (Tableling.debug) {
4252
+ console.log(_.first(args) + ' - ' + JSON.stringify(args.slice(1)));
4578
4253
  }
4579
- });
4580
-
4581
- Tableling.Plain.QuickSearchView = Tableling.Plain.Table.prototype.quickSearchView = Tableling.FieldModule.extend({
4582
-
4583
- name : 'quickSearch',
4584
- template : function(data) {
4585
- return _.template('<input type="text" name="quickSearch" placeholder="<%- quickSearch %>" />', data);
4586
- },
4587
-
4588
- i18n : {
4589
- quickSearch : 'Quick search...'
4590
- },
4591
-
4592
- config : function() {
4593
- var config = Tableling.FieldModule.prototype.config.call(this);
4594
- config.page = 1;
4595
- return config;
4254
+
4255
+ this.vent.trigger.apply(this.vent, args);
4256
+ }
4257
+ });
4258
+
4259
+ // Tableling.Collection
4260
+ // --------------------
4261
+ //
4262
+ // Tableling expects fetch responses to have a `total` property in addition
4263
+ // to the model data. You can extend this Backbone.Collection subclass which
4264
+ // expects the following response format:
4265
+ //
4266
+ // {
4267
+ // "total": 12,
4268
+ // "data": [
4269
+ // { /* ... model data ... */ },
4270
+ // { /* ... model data ... */ }
4271
+ // ]
4272
+ // }
4273
+ Tableling.Collection = Backbone.Collection.extend({
4274
+
4275
+ parse: function(response) {
4276
+ return response.data;
4277
+ }
4278
+ });
4279
+
4280
+ // Implementations
4281
+ // ---------------
4282
+ //
4283
+ // <a href="tableling.bootstrap.html">tableling.bootstrap</a> provides views styled
4284
+ // with [Twitter Bootstrap](http://twitter.github.com/bootstrap/) classes.
4285
+
4286
+ // Tableling.Modular
4287
+ // -----------------
4288
+ //
4289
+ // Tableling subclass which splits functionality into *modules*
4290
+ // and handles rendering.
4291
+ Tableling.Modular = Tableling.Table.extend({
4292
+
4293
+ // The list of module names must be specified by subclasses.
4294
+ modules: [],
4295
+
4296
+ // Modules are set up after rendering, before refreshing.
4297
+ setup: function() {
4298
+
4299
+ this.moduleViews = {};
4300
+ _.each(this.modules, this.setupModule, this);
4301
+
4302
+ Tableling.Table.prototype.setup.call(this);
4303
+ },
4304
+
4305
+ // ### Modules
4306
+ // Each module is identified by a name, for example `pageSize`.
4307
+ setupModule: function(name) {
4308
+
4309
+ // The layout must have a region named after the module, e.g. `pageSizeRegion`.
4310
+ var region = name + 'Region';
4311
+
4312
+ // It must have a view class, e.g. `pageSizeView`, which will be shown into
4313
+ // the region.
4314
+ var viewClass = this[name + 'View'];
4315
+
4316
+ // When instantiated, the view class will be passed the event
4317
+ // aggregator as the `vent` option. Additional options can be
4318
+ // given named after the view class, e.g. `pageSizeViewOptions`.
4319
+ var options = _.extend(this.getModuleOptions(name), { vent: this.vent });
4320
+
4321
+ // The collection is also passed to view classes.
4322
+ _.defaults(options, { collection: this.getCollection() });
4323
+
4324
+ var view = new viewClass(options);
4325
+
4326
+ // Module view instances are stored by name in the `moduleViews` property
4327
+ // for future reference.
4328
+ this.moduleViews[name] = view;
4329
+
4330
+ this[region].show(view);
4331
+ return view;
4332
+ },
4333
+
4334
+ // By default the collection is the one given at construction.
4335
+ // Otherwise, a modular table expects a `table` module which
4336
+ // should have a collection (e.g. a Marionette CompositeView or
4337
+ // CollectionView). If your subclass does not have either, it
4338
+ // should override this method to return the Backbone.Collection
4339
+ // used to fetch table data.
4340
+ getCollection: function() {
4341
+ return this.collection || (this.moduleViews && this.moduleViews.table ? this.moduleViews.table.collection : undefined);
4342
+ },
4343
+
4344
+ getModuleOptions: function(name) {
4345
+ return _.result(this, name + 'ViewOptions') || {};
4346
+ }
4347
+ });
4348
+
4349
+ // ### Example
4350
+ // This is how a `PageSizeView` module might be registered in a subclass:
4351
+ //
4352
+ // var MyTable = Tableling.Modular.extend({
4353
+ //
4354
+ // modules: [ 'pageSize' ],
4355
+ //
4356
+ // pageSizeView: PageSizeView,
4357
+ // pageSizeViewOptions: {
4358
+ // itemView: PageSizeItem
4359
+ // },
4360
+ //
4361
+ // regions: {
4362
+ // pageSizeRegion: '.pageSize'
4363
+ // }
4364
+ // });
4365
+
4366
+ // Tableling.Module
4367
+ // ----------------
4368
+ //
4369
+ // A module is an item view that is automatically bound to the table's
4370
+ // event aggregator.
4371
+ Tableling.Module = Backbone.Marionette.ItemView.extend({
4372
+
4373
+ i18n: {},
4374
+ templateHelpers: function() {
4375
+ return this.i18n;
4376
+ },
4377
+
4378
+ initialize: function(options) {
4379
+
4380
+ this.vent = options.vent;
4381
+
4382
+ // The `setup` method of the view is called when the table
4383
+ // is first set up.
4384
+ this.vent.on('table:setup', this.setup, this);
4385
+
4386
+ // The `refresh` method of the view is called every time the table
4387
+ // is refreshed.
4388
+ this.vent.on('table:refreshed', this.refresh, this);
4389
+
4390
+ this.i18n = _.clone(options.i18n || this.i18n);
4391
+
4392
+ if (typeof(this.initializeModule) == 'function') {
4393
+ this.initializeModule(options);
4596
4394
  }
4597
- });
4598
-
4599
- Tableling.Plain.InfoView = Tableling.Plain.Table.prototype.infoView = Tableling.Module.extend({
4600
-
4601
- template : function(data) {
4602
- return _.template(data.template, {
4603
- first : '<span class="first">0</span>',
4604
- last : '<span class="last">0</span>',
4605
- total : '<span class="total">0</span>'
4395
+ },
4396
+
4397
+ // Call `update` to trigger an update of the table.
4398
+ update: function() {
4399
+ this.vent.trigger('table:update', this.config());
4400
+ },
4401
+
4402
+ // Implementations should override this to set initial values.
4403
+ setup: function(config) {
4404
+ },
4405
+
4406
+ // Implementations should override this to stay up to date with
4407
+ // the table state.
4408
+ refresh: function(config) {
4409
+ },
4410
+
4411
+ // New table configuration to be sent on updates. For example,
4412
+ // a page size view might update the `pageSize` property.
4413
+ config: function() {
4414
+ return {};
4415
+ }
4416
+ });
4417
+
4418
+ // Tableling.FieldModule
4419
+ // ---------------------
4420
+ //
4421
+ // A basic module with a single form field. It comes with sensible
4422
+ // defaults and only requires a `name` and a `template` parameter.
4423
+ Tableling.FieldModule = Tableling.Module.extend({
4424
+
4425
+ initialize: function(options) {
4426
+ if (!_.isString(this.name)) {
4427
+ throw new Error("Tableling module must have a name property.");
4428
+ }
4429
+
4430
+ if (!this.ui) {
4431
+ this.ui = {};
4432
+ }
4433
+ // The name attribute of the form field is the same as the
4434
+ // module's, e.g. `pageSize`.
4435
+ this.ui.field = '[name="' + this.name + '"]';
4436
+
4437
+ if (!this.events) {
4438
+ this.events = {};
4439
+ }
4440
+ this.events.submit = 'onSubmit';
4441
+ this.events['change [name="' + this.name + '"]'] = 'update';
4442
+
4443
+ Tableling.Module.prototype.initialize.call(this, options);
4444
+ },
4445
+
4446
+ setup: function(config) {
4447
+ this.setupValue(config[this.name]);
4448
+ this.vent.trigger('table:update', this.config(), { refresh: false });
4449
+ },
4450
+
4451
+ setupValue: function(value) {
4452
+ this.ui.field.val(value);
4453
+ },
4454
+
4455
+ // The table property updated is the one with the same name as the module.
4456
+ config: function() {
4457
+ var config = {};
4458
+ config[this.name] = this.ui.field.val();
4459
+ return config;
4460
+ },
4461
+
4462
+ onSubmit: function(e) {
4463
+ e.preventDefault();
4464
+ return false;
4465
+ }
4466
+ });
4467
+
4468
+ // This is how a `PageSizeView` module might be implemented:
4469
+ //
4470
+ // var html = '<input type="text" name="pageSize" />';
4471
+ //
4472
+ // var PageSizeView = Tableling.FieldModule.extend({
4473
+ // name: 'pageSize'
4474
+ // template: _.template(html)
4475
+ // });
4476
+ //
4477
+ // When the value of the input field changes, the event aggregator will
4478
+ // receive a `tableling:update` event with the `pageSize` property set
4479
+ // to that value.
4480
+
4481
+ Tableling.Plain = {};
4482
+
4483
+ Tableling.Plain.Table = Tableling.Modular.extend({
4484
+
4485
+ className: 'tableling',
4486
+ modules: [ 'table', 'pageSize', 'quickSearch', 'info', 'page' ],
4487
+ 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>'),
4488
+
4489
+ regions: {
4490
+ tableRegion: '.table',
4491
+ pageSizeRegion: '.pageSize',
4492
+ quickSearchRegion: '.quickSearch',
4493
+ infoRegion: '.info',
4494
+ pageRegion: '.page'
4495
+ }
4496
+ });
4497
+
4498
+ // TODO: make table view a module
4499
+ Tableling.Plain.TableView = Backbone.Marionette.CompositeView.extend({
4500
+
4501
+ moduleEvents: {
4502
+ 'click thead th.sorting': 'updateSort',
4503
+ 'click thead th.sorting-asc': 'updateSort',
4504
+ 'click thead th.sorting-desc': 'updateSort'
4505
+ },
4506
+
4507
+ // TODO: add auto-sort
4508
+ initialize: function(options) {
4509
+
4510
+ this.vent = options.vent;
4511
+ this.sort = [];
4512
+ this.vent.on('table:setup', this.setSort, this);
4513
+ this.vent.on('table:refreshed', this.setSort, this);
4514
+ this.events = _.extend({}, this.events || {}, this.moduleEvents);
4515
+
4516
+ if (typeof(this.initializeModule) == 'function') {
4517
+ this.initializeModule(options);
4518
+ }
4519
+ },
4520
+
4521
+ updateSort: function(ev) {
4522
+
4523
+ var el = $(ev.currentTarget);
4524
+ if (!(el.hasClass('sorting') || el.hasClass('sorting-asc') || el.hasClass('sorting-desc'))) {
4525
+ return;
4526
+ }
4527
+
4528
+ var field = this.fieldName(el);
4529
+
4530
+ if (ev.shiftKey || this.sort.length == 1) {
4531
+
4532
+ var index = -1;
4533
+ _.find(this.sort, function(item, i) {
4534
+ if (item.split(' ')[0] == field) {
4535
+ index = i;
4536
+ }
4606
4537
  });
4607
- },
4608
-
4609
- i18n : {
4610
- template : 'Showing <%= first %> to <%= last %> of <%= total %> entries'
4611
- },
4612
-
4613
- ui : {
4614
- first: '.first',
4615
- last: '.last',
4616
- total: '.total'
4617
- },
4618
-
4619
- refresh : function(data) {
4620
- if (data) {
4621
- this.ui.first.text(this.firstRecord(data));
4622
- this.ui.last.text(this.lastRecord(data));
4623
- this.ui.total.text(data.total);
4538
+
4539
+ if (index >= 0) {
4540
+
4541
+ var parts = this.sort[index].split(' ');
4542
+ this.sort[index] = parts[0] + ' ' + (parts[1] == 'asc' ? 'desc' : 'asc');
4543
+ this.showSort();
4544
+ return this.vent.trigger('table:update', this.config());
4624
4545
  }
4625
- },
4626
-
4627
- firstRecord : function(data) {
4628
- return data.length ? ((data.page || 1) - 1) * data.pageSize + 1 : 0;
4629
- },
4630
-
4631
- lastRecord : function(data) {
4632
- return data.length ? this.firstRecord(data) + data.length - 1 : 0;
4633
4546
  }
4634
- });
4635
-
4636
- Tableling.Plain.PageView = Tableling.Plain.Table.prototype.pageView = Tableling.Module.extend({
4547
+
4548
+ if (!ev.shiftKey) {
4549
+ this.sort.length = 0;
4550
+ }
4551
+
4552
+ this.sort.push(field + ' asc');
4553
+
4554
+ this.showSort();
4555
+
4556
+ this.vent.trigger('table:update', this.config());
4557
+ },
4558
+
4559
+ setSort: function(config) {
4560
+ if (config && config.sort) {
4561
+ this.sort = config.sort.slice(0);
4562
+ this.showSort();
4563
+ }
4564
+ },
4565
+
4566
+ showSort: function() {
4567
+
4568
+ this.$el.find('thead th.sorting, thead th.sorting-asc, thead th.sorting-desc').removeClass('sorting sorting-asc sorting-desc').addClass('sorting');
4569
+
4570
+ for (var i = 0; i < this.sort.length; i++) {
4571
+
4572
+ var parts = this.sort[i].split(' ');
4573
+ var name = parts[0];
4574
+ var direction = parts[1];
4637
4575
 
4638
- 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>'),
4639
- pageTemplate : _.template('<li class="page"><a href="#"><%- number %></a></li>'),
4640
-
4641
- ui : {
4642
- first : '.first',
4643
- previous : '.previous',
4644
- next : '.next',
4645
- last : '.last'
4646
- },
4647
-
4648
- events : {
4649
- 'click .first:not(.disabled)' : 'goToFirstPage',
4650
- 'click .previous:not(.disabled)' : 'goToPreviousPage',
4651
- 'click .page:not(.disabled)' : 'goToPage',
4652
- 'click .next:not(.disabled)' : 'goToNextPage',
4653
- 'click .last:not(.disabled)' : 'goToLastPage'
4654
- },
4655
-
4656
- refresh : function(data) {
4657
- this.$el.find('.page').remove();
4658
- if (!data || !data.length) {
4659
- this.ui.first.addClass('disabled');
4660
- this.ui.previous.addClass('disabled');
4661
- this.ui.next.addClass('disabled');
4662
- this.ui.last.addClass('disabled');
4663
- } else {
4664
- this.data = data;
4665
- this.enable(this.ui.first, this.getPage(data) > 1);
4666
- this.enable(this.ui.previous, this.getPage(data) > 1);
4667
- this.setupPages();
4668
- this.enable(this.ui.next, this.getPage(data) < this.numberOfPages(data));
4669
- this.enable(this.ui.last, this.getPage(data) < this.numberOfPages(data));
4670
- }
4671
- },
4672
-
4673
- setupPages : function() {
4674
-
4675
- var page = this.getPage(this.data);
4676
- var total = this.numberOfPages();
4677
-
4678
- var first = page - 2;
4679
- if (total - first < 4) {
4680
- first = total - 4;
4681
- }
4682
-
4683
- if (first < 1) {
4684
- first = 1;
4576
+ field = this.$el.find('thead [data-field="' + name + '"]');
4577
+ if (!field.length) {
4578
+ field = this.$el.find('thead th:contains("' + name + '")');
4685
4579
  }
4686
-
4687
- var n = 5;
4688
- if (first + n - 1 > total) {
4689
- n = total - first + 1;
4690
- }
4691
-
4692
- _.times(n, function(i) {
4693
- $(this.pageTemplate({ number : first + i })).insertBefore(this.ui.next);
4694
- }, this);
4695
-
4696
- var i = page - first;
4697
- this.$el.find('.page').slice(i, i + 1).addClass('disabled');
4698
- },
4699
-
4700
- enable : function(el, enabled) {
4701
- el.removeClass('disabled');
4702
- if (!enabled) {
4703
- el.addClass('disabled');
4580
+
4581
+ if (field.length) {
4582
+ field.removeClass('sorting').addClass(direction == 'desc' ? 'sorting-desc' : 'sorting-asc');
4704
4583
  }
4705
- },
4706
-
4707
- numberOfPages : function() {
4708
- return Math.ceil(this.data.total / this.data.pageSize);
4709
- },
4710
-
4711
- goToFirstPage : function(e) {
4712
- e.preventDefault();
4713
- this.goToPageNumber(1);
4714
- },
4715
-
4716
- goToPreviousPage : function(e) {
4717
- e.preventDefault();
4718
- this.goToPageNumber(this.getPage(this.data) - 1);
4719
- },
4720
-
4721
- goToPage : function(e) {
4722
- e.preventDefault();
4723
- this.goToPageNumber(parseInt($(e.target).text(), 10));
4724
- },
4725
-
4726
- goToNextPage : function(e) {
4727
- e.preventDefault();
4728
- this.goToPageNumber(this.getPage(this.data) + 1);
4729
- },
4730
-
4731
- goToLastPage : function(e) {
4732
- e.preventDefault();
4733
- this.goToPageNumber(this.numberOfPages());
4734
- },
4735
-
4736
- goToPageNumber : function(n) {
4737
- this.vent.trigger('table:update', { page : n });
4738
- },
4739
-
4740
- getPage : function(data) {
4741
- return data.page || 1;
4742
4584
  }
4743
- });
4744
-
4745
- Tableling.Bootstrap = {};
4746
-
4747
- Tableling.Bootstrap.Table = Tableling.Plain.Table.extend({
4748
- 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>')
4749
- });
4750
-
4751
- Tableling.Bootstrap.TableView = Tableling.Plain.TableView.extend({});
4752
-
4753
- Tableling.Bootstrap.PageSizeView = Tableling.Bootstrap.Table.prototype.pageSizeView = Tableling.Plain.PageSizeView.extend({
4754
-
4755
- tagName : 'form',
4756
- className : 'form-inline',
4757
- attributes : {
4758
- role : 'form'
4759
- },
4760
- template : function(data) {
4761
- return _.template('<div class="formGroup"><select name="pageSize" class="form-control"><option>5</option><option>10</option><option>15</option></select> <%- entries %></div>', data);
4585
+ },
4586
+
4587
+ config: function() {
4588
+ return {
4589
+ page: 1,
4590
+ sort: this.sortConfig()
4591
+ };
4592
+ },
4593
+
4594
+ sortConfig: function() {
4595
+ return this.sort.length ? this.sort : null;
4596
+ },
4597
+
4598
+ fieldName: function(el) {
4599
+ return el.data('field') || el.text();
4600
+ }
4601
+ });
4602
+
4603
+ Tableling.Plain.PageSizeView = Tableling.Plain.Table.prototype.pageSizeView = Tableling.FieldModule.extend({
4604
+
4605
+ // TODO: update current page intelligently
4606
+ name: 'pageSize',
4607
+ template: function(data) {
4608
+ return _.template('<select name="pageSize" /> <%- entries %>', data);
4609
+ },
4610
+
4611
+ i18n: {
4612
+ entries: 'entries per page'
4613
+ },
4614
+ sizes: [ 10, 15, 20, 25, 50 ],
4615
+
4616
+ ui: {
4617
+ field: 'select'
4618
+ },
4619
+
4620
+ initialize: function(options) {
4621
+ this.sizes = _.clone(options.sizes || this.sizes);
4622
+ Tableling.FieldModule.prototype.initialize.call(this, options);
4623
+ },
4624
+
4625
+ onRender: function() {
4626
+ this.ui.field.empty();
4627
+ _.each(this.sizes, _.bind(this.addSize, this));
4628
+ },
4629
+
4630
+ addSize: function(size) {
4631
+ $('<option />').text(size).appendTo(this.ui.field);
4632
+ },
4633
+
4634
+ setupValue: function(value) {
4635
+ if (value) {
4636
+ Tableling.FieldModule.prototype.setupValue.apply(this, Array.prototype.slice.call(arguments));
4762
4637
  }
4763
- });
4764
-
4765
- Tableling.Bootstrap.QuickSearchView = Tableling.Bootstrap.Table.prototype.quickSearchView = Tableling.Plain.QuickSearchView.extend({
4766
-
4767
- tagName : 'form',
4768
- className : 'form-inline',
4769
- attributes : {
4770
- role : 'form'
4771
- },
4772
- template : function(data) {
4773
- return _.template('<div class="formGroup"><input type="text" name="quickSearch" class="form-control" placeholder="<%- quickSearch %>" /></div>', data);
4638
+ },
4639
+
4640
+ config: function() {
4641
+ var config = Tableling.FieldModule.prototype.config.call(this);
4642
+ config.page = 1;
4643
+ return config;
4644
+ }
4645
+ });
4646
+
4647
+ Tableling.Plain.QuickSearchView = Tableling.Plain.Table.prototype.quickSearchView = Tableling.FieldModule.extend({
4648
+
4649
+ name: 'quickSearch',
4650
+ template: function(data) {
4651
+ return _.template('<input type="text" name="quickSearch" placeholder="<%- quickSearch %>" />', data);
4652
+ },
4653
+
4654
+ i18n: {
4655
+ quickSearch: 'Quick search...'
4656
+ },
4657
+
4658
+ config: function() {
4659
+ var config = Tableling.FieldModule.prototype.config.call(this);
4660
+ config.page = 1;
4661
+ return config;
4662
+ }
4663
+ });
4664
+
4665
+ Tableling.Plain.InfoView = Tableling.Plain.Table.prototype.infoView = Tableling.Module.extend({
4666
+
4667
+ template: function(data) {
4668
+ return _.template(data.template, {
4669
+ first: '<span class="first">0</span>',
4670
+ last: '<span class="last">0</span>',
4671
+ total: '<span class="total">0</span>'
4672
+ });
4673
+ },
4674
+
4675
+ i18n: {
4676
+ template: 'Showing <%= first %> to <%= last %> of <%= total %> entries'
4677
+ },
4678
+
4679
+ ui: {
4680
+ first: '.first',
4681
+ last: '.last',
4682
+ total: '.total'
4683
+ },
4684
+
4685
+ refresh: function(data) {
4686
+ if (data) {
4687
+ this.ui.first.text(this.firstRecord(data));
4688
+ this.ui.last.text(this.lastRecord(data));
4689
+ this.ui.total.text(data.total);
4774
4690
  }
4775
- });
4776
-
4777
- Tableling.Bootstrap.InfoView = Tableling.Bootstrap.Table.prototype.infoView = Tableling.Plain.InfoView.extend({});
4778
-
4779
- Tableling.Bootstrap.PageView = Tableling.Bootstrap.Table.prototype.pageView = Tableling.Plain.PageView.extend({});
4780
-
4691
+ },
4692
+
4693
+ firstRecord: function(data) {
4694
+ return data.length ? ((data.page || 1) - 1) * data.pageSize + 1 : 0;
4695
+ },
4696
+
4697
+ lastRecord: function(data) {
4698
+ return data.length ? this.firstRecord(data) + data.length - 1 : 0;
4699
+ }
4700
+ });
4701
+
4702
+ Tableling.Plain.PageView = Tableling.Plain.Table.prototype.pageView = Tableling.Module.extend({
4703
+
4704
+ 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>'),
4705
+ pageTemplate: _.template('<li class="page"><a href="#"><%- number %></a></li>'),
4706
+
4707
+ ui: {
4708
+ first: '.first',
4709
+ previous: '.previous',
4710
+ next: '.next',
4711
+ last: '.last'
4712
+ },
4713
+
4714
+ events: {
4715
+ 'click .first:not(.disabled)': 'goToFirstPage',
4716
+ 'click .previous:not(.disabled)': 'goToPreviousPage',
4717
+ 'click .page:not(.disabled)': 'goToPage',
4718
+ 'click .next:not(.disabled)': 'goToNextPage',
4719
+ 'click .last:not(.disabled)': 'goToLastPage'
4720
+ },
4721
+
4722
+ refresh: function(data) {
4723
+ this.$el.find('.page').remove();
4724
+ if (!data || !data.length) {
4725
+ this.ui.first.addClass('disabled');
4726
+ this.ui.previous.addClass('disabled');
4727
+ this.ui.next.addClass('disabled');
4728
+ this.ui.last.addClass('disabled');
4729
+ } else {
4730
+ this.data = data;
4731
+ this.enable(this.ui.first, this.getPage(data) > 1);
4732
+ this.enable(this.ui.previous, this.getPage(data) > 1);
4733
+ this.setupPages();
4734
+ this.enable(this.ui.next, this.getPage(data) < this.numberOfPages(data));
4735
+ this.enable(this.ui.last, this.getPage(data) < this.numberOfPages(data));
4736
+ }
4737
+ },
4738
+
4739
+ setupPages: function() {
4740
+
4741
+ var page = this.getPage(this.data);
4742
+ var total = this.numberOfPages();
4743
+
4744
+ var first = page - 2;
4745
+ if (total - first < 4) {
4746
+ first = total - 4;
4747
+ }
4748
+
4749
+ if (first < 1) {
4750
+ first = 1;
4751
+ }
4752
+
4753
+ var n = 5;
4754
+ if (first + n - 1 > total) {
4755
+ n = total - first + 1;
4756
+ }
4757
+
4758
+ _.times(n, function(i) {
4759
+ $(this.pageTemplate({ number: first + i })).insertBefore(this.ui.next);
4760
+ }, this);
4761
+
4762
+ var i = page - first;
4763
+ this.$el.find('.page').slice(i, i + 1).addClass('disabled');
4764
+ },
4765
+
4766
+ enable: function(el, enabled) {
4767
+ el.removeClass('disabled');
4768
+ if (!enabled) {
4769
+ el.addClass('disabled');
4770
+ }
4771
+ },
4772
+
4773
+ numberOfPages: function() {
4774
+ return Math.ceil(this.data.total / this.data.pageSize);
4775
+ },
4776
+
4777
+ goToFirstPage: function(e) {
4778
+ e.preventDefault();
4779
+ this.goToPageNumber(1);
4780
+ },
4781
+
4782
+ goToPreviousPage: function(e) {
4783
+ e.preventDefault();
4784
+ this.goToPageNumber(this.getPage(this.data) - 1);
4785
+ },
4786
+
4787
+ goToPage: function(e) {
4788
+ e.preventDefault();
4789
+ this.goToPageNumber(parseInt($(e.target).text(), 10));
4790
+ },
4791
+
4792
+ goToNextPage: function(e) {
4793
+ e.preventDefault();
4794
+ this.goToPageNumber(this.getPage(this.data) + 1);
4795
+ },
4796
+
4797
+ goToLastPage: function(e) {
4798
+ e.preventDefault();
4799
+ this.goToPageNumber(this.numberOfPages());
4800
+ },
4801
+
4802
+ goToPageNumber: function(n) {
4803
+ this.vent.trigger('table:update', { page: n });
4804
+ },
4805
+
4806
+ getPage: function(data) {
4807
+ return data.page || 1;
4808
+ }
4809
+ });
4810
+
4811
+ Tableling.Bootstrap = {};
4812
+
4813
+ Tableling.Bootstrap.Table = Tableling.Plain.Table.extend({
4814
+ 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>')
4815
+ });
4816
+
4817
+ Tableling.Bootstrap.TableView = Tableling.Plain.TableView.extend({});
4818
+
4819
+ Tableling.Bootstrap.PageSizeView = Tableling.Bootstrap.Table.prototype.pageSizeView = Tableling.Plain.PageSizeView.extend({
4820
+
4821
+ tagName: 'form',
4822
+ className: 'form-inline',
4823
+ attributes: {
4824
+ role: 'form'
4825
+ },
4826
+ template: function(data) {
4827
+ return _.template('<div class="formGroup"><select name="pageSize" class="form-control"><option>5</option><option>10</option><option>15</option></select> <%- entries %></div>', data);
4828
+ }
4829
+ });
4830
+
4831
+ Tableling.Bootstrap.QuickSearchView = Tableling.Bootstrap.Table.prototype.quickSearchView = Tableling.Plain.QuickSearchView.extend({
4832
+
4833
+ tagName: 'form',
4834
+ className: 'form-inline',
4835
+ attributes: {
4836
+ role: 'form'
4837
+ },
4838
+ template: function(data) {
4839
+ return _.template('<div class="formGroup"><input type="text" name="quickSearch" class="form-control" placeholder="<%- quickSearch %>" /></div>', data);
4840
+ }
4841
+ });
4842
+
4843
+ Tableling.Bootstrap.InfoView = Tableling.Bootstrap.Table.prototype.infoView = Tableling.Plain.InfoView.extend({});
4844
+
4845
+ Tableling.Bootstrap.PageView = Tableling.Bootstrap.Table.prototype.pageView = Tableling.Plain.PageView.extend({});
4781
4846
 
4782
4847
  return Tableling;
4783
4848
 
4784
- })(Backbone, _, $ || window.jQuery || window.Zepto || window.ender);
4849
+ })(Backbone, _, $ || window.jQuery || window.Zepto || window.ender);