tableling-rails 0.0.23 → 0.0.24

Sign up to get free protection for your applications and to get access to all the features.
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);