tableling-rails 0.0.7 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (111) hide show
  1. data/lib/tableling-rails/model.rb +1 -1
  2. data/lib/tableling-rails/version.rb +1 -1
  3. data/spec/dummy/db/development.sqlite3 +0 -0
  4. data/spec/dummy/db/test.sqlite3 +0 -0
  5. data/spec/dummy/log/development.log +25 -7250
  6. data/spec/dummy/log/test.log +3 -60
  7. data/spec/models/book_spec.rb +1 -0
  8. data/vendor/assets/javascripts/tableling.backbone.js +1980 -1489
  9. data/vendor/assets/javascripts/tableling.js +83 -53
  10. data/vendor/assets/javascripts/tableling.world.js +9734 -9102
  11. metadata +2 -202
  12. data/spec/dummy/tmp/cache/assets/BF1/770/sprockets%2F8228a53c539c582499241527570aa216 +0 -0
  13. data/spec/dummy/tmp/cache/assets/C18/6A0/sprockets%2F1e7024441489753901b45a24af2942e3 +0 -0
  14. data/spec/dummy/tmp/cache/assets/C20/C20/sprockets%2F932e86665765e61017be1015b249d270 +0 -0
  15. data/spec/dummy/tmp/cache/assets/C3F/0C0/sprockets%2F4643701467ac62c314510c8dd021916f +0 -0
  16. data/spec/dummy/tmp/cache/assets/C53/AD0/sprockets%2F712078516a81502575c831cb0a8e898e +0 -0
  17. data/spec/dummy/tmp/cache/assets/C59/0F0/sprockets%2F099f4e84a443568245ff0674030f6e05 +0 -0
  18. data/spec/dummy/tmp/cache/assets/C60/CF0/sprockets%2F32a45292ee67867d8715952f3072ad84 +0 -0
  19. data/spec/dummy/tmp/cache/assets/C6F/0E0/sprockets%2F82372776487d948ef7666d304d6fb345 +0 -0
  20. data/spec/dummy/tmp/cache/assets/C80/840/sprockets%2F562c2d168da585f80579347d10790a0a +0 -0
  21. data/spec/dummy/tmp/cache/assets/C82/F20/sprockets%2F117015d65237e3682f5b21c87d58ab95 +0 -0
  22. data/spec/dummy/tmp/cache/assets/C86/280/sprockets%2F1345c9947753f018f45371ec5083fa2d +0 -0
  23. data/spec/dummy/tmp/cache/assets/C91/350/sprockets%2F6864c49a10954375948d9f306db308fb +0 -0
  24. data/spec/dummy/tmp/cache/assets/C98/B70/sprockets%2F64db420a3a172209bf0b734312a4888a +0 -0
  25. data/spec/dummy/tmp/cache/assets/C9A/900/sprockets%2Fc1b5b7611d7c10e124512724f29f241d +0 -0
  26. data/spec/dummy/tmp/cache/assets/CAE/5B0/sprockets%2F046918545d85d144c4aa32d3e19625cf +0 -0
  27. data/spec/dummy/tmp/cache/assets/CAF/540/sprockets%2F8f42e689b84f0b25c0180046467adf31 +0 -0
  28. data/spec/dummy/tmp/cache/assets/CB1/9A0/sprockets%2F57805dfa396248a9665cb4a0c85116d0 +0 -0
  29. data/spec/dummy/tmp/cache/assets/CC1/4E0/sprockets%2F6ef77414c793c84a437244d7d965ba90 +0 -0
  30. data/spec/dummy/tmp/cache/assets/CC1/B50/sprockets%2F13559b77aaaf524896e427719ef5d354 +0 -0
  31. data/spec/dummy/tmp/cache/assets/CC3/B10/sprockets%2F0a2c59284c7a5d99581883a9763b5ad8 +0 -0
  32. data/spec/dummy/tmp/cache/assets/CC9/940/sprockets%2F8128c279185cf97dba7914278ce838b4 +0 -0
  33. data/spec/dummy/tmp/cache/assets/CCA/0C0/sprockets%2F6c3e053393afe303432ca73f1b41490b +0 -0
  34. data/spec/dummy/tmp/cache/assets/CD4/420/sprockets%2F19b3d492932dc6a59093dfb1aa852101 +0 -0
  35. data/spec/dummy/tmp/cache/assets/CD5/2C0/sprockets%2F166c056119ebdfb8b7104c97b424b423 +0 -0
  36. data/spec/dummy/tmp/cache/assets/CD8/370/sprockets%2F357970feca3ac29060c1e3861e2c0953 +0 -0
  37. data/spec/dummy/tmp/cache/assets/CD8/520/sprockets%2F89128ce416ae74c4148c5c1f281e111c +0 -0
  38. data/spec/dummy/tmp/cache/assets/CDA/600/sprockets%2F98ab7e75433ba5d503533fb00b488c80 +0 -0
  39. data/spec/dummy/tmp/cache/assets/CDB/610/sprockets%2F7adf20860b018477a95cdb8321b49d16 +0 -0
  40. data/spec/dummy/tmp/cache/assets/CDD/F50/sprockets%2F7353f31f80f1139aade47728a62af336 +0 -0
  41. data/spec/dummy/tmp/cache/assets/CE1/0F0/sprockets%2F92d30845fe3923e231cfe7e056a654b4 +0 -0
  42. data/spec/dummy/tmp/cache/assets/CE2/E70/sprockets%2Fd569808c7b411447dec0137e4d1ee707 +0 -0
  43. data/spec/dummy/tmp/cache/assets/CE9/760/sprockets%2F5ae7fab601683996918936cad21fc244 +0 -0
  44. data/spec/dummy/tmp/cache/assets/CE9/B90/sprockets%2Fea2eb066933b1d8b5949972c3c79353a +0 -0
  45. data/spec/dummy/tmp/cache/assets/CF1/AF0/sprockets%2F14dc9633b61024231ebec0c1a4a0259f +0 -0
  46. data/spec/dummy/tmp/cache/assets/D02/340/sprockets%2Fae58ed66f72137a594b0912bab600e03 +0 -0
  47. data/spec/dummy/tmp/cache/assets/D0A/780/sprockets%2F3d727e58c55c30009bbaf1462e466d4d +0 -0
  48. data/spec/dummy/tmp/cache/assets/D0A/AC0/sprockets%2Fc154991d0a7564e15c0fb4a555c5c73d +0 -0
  49. data/spec/dummy/tmp/cache/assets/D0B/190/sprockets%2F68cb0d1054ca546fc473274c4ac8737c +0 -0
  50. data/spec/dummy/tmp/cache/assets/D11/530/sprockets%2F13bc8c66140adef2e97648630866aae3 +0 -0
  51. data/spec/dummy/tmp/cache/assets/D16/AA0/sprockets%2Ff181e659a47e2cf9c257f32d80452bd3 +0 -0
  52. data/spec/dummy/tmp/cache/assets/D18/840/sprockets%2F50c1885539f61359c48af74fcae1df31 +0 -0
  53. data/spec/dummy/tmp/cache/assets/D1B/940/sprockets%2F7c4819d1dd9b64a22941d8c7bd1e6954 +0 -0
  54. data/spec/dummy/tmp/cache/assets/D21/890/sprockets%2F73f9cd97cac4382d565e7b835709a2e3 +0 -0
  55. data/spec/dummy/tmp/cache/assets/D2B/1F0/sprockets%2F6a4c116e8316d082bb0ffae02e94b107 +0 -0
  56. data/spec/dummy/tmp/cache/assets/D32/A10/sprockets%2F13fe41fee1fe35b49d145bcc06610705 +0 -0
  57. data/spec/dummy/tmp/cache/assets/D3A/720/sprockets%2F0e2516c1cac22716289bfc87d5ea9a26 +0 -0
  58. data/spec/dummy/tmp/cache/assets/D3C/E20/sprockets%2Ffc6c44ec250bc532c55649e5292b5c1f +0 -0
  59. data/spec/dummy/tmp/cache/assets/D4B/BF0/sprockets%2Fdf124de0072e308f8e157eae77b85d88 +0 -0
  60. data/spec/dummy/tmp/cache/assets/D4B/C30/sprockets%2Fe8aa746e637d69ff0ac1e71e528c5292 +0 -0
  61. data/spec/dummy/tmp/cache/assets/D4E/1B0/sprockets%2Ff7cbd26ba1d28d48de824f0e94586655 +0 -0
  62. data/spec/dummy/tmp/cache/assets/D54/910/sprockets%2F787b6a4c2386b5edf98f90508b85cc3d +0 -0
  63. data/spec/dummy/tmp/cache/assets/D5A/E20/sprockets%2Febf23d670481ba70b341aa2eb6e37b08 +0 -0
  64. data/spec/dummy/tmp/cache/assets/D5A/EA0/sprockets%2Fd771ace226fc8215a3572e0aa35bb0d6 +0 -0
  65. data/spec/dummy/tmp/cache/assets/D5C/1F0/sprockets%2F2d3f2cfd98bf787168fb59f84ab24784 +0 -0
  66. data/spec/dummy/tmp/cache/assets/D5D/C60/sprockets%2Ffa0336b42d01aa9173d9d4c12fc4e82d +0 -0
  67. data/spec/dummy/tmp/cache/assets/D63/170/sprockets%2Fb5b73c3b0862ee59b701a6819b2aa4cd +0 -0
  68. data/spec/dummy/tmp/cache/assets/D65/DD0/sprockets%2Fed4503679ef66f0e597bfc738bc9e698 +0 -0
  69. data/spec/dummy/tmp/cache/assets/D66/580/sprockets%2Ffe6bb9bb5ef9c54f501650b1365c1b05 +0 -0
  70. data/spec/dummy/tmp/cache/assets/D68/930/sprockets%2F5a15e52e84502f9fceb7ea04020f65fc +0 -0
  71. data/spec/dummy/tmp/cache/assets/D6A/F40/sprockets%2F53fdbea64f1803eebb72f426f74110d8 +0 -0
  72. data/spec/dummy/tmp/cache/assets/D6B/D30/sprockets%2Faeb25572f539ffdbb122e9de1218572a +0 -0
  73. data/spec/dummy/tmp/cache/assets/D6F/110/sprockets%2F2c8c9177efb7e5be1a7055bb0d44792a +0 -0
  74. data/spec/dummy/tmp/cache/assets/D76/3F0/sprockets%2Fbb569c97f9cc01af670906b40c6be9e2 +0 -0
  75. data/spec/dummy/tmp/cache/assets/D81/510/sprockets%2F3e6bd5a2d6d0d88f61f5b616f7d879b5 +0 -0
  76. data/spec/dummy/tmp/cache/assets/D82/940/sprockets%2F29cd0ae31066baa6f2cb92b5c6305aa2 +0 -0
  77. data/spec/dummy/tmp/cache/assets/D85/D20/sprockets%2F7f7f398c298ca8a1e812b6c9832bcf6e +0 -0
  78. data/spec/dummy/tmp/cache/assets/D8C/140/sprockets%2F2afdbb3902a1be5d2f045cfc837903a3 +0 -0
  79. data/spec/dummy/tmp/cache/assets/D8E/660/sprockets%2F3a3eed926bdf64000ccacb36022f78d4 +0 -0
  80. data/spec/dummy/tmp/cache/assets/D95/690/sprockets%2Fe4d19a04705ee5d72c7fa64dc2070ddb +0 -0
  81. data/spec/dummy/tmp/cache/assets/D97/230/sprockets%2F0f46cb65e018de57d2c8b480a0ec5dd3 +0 -0
  82. data/spec/dummy/tmp/cache/assets/DA1/F00/sprockets%2F7b23e35dd61e9d734a59179e2d2aebea +0 -0
  83. data/spec/dummy/tmp/cache/assets/DA3/950/sprockets%2F38737f6fa54dbda0dfe3ce4c51710e79 +0 -0
  84. data/spec/dummy/tmp/cache/assets/DA4/EC0/sprockets%2F8da2289e16c1d0cac94aa825ee18d99d +0 -0
  85. data/spec/dummy/tmp/cache/assets/DA6/120/sprockets%2Fc5880aca76ccbb51f9388362e8afc1e6 +0 -0
  86. data/spec/dummy/tmp/cache/assets/DA7/390/sprockets%2F25e744cd1b6f8f7ce1d52c1e86a8f19a +0 -0
  87. data/spec/dummy/tmp/cache/assets/DAB/3E0/sprockets%2F63d2fa621beec4fe878f9eb2884426ab +0 -0
  88. data/spec/dummy/tmp/cache/assets/DAB/660/sprockets%2F9a8c36769afcc7027b4b6ee0c3e77ce5 +0 -0
  89. data/spec/dummy/tmp/cache/assets/DB4/7A0/sprockets%2F900105aaabba7938ce23f1e0aade71e5 +0 -0
  90. data/spec/dummy/tmp/cache/assets/DB4/F90/sprockets%2Fcf5e68e329cedb58b84f445f2b8e37b5 +0 -0
  91. data/spec/dummy/tmp/cache/assets/DBF/EB0/sprockets%2F2afcec323c8ba12a2b7c852f23aa589e +0 -0
  92. data/spec/dummy/tmp/cache/assets/DCE/9F0/sprockets%2F8fa8c08ea8da97c06cd6c32fe3e613b0 +0 -0
  93. data/spec/dummy/tmp/cache/assets/DCF/E50/sprockets%2Fc39ff2ed4e1fb4269c39d415acf0d90b +0 -0
  94. data/spec/dummy/tmp/cache/assets/DD8/0A0/sprockets%2Fb2c3097effcd6084ec75e1f745d1d7dd +0 -0
  95. data/spec/dummy/tmp/cache/assets/DDC/400/sprockets%2Fcffd775d018f68ce5dba1ee0d951a994 +0 -0
  96. data/spec/dummy/tmp/cache/assets/DE2/6D0/sprockets%2Fdcaa7d2b2814413aa75d3ba6ada3c6b5 +0 -0
  97. data/spec/dummy/tmp/cache/assets/DF0/040/sprockets%2F04bbbe1ee5ca914173cd90cf6e8a4e0b +0 -0
  98. data/spec/dummy/tmp/cache/assets/DF0/D60/sprockets%2F5a54a1ecc1db271edfc0727bfcb8d912 +0 -0
  99. data/spec/dummy/tmp/cache/assets/DF7/C80/sprockets%2Ff503b3666caf04beb7cfe54cbdb0575e +0 -0
  100. data/spec/dummy/tmp/cache/assets/DFA/B70/sprockets%2F262fb95d3f7cfc16febb0f0128ac38ce +0 -0
  101. data/spec/dummy/tmp/cache/assets/DFE/E10/sprockets%2F9a19dfdd7eb0bf5262919a0207ecfdbf +0 -0
  102. data/spec/dummy/tmp/cache/assets/E03/260/sprockets%2Fd1fcfea59ff53c32557ffcd93016cb2e +0 -0
  103. data/spec/dummy/tmp/cache/assets/E04/890/sprockets%2F2f5173deea6c795b8fdde723bb4b63af +0 -0
  104. data/spec/dummy/tmp/cache/assets/E0A/510/sprockets%2F8efee416ba436e75bea85d4d367faea7 +0 -0
  105. data/spec/dummy/tmp/cache/assets/E33/8F0/sprockets%2F4933e07f8ef2da7eb5bee9a6fcac3d41 +0 -0
  106. data/spec/dummy/tmp/cache/assets/E3C/2D0/sprockets%2Fef329f0cde3de3e956c8a8feb7b814cc +0 -0
  107. data/spec/dummy/tmp/cache/assets/E43/810/sprockets%2F79b0ef75ceb26a9c8fca797db3efba96 +0 -0
  108. data/spec/dummy/tmp/cache/assets/E67/730/sprockets%2Fadd4efdfa7661cc60e8ea63cef1f0f98 +0 -0
  109. data/spec/dummy/tmp/cache/assets/E96/1B0/sprockets%2F6d5fe51f9c1a3d2f898becd0bcddef3e +0 -0
  110. data/spec/dummy/tmp/cache/assets/ED4/410/sprockets%2F082adbb8e2abd8a21c8b34aebbbfbfad +0 -0
  111. data/spec/dummy/tmp/pids/server.pid +0 -1
@@ -1,4 +1,4 @@
1
- // Backbone.js 0.9.2
1
+ // Backbone.js 0.9.10
2
2
 
3
3
  // (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
4
4
  // Backbone may be freely distributed under the MIT license.
@@ -10,7 +10,7 @@
10
10
  // Initial Setup
11
11
  // -------------
12
12
 
13
- // Save a reference to the global object (`window` in the browser, `global`
13
+ // Save a reference to the global object (`window` in the browser, `exports`
14
14
  // on the server).
15
15
  var root = this;
16
16
 
@@ -18,9 +18,11 @@
18
18
  // restored later on, if `noConflict` is used.
19
19
  var previousBackbone = root.Backbone;
20
20
 
21
- // Create a local reference to slice/splice.
22
- var slice = Array.prototype.slice;
23
- var splice = Array.prototype.splice;
21
+ // Create a local reference to array methods.
22
+ var array = [];
23
+ var push = array.push;
24
+ var slice = array.slice;
25
+ var splice = array.splice;
24
26
 
25
27
  // The top-level namespace. All public Backbone classes and modules will
26
28
  // be attached to this. Exported for both CommonJS and the browser.
@@ -32,23 +34,14 @@
32
34
  }
33
35
 
34
36
  // Current version of the library. Keep in sync with `package.json`.
35
- Backbone.VERSION = '0.9.2';
37
+ Backbone.VERSION = '0.9.10';
36
38
 
37
39
  // Require Underscore, if we're on the server, and it's not already present.
38
40
  var _ = root._;
39
41
  if (!_ && (typeof require !== 'undefined')) _ = require('underscore');
40
42
 
41
43
  // For Backbone's purposes, jQuery, Zepto, or Ender owns the `$` variable.
42
- var $ = root.jQuery || root.Zepto || root.ender;
43
-
44
- // Set the JavaScript library that will be used for DOM manipulation and
45
- // Ajax calls (a.k.a. the `$` variable). By default Backbone will use: jQuery,
46
- // Zepto, or Ender; but the `setDomLibrary()` method lets you inject an
47
- // alternate JavaScript library (or a mock library for testing your views
48
- // outside of a browser).
49
- Backbone.setDomLibrary = function(lib) {
50
- $ = lib;
51
- };
44
+ Backbone.$ = root.jQuery || root.Zepto || root.ender;
52
45
 
53
46
  // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
54
47
  // to its previous owner. Returns a reference to this Backbone object.
@@ -69,14 +62,51 @@
69
62
  Backbone.emulateJSON = false;
70
63
 
71
64
  // Backbone.Events
72
- // -----------------
65
+ // ---------------
73
66
 
74
- // Regular expression used to split event strings
67
+ // Regular expression used to split event strings.
75
68
  var eventSplitter = /\s+/;
76
69
 
70
+ // Implement fancy features of the Events API such as multiple event
71
+ // names `"change blur"` and jQuery-style event maps `{change: action}`
72
+ // in terms of the existing API.
73
+ var eventsApi = function(obj, action, name, rest) {
74
+ if (!name) return true;
75
+ if (typeof name === 'object') {
76
+ for (var key in name) {
77
+ obj[action].apply(obj, [key, name[key]].concat(rest));
78
+ }
79
+ } else if (eventSplitter.test(name)) {
80
+ var names = name.split(eventSplitter);
81
+ for (var i = 0, l = names.length; i < l; i++) {
82
+ obj[action].apply(obj, [names[i]].concat(rest));
83
+ }
84
+ } else {
85
+ return true;
86
+ }
87
+ };
88
+
89
+ // Optimized internal dispatch function for triggering events. Tries to
90
+ // keep the usual cases speedy (most Backbone events have 3 arguments).
91
+ var triggerEvents = function(events, args) {
92
+ var ev, i = -1, l = events.length;
93
+ switch (args.length) {
94
+ case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx);
95
+ return;
96
+ case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, args[0]);
97
+ return;
98
+ case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, args[0], args[1]);
99
+ return;
100
+ case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, args[0], args[1], args[2]);
101
+ return;
102
+ default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args);
103
+ }
104
+ };
105
+
77
106
  // A module that can be mixed in to *any object* in order to provide it with
78
- // custom events. You may bind with `on` or remove with `off` callback functions
79
- // to an event; trigger`-ing an event fires all callbacks in succession.
107
+ // custom events. You may bind with `on` or remove with `off` callback
108
+ // functions to an event; `trigger`-ing an event fires all callbacks in
109
+ // succession.
80
110
  //
81
111
  // var object = {};
82
112
  // _.extend(object, Backbone.Events);
@@ -85,58 +115,59 @@
85
115
  //
86
116
  var Events = Backbone.Events = {
87
117
 
88
- // Bind one or more space separated events, `events`, to a `callback`
89
- // function. Passing `"all"` will bind the callback to all events fired.
90
- on: function(events, callback, context) {
91
-
92
- var calls, event, node, tail, list;
93
- if (!callback) return this;
94
- events = events.split(eventSplitter);
95
- calls = this._callbacks || (this._callbacks = {});
96
-
97
- // Create an immutable callback list, allowing traversal during
98
- // modification. The tail is an empty object that will always be used
99
- // as the next node.
100
- while (event = events.shift()) {
101
- list = calls[event];
102
- node = list ? list.tail : {};
103
- node.next = tail = {};
104
- node.context = context;
105
- node.callback = callback;
106
- calls[event] = {tail: tail, next: list ? list.next : node};
107
- }
108
-
118
+ // Bind one or more space separated events, or an events map,
119
+ // to a `callback` function. Passing `"all"` will bind the callback to
120
+ // all events fired.
121
+ on: function(name, callback, context) {
122
+ if (!(eventsApi(this, 'on', name, [callback, context]) && callback)) return this;
123
+ this._events || (this._events = {});
124
+ var list = this._events[name] || (this._events[name] = []);
125
+ list.push({callback: callback, context: context, ctx: context || this});
109
126
  return this;
110
127
  },
111
128
 
112
- // Remove one or many callbacks. If `context` is null, removes all callbacks
113
- // with that function. If `callback` is null, removes all callbacks for the
114
- // event. If `events` is null, removes all bound callbacks for all events.
115
- off: function(events, callback, context) {
116
- var event, calls, node, tail, cb, ctx;
129
+ // Bind events to only be triggered a single time. After the first time
130
+ // the callback is invoked, it will be removed.
131
+ once: function(name, callback, context) {
132
+ if (!(eventsApi(this, 'once', name, [callback, context]) && callback)) return this;
133
+ var self = this;
134
+ var once = _.once(function() {
135
+ self.off(name, once);
136
+ callback.apply(this, arguments);
137
+ });
138
+ once._callback = callback;
139
+ this.on(name, once, context);
140
+ return this;
141
+ },
117
142
 
118
- // No events, or removing *all* events.
119
- if (!(calls = this._callbacks)) return;
120
- if (!(events || callback || context)) {
121
- delete this._callbacks;
143
+ // Remove one or many callbacks. If `context` is null, removes all
144
+ // callbacks with that function. If `callback` is null, removes all
145
+ // callbacks for the event. If `name` is null, removes all bound
146
+ // callbacks for all events.
147
+ off: function(name, callback, context) {
148
+ var list, ev, events, names, i, l, j, k;
149
+ if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;
150
+ if (!name && !callback && !context) {
151
+ this._events = {};
122
152
  return this;
123
153
  }
124
154
 
125
- // Loop through the listed events and contexts, splicing them out of the
126
- // linked list of callbacks if appropriate.
127
- events = events ? events.split(eventSplitter) : _.keys(calls);
128
- while (event = events.shift()) {
129
- node = calls[event];
130
- delete calls[event];
131
- if (!node || !(callback || context)) continue;
132
- // Create a new list, omitting the indicated callbacks.
133
- tail = node.tail;
134
- while ((node = node.next) !== tail) {
135
- cb = node.callback;
136
- ctx = node.context;
137
- if ((callback && cb !== callback) || (context && ctx !== context)) {
138
- this.on(event, cb, ctx);
155
+ names = name ? [name] : _.keys(this._events);
156
+ for (i = 0, l = names.length; i < l; i++) {
157
+ name = names[i];
158
+ if (list = this._events[name]) {
159
+ events = [];
160
+ if (callback || context) {
161
+ for (j = 0, k = list.length; j < k; j++) {
162
+ ev = list[j];
163
+ if ((callback && callback !== ev.callback &&
164
+ callback !== ev.callback._callback) ||
165
+ (context && context !== ev.context)) {
166
+ events.push(ev);
167
+ }
168
+ }
139
169
  }
170
+ this._events[name] = events;
140
171
  }
141
172
  }
142
173
 
@@ -147,40 +178,54 @@
147
178
  // passed the same arguments as `trigger` is, apart from the event name
148
179
  // (unless you're listening on `"all"`, which will cause your callback to
149
180
  // receive the true name of the event as the first argument).
150
- trigger: function(events) {
151
- var event, node, calls, tail, args, all, rest;
152
- if (!(calls = this._callbacks)) return this;
153
- all = calls.all;
154
- events = events.split(eventSplitter);
155
- rest = slice.call(arguments, 1);
156
-
157
- // For each event, walk through the linked list of callbacks twice,
158
- // first to trigger the event, then to trigger any `"all"` callbacks.
159
- while (event = events.shift()) {
160
- if (node = calls[event]) {
161
- tail = node.tail;
162
- while ((node = node.next) !== tail) {
163
- node.callback.apply(node.context || this, rest);
164
- }
165
- }
166
- if (node = all) {
167
- tail = node.tail;
168
- args = [event].concat(rest);
169
- while ((node = node.next) !== tail) {
170
- node.callback.apply(node.context || this, args);
171
- }
181
+ trigger: function(name) {
182
+ if (!this._events) return this;
183
+ var args = slice.call(arguments, 1);
184
+ if (!eventsApi(this, 'trigger', name, args)) return this;
185
+ var events = this._events[name];
186
+ var allEvents = this._events.all;
187
+ if (events) triggerEvents(events, args);
188
+ if (allEvents) triggerEvents(allEvents, arguments);
189
+ return this;
190
+ },
191
+
192
+ // An inversion-of-control version of `on`. Tell *this* object to listen to
193
+ // an event in another object ... keeping track of what it's listening to.
194
+ listenTo: function(obj, name, callback) {
195
+ var listeners = this._listeners || (this._listeners = {});
196
+ var id = obj._listenerId || (obj._listenerId = _.uniqueId('l'));
197
+ listeners[id] = obj;
198
+ obj.on(name, typeof name === 'object' ? this : callback, this);
199
+ return this;
200
+ },
201
+
202
+ // Tell this object to stop listening to either specific events ... or
203
+ // to every object it's currently listening to.
204
+ stopListening: function(obj, name, callback) {
205
+ var listeners = this._listeners;
206
+ if (!listeners) return;
207
+ if (obj) {
208
+ obj.off(name, typeof name === 'object' ? this : callback, this);
209
+ if (!name && !callback) delete listeners[obj._listenerId];
210
+ } else {
211
+ if (typeof name === 'object') callback = this;
212
+ for (var id in listeners) {
213
+ listeners[id].off(name, callback, this);
172
214
  }
215
+ this._listeners = {};
173
216
  }
174
-
175
217
  return this;
176
218
  }
177
-
178
219
  };
179
220
 
180
221
  // Aliases for backwards compatibility.
181
222
  Events.bind = Events.on;
182
223
  Events.unbind = Events.off;
183
224
 
225
+ // Allow the `Backbone` object to serve as a global event bus, for folks who
226
+ // want global "pubsub" in a convenient place.
227
+ _.extend(Backbone, Events);
228
+
184
229
  // Backbone.Model
185
230
  // --------------
186
231
 
@@ -188,24 +233,16 @@
188
233
  // is automatically generated and assigned for you.
189
234
  var Model = Backbone.Model = function(attributes, options) {
190
235
  var defaults;
191
- attributes || (attributes = {});
192
- if (options && options.parse) attributes = this.parse(attributes);
193
- if (defaults = getValue(this, 'defaults')) {
194
- attributes = _.extend({}, defaults, attributes);
195
- }
196
- if (options && options.collection) this.collection = options.collection;
197
- this.attributes = {};
198
- this._escapedAttributes = {};
236
+ var attrs = attributes || {};
199
237
  this.cid = _.uniqueId('c');
238
+ this.attributes = {};
239
+ if (options && options.collection) this.collection = options.collection;
240
+ if (options && options.parse) attrs = this.parse(attrs, options) || {};
241
+ if (defaults = _.result(this, 'defaults')) {
242
+ attrs = _.defaults({}, attrs, defaults);
243
+ }
244
+ this.set(attrs, options);
200
245
  this.changed = {};
201
- this._silent = {};
202
- this._pending = {};
203
- this.set(attributes, {silent: true});
204
- // Reset change tracking.
205
- this.changed = {};
206
- this._silent = {};
207
- this._pending = {};
208
- this._previousAttributes = _.clone(this.attributes);
209
246
  this.initialize.apply(this, arguments);
210
247
  };
211
248
 
@@ -215,14 +252,6 @@
215
252
  // A hash of attributes whose current and previous value differ.
216
253
  changed: null,
217
254
 
218
- // A hash of attributes that have silently changed since the last time
219
- // `change` was called. Will become pending attributes on the next call.
220
- _silent: null,
221
-
222
- // A hash of attributes that have changed since the last `'change'` event
223
- // began.
224
- _pending: null,
225
-
226
255
  // The default name for the JSON `id` attribute is `"id"`. MongoDB and
227
256
  // CouchDB users may want to set this to `"_id"`.
228
257
  idAttribute: 'id',
@@ -236,6 +265,11 @@
236
265
  return _.clone(this.attributes);
237
266
  },
238
267
 
268
+ // Proxy `Backbone.sync` by default.
269
+ sync: function() {
270
+ return Backbone.sync.apply(this, arguments);
271
+ },
272
+
239
273
  // Get the value of an attribute.
240
274
  get: function(attr) {
241
275
  return this.attributes[attr];
@@ -243,10 +277,7 @@
243
277
 
244
278
  // Get the HTML-escaped value of an attribute.
245
279
  escape: function(attr) {
246
- var html;
247
- if (html = this._escapedAttributes[attr]) return html;
248
- var val = this.get(attr);
249
- return this._escapedAttributes[attr] = _.escape(val == null ? '' : '' + val);
280
+ return _.escape(this.get(attr));
250
281
  },
251
282
 
252
283
  // Returns `true` if the attribute contains a value that is not null
@@ -255,146 +286,192 @@
255
286
  return this.get(attr) != null;
256
287
  },
257
288
 
289
+ // ----------------------------------------------------------------------
290
+
258
291
  // Set a hash of model attributes on the object, firing `"change"` unless
259
292
  // you choose to silence it.
260
- set: function(key, value, options) {
261
- var attrs, attr, val;
293
+ set: function(key, val, options) {
294
+ var attr, attrs, unset, changes, silent, changing, prev, current;
295
+ if (key == null) return this;
262
296
 
263
297
  // Handle both `"key", value` and `{key: value}` -style arguments.
264
- if (_.isObject(key) || key == null) {
298
+ if (typeof key === 'object') {
265
299
  attrs = key;
266
- options = value;
300
+ options = val;
267
301
  } else {
268
- attrs = {};
269
- attrs[key] = value;
302
+ (attrs = {})[key] = val;
270
303
  }
271
304
 
272
- // Extract attributes and options.
273
305
  options || (options = {});
274
- if (!attrs) return this;
275
- if (attrs instanceof Model) attrs = attrs.attributes;
276
- if (options.unset) for (attr in attrs) attrs[attr] = void 0;
277
306
 
278
307
  // Run validation.
279
308
  if (!this._validate(attrs, options)) return false;
280
309
 
310
+ // Extract attributes and options.
311
+ unset = options.unset;
312
+ silent = options.silent;
313
+ changes = [];
314
+ changing = this._changing;
315
+ this._changing = true;
316
+
317
+ if (!changing) {
318
+ this._previousAttributes = _.clone(this.attributes);
319
+ this.changed = {};
320
+ }
321
+ current = this.attributes, prev = this._previousAttributes;
322
+
281
323
  // Check for changes of `id`.
282
324
  if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
283
325
 
284
- var changes = options.changes = {};
285
- var now = this.attributes;
286
- var escaped = this._escapedAttributes;
287
- var prev = this._previousAttributes || {};
288
-
289
- // For each `set` attribute...
326
+ // For each `set` attribute, update or delete the current value.
290
327
  for (attr in attrs) {
291
328
  val = attrs[attr];
292
-
293
- // If the new and current value differ, record the change.
294
- if (!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr))) {
295
- delete escaped[attr];
296
- (options.silent ? this._silent : changes)[attr] = true;
297
- }
298
-
299
- // Update or delete the current value.
300
- options.unset ? delete now[attr] : now[attr] = val;
301
-
302
- // If the new and previous value differ, record the change. If not,
303
- // then remove changes for this attribute.
304
- if (!_.isEqual(prev[attr], val) || (_.has(now, attr) != _.has(prev, attr))) {
329
+ if (!_.isEqual(current[attr], val)) changes.push(attr);
330
+ if (!_.isEqual(prev[attr], val)) {
305
331
  this.changed[attr] = val;
306
- if (!options.silent) this._pending[attr] = true;
307
332
  } else {
308
333
  delete this.changed[attr];
309
- delete this._pending[attr];
334
+ }
335
+ unset ? delete current[attr] : current[attr] = val;
336
+ }
337
+
338
+ // Trigger all relevant attribute changes.
339
+ if (!silent) {
340
+ if (changes.length) this._pending = true;
341
+ for (var i = 0, l = changes.length; i < l; i++) {
342
+ this.trigger('change:' + changes[i], this, current[changes[i]], options);
310
343
  }
311
344
  }
312
345
 
313
- // Fire the `"change"` events.
314
- if (!options.silent) this.change(options);
346
+ if (changing) return this;
347
+ if (!silent) {
348
+ while (this._pending) {
349
+ this._pending = false;
350
+ this.trigger('change', this, options);
351
+ }
352
+ }
353
+ this._pending = false;
354
+ this._changing = false;
315
355
  return this;
316
356
  },
317
357
 
318
358
  // Remove an attribute from the model, firing `"change"` unless you choose
319
359
  // to silence it. `unset` is a noop if the attribute doesn't exist.
320
360
  unset: function(attr, options) {
321
- (options || (options = {})).unset = true;
322
- return this.set(attr, null, options);
361
+ return this.set(attr, void 0, _.extend({}, options, {unset: true}));
323
362
  },
324
363
 
325
364
  // Clear all attributes on the model, firing `"change"` unless you choose
326
365
  // to silence it.
327
366
  clear: function(options) {
328
- (options || (options = {})).unset = true;
329
- return this.set(_.clone(this.attributes), options);
367
+ var attrs = {};
368
+ for (var key in this.attributes) attrs[key] = void 0;
369
+ return this.set(attrs, _.extend({}, options, {unset: true}));
370
+ },
371
+
372
+ // Determine if the model has changed since the last `"change"` event.
373
+ // If you specify an attribute name, determine if that attribute has changed.
374
+ hasChanged: function(attr) {
375
+ if (attr == null) return !_.isEmpty(this.changed);
376
+ return _.has(this.changed, attr);
377
+ },
378
+
379
+ // Return an object containing all the attributes that have changed, or
380
+ // false if there are no changed attributes. Useful for determining what
381
+ // parts of a view need to be updated and/or what attributes need to be
382
+ // persisted to the server. Unset attributes will be set to undefined.
383
+ // You can also pass an attributes object to diff against the model,
384
+ // determining if there *would be* a change.
385
+ changedAttributes: function(diff) {
386
+ if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
387
+ var val, changed = false;
388
+ var old = this._changing ? this._previousAttributes : this.attributes;
389
+ for (var attr in diff) {
390
+ if (_.isEqual(old[attr], (val = diff[attr]))) continue;
391
+ (changed || (changed = {}))[attr] = val;
392
+ }
393
+ return changed;
394
+ },
395
+
396
+ // Get the previous value of an attribute, recorded at the time the last
397
+ // `"change"` event was fired.
398
+ previous: function(attr) {
399
+ if (attr == null || !this._previousAttributes) return null;
400
+ return this._previousAttributes[attr];
401
+ },
402
+
403
+ // Get all of the attributes of the model at the time of the previous
404
+ // `"change"` event.
405
+ previousAttributes: function() {
406
+ return _.clone(this._previousAttributes);
330
407
  },
331
408
 
409
+ // ---------------------------------------------------------------------
410
+
332
411
  // Fetch the model from the server. If the server's representation of the
333
412
  // model differs from its current attributes, they will be overriden,
334
413
  // triggering a `"change"` event.
335
414
  fetch: function(options) {
336
415
  options = options ? _.clone(options) : {};
337
- var model = this;
416
+ if (options.parse === void 0) options.parse = true;
338
417
  var success = options.success;
339
- options.success = function(resp, status, xhr) {
340
- if (!model.set(model.parse(resp, xhr), options)) return false;
341
- if (success) success(model, resp);
418
+ options.success = function(model, resp, options) {
419
+ if (!model.set(model.parse(resp, options), options)) return false;
420
+ if (success) success(model, resp, options);
342
421
  };
343
- options.error = Backbone.wrapError(options.error, model, options);
344
- return (this.sync || Backbone.sync).call(this, 'read', this, options);
422
+ return this.sync('read', this, options);
345
423
  },
346
424
 
347
425
  // Set a hash of model attributes, and sync the model to the server.
348
426
  // If the server returns an attributes hash that differs, the model's
349
427
  // state will be `set` again.
350
- save: function(key, value, options) {
351
- var attrs, current;
428
+ save: function(key, val, options) {
429
+ var attrs, success, method, xhr, attributes = this.attributes;
352
430
 
353
- // Handle both `("key", value)` and `({key: value})` -style calls.
354
- if (_.isObject(key) || key == null) {
431
+ // Handle both `"key", value` and `{key: value}` -style arguments.
432
+ if (key == null || typeof key === 'object') {
355
433
  attrs = key;
356
- options = value;
434
+ options = val;
357
435
  } else {
358
- attrs = {};
359
- attrs[key] = value;
436
+ (attrs = {})[key] = val;
360
437
  }
361
- options = options ? _.clone(options) : {};
362
438
 
363
- // If we're "wait"-ing to set changed attributes, validate early.
364
- if (options.wait) {
365
- if (!this._validate(attrs, options)) return false;
366
- current = _.clone(this.attributes);
367
- }
439
+ // If we're not waiting and attributes exist, save acts as `set(attr).save(null, opts)`.
440
+ if (attrs && (!options || !options.wait) && !this.set(attrs, options)) return false;
368
441
 
369
- // Regular saves `set` attributes before persisting to the server.
370
- var silentOptions = _.extend({}, options, {silent: true});
371
- if (attrs && !this.set(attrs, options.wait ? silentOptions : options)) {
372
- return false;
442
+ options = _.extend({validate: true}, options);
443
+
444
+ // Do not persist invalid models.
445
+ if (!this._validate(attrs, options)) return false;
446
+
447
+ // Set temporary attributes if `{wait: true}`.
448
+ if (attrs && options.wait) {
449
+ this.attributes = _.extend({}, attributes, attrs);
373
450
  }
374
451
 
375
452
  // After a successful server-side save, the client is (optionally)
376
453
  // updated with the server-side state.
377
- var model = this;
378
- var success = options.success;
379
- options.success = function(resp, status, xhr) {
380
- var serverAttrs = model.parse(resp, xhr);
381
- if (options.wait) {
382
- delete options.wait;
383
- serverAttrs = _.extend(attrs || {}, serverAttrs);
384
- }
385
- if (!model.set(serverAttrs, options)) return false;
386
- if (success) {
387
- success(model, resp);
388
- } else {
389
- model.trigger('sync', model, resp, options);
454
+ if (options.parse === void 0) options.parse = true;
455
+ success = options.success;
456
+ options.success = function(model, resp, options) {
457
+ // Ensure attributes are restored during synchronous saves.
458
+ model.attributes = attributes;
459
+ var serverAttrs = model.parse(resp, options);
460
+ if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
461
+ if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
462
+ return false;
390
463
  }
464
+ if (success) success(model, resp, options);
391
465
  };
392
466
 
393
467
  // Finish configuring and sending the Ajax request.
394
- options.error = Backbone.wrapError(options.error, model, options);
395
- var method = this.isNew() ? 'create' : 'update';
396
- var xhr = (this.sync || Backbone.sync).call(this, method, this, options);
397
- if (options.wait) this.set(current, silentOptions);
468
+ method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
469
+ if (method === 'patch') options.attrs = attrs;
470
+ xhr = this.sync(method, this, options);
471
+
472
+ // Restore attributes.
473
+ if (attrs && options.wait) this.attributes = attributes;
474
+
398
475
  return xhr;
399
476
  },
400
477
 
@@ -406,27 +483,22 @@
406
483
  var model = this;
407
484
  var success = options.success;
408
485
 
409
- var triggerDestroy = function() {
486
+ var destroy = function() {
410
487
  model.trigger('destroy', model, model.collection, options);
411
488
  };
412
489
 
490
+ options.success = function(model, resp, options) {
491
+ if (options.wait || model.isNew()) destroy();
492
+ if (success) success(model, resp, options);
493
+ };
494
+
413
495
  if (this.isNew()) {
414
- triggerDestroy();
496
+ options.success(this, null, options);
415
497
  return false;
416
498
  }
417
499
 
418
- options.success = function(resp) {
419
- if (options.wait) triggerDestroy();
420
- if (success) {
421
- success(model, resp);
422
- } else {
423
- model.trigger('sync', model, resp, options);
424
- }
425
- };
426
-
427
- options.error = Backbone.wrapError(options.error, model, options);
428
- var xhr = (this.sync || Backbone.sync).call(this, 'delete', this, options);
429
- if (!options.wait) triggerDestroy();
500
+ var xhr = this.sync('delete', this, options);
501
+ if (!options.wait) destroy();
430
502
  return xhr;
431
503
  },
432
504
 
@@ -434,14 +506,14 @@
434
506
  // using Backbone's restful methods, override this to change the endpoint
435
507
  // that will be called.
436
508
  url: function() {
437
- var base = getValue(this, 'urlRoot') || getValue(this.collection, 'url') || urlError();
509
+ var base = _.result(this, 'urlRoot') || _.result(this.collection, 'url') || urlError();
438
510
  if (this.isNew()) return base;
439
- return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
511
+ return base + (base.charAt(base.length - 1) === '/' ? '' : '/') + encodeURIComponent(this.id);
440
512
  },
441
513
 
442
514
  // **parse** converts a response into the hash of attributes to be `set` on
443
515
  // the model. The default implementation is just to pass the response along.
444
- parse: function(resp, xhr) {
516
+ parse: function(resp, options) {
445
517
  return resp;
446
518
  },
447
519
 
@@ -455,96 +527,20 @@
455
527
  return this.id == null;
456
528
  },
457
529
 
458
- // Call this method to manually fire a `"change"` event for this model and
459
- // a `"change:attribute"` event for each changed attribute.
460
- // Calling this will cause all objects observing the model to update.
461
- change: function(options) {
462
- options || (options = {});
463
- var changing = this._changing;
464
- this._changing = true;
465
-
466
- // Silent changes become pending changes.
467
- for (var attr in this._silent) this._pending[attr] = true;
468
-
469
- // Silent changes are triggered.
470
- var changes = _.extend({}, options.changes, this._silent);
471
- this._silent = {};
472
- for (var attr in changes) {
473
- this.trigger('change:' + attr, this, this.get(attr), options);
474
- }
475
- if (changing) return this;
476
-
477
- // Continue firing `"change"` events while there are pending changes.
478
- while (!_.isEmpty(this._pending)) {
479
- this._pending = {};
480
- this.trigger('change', this, options);
481
- // Pending and silent changes still remain.
482
- for (var attr in this.changed) {
483
- if (this._pending[attr] || this._silent[attr]) continue;
484
- delete this.changed[attr];
485
- }
486
- this._previousAttributes = _.clone(this.attributes);
487
- }
488
-
489
- this._changing = false;
490
- return this;
491
- },
492
-
493
- // Determine if the model has changed since the last `"change"` event.
494
- // If you specify an attribute name, determine if that attribute has changed.
495
- hasChanged: function(attr) {
496
- if (!arguments.length) return !_.isEmpty(this.changed);
497
- return _.has(this.changed, attr);
498
- },
499
-
500
- // Return an object containing all the attributes that have changed, or
501
- // false if there are no changed attributes. Useful for determining what
502
- // parts of a view need to be updated and/or what attributes need to be
503
- // persisted to the server. Unset attributes will be set to undefined.
504
- // You can also pass an attributes object to diff against the model,
505
- // determining if there *would be* a change.
506
- changedAttributes: function(diff) {
507
- if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
508
- var val, changed = false, old = this._previousAttributes;
509
- for (var attr in diff) {
510
- if (_.isEqual(old[attr], (val = diff[attr]))) continue;
511
- (changed || (changed = {}))[attr] = val;
512
- }
513
- return changed;
514
- },
515
-
516
- // Get the previous value of an attribute, recorded at the time the last
517
- // `"change"` event was fired.
518
- previous: function(attr) {
519
- if (!arguments.length || !this._previousAttributes) return null;
520
- return this._previousAttributes[attr];
521
- },
522
-
523
- // Get all of the attributes of the model at the time of the previous
524
- // `"change"` event.
525
- previousAttributes: function() {
526
- return _.clone(this._previousAttributes);
527
- },
528
-
529
- // Check if the model is currently in a valid state. It's only possible to
530
- // get into an *invalid* state if you're using silent changes.
531
- isValid: function() {
532
- return !this.validate(this.attributes);
530
+ // Check if the model is currently in a valid state.
531
+ isValid: function(options) {
532
+ return !this.validate || !this.validate(this.attributes, options);
533
533
  },
534
534
 
535
535
  // Run validation against the next complete set of model attributes,
536
- // returning `true` if all is well. If a specific `error` callback has
537
- // been passed, call that instead of firing the general `"error"` event.
536
+ // returning `true` if all is well. Otherwise, fire a general
537
+ // `"error"` event and call the error callback, if specified.
538
538
  _validate: function(attrs, options) {
539
- if (options.silent || !this.validate) return true;
539
+ if (!options.validate || !this.validate) return true;
540
540
  attrs = _.extend({}, this.attributes, attrs);
541
- var error = this.validate(attrs, options);
541
+ var error = this.validationError = this.validate(attrs, options) || null;
542
542
  if (!error) return true;
543
- if (options && options.error) {
544
- options.error(this, error, options);
545
- } else {
546
- this.trigger('error', this, error, options);
547
- }
543
+ this.trigger('invalid', this, error, options || {});
548
544
  return false;
549
545
  }
550
546
 
@@ -559,10 +555,11 @@
559
555
  var Collection = Backbone.Collection = function(models, options) {
560
556
  options || (options = {});
561
557
  if (options.model) this.model = options.model;
562
- if (options.comparator) this.comparator = options.comparator;
558
+ if (options.comparator !== void 0) this.comparator = options.comparator;
559
+ this.models = [];
563
560
  this._reset();
564
561
  this.initialize.apply(this, arguments);
565
- if (models) this.reset(models, {silent: true, parse: options.parse});
562
+ if (models) this.reset(models, _.extend({silent: true}, options));
566
563
  };
567
564
 
568
565
  // Define the Collection's inheritable methods.
@@ -582,68 +579,86 @@
582
579
  return this.map(function(model){ return model.toJSON(options); });
583
580
  },
584
581
 
585
- // Add a model, or list of models to the set. Pass **silent** to avoid
586
- // firing the `add` event for every new model.
582
+ // Proxy `Backbone.sync` by default.
583
+ sync: function() {
584
+ return Backbone.sync.apply(this, arguments);
585
+ },
586
+
587
+ // Add a model, or list of models to the set.
587
588
  add: function(models, options) {
588
- var i, index, length, model, cid, id, cids = {}, ids = {}, dups = [];
589
- options || (options = {});
590
589
  models = _.isArray(models) ? models.slice() : [models];
591
-
592
- // Begin by turning bare objects into model references, and preventing
593
- // invalid models or duplicate models from being added.
594
- for (i = 0, length = models.length; i < length; i++) {
595
- if (!(model = models[i] = this._prepareModel(models[i], options))) {
596
- throw new Error("Can't add an invalid model to a collection");
590
+ options || (options = {});
591
+ var i, l, model, attrs, existing, doSort, add, at, sort, sortAttr;
592
+ add = [];
593
+ at = options.at;
594
+ sort = this.comparator && (at == null) && options.sort != false;
595
+ sortAttr = _.isString(this.comparator) ? this.comparator : null;
596
+
597
+ // Turn bare objects into model references, and prevent invalid models
598
+ // from being added.
599
+ for (i = 0, l = models.length; i < l; i++) {
600
+ if (!(model = this._prepareModel(attrs = models[i], options))) {
601
+ this.trigger('invalid', this, attrs, options);
602
+ continue;
597
603
  }
598
- cid = model.cid;
599
- id = model.id;
600
- if (cids[cid] || this._byCid[cid] || ((id != null) && (ids[id] || this._byId[id]))) {
601
- dups.push(i);
604
+
605
+ // If a duplicate is found, prevent it from being added and
606
+ // optionally merge it into the existing model.
607
+ if (existing = this.get(model)) {
608
+ if (options.merge) {
609
+ existing.set(attrs === model ? model.attributes : attrs, options);
610
+ if (sort && !doSort && existing.hasChanged(sortAttr)) doSort = true;
611
+ }
602
612
  continue;
603
613
  }
604
- cids[cid] = ids[id] = model;
605
- }
606
614
 
607
- // Remove duplicates.
608
- i = dups.length;
609
- while (i--) {
610
- models.splice(dups[i], 1);
611
- }
615
+ // This is a new model, push it to the `add` list.
616
+ add.push(model);
612
617
 
613
- // Listen to added models' events, and index models for lookup by
614
- // `id` and by `cid`.
615
- for (i = 0, length = models.length; i < length; i++) {
616
- (model = models[i]).on('all', this._onModelEvent, this);
617
- this._byCid[model.cid] = model;
618
+ // Listen to added models' events, and index models for lookup by
619
+ // `id` and by `cid`.
620
+ model.on('all', this._onModelEvent, this);
621
+ this._byId[model.cid] = model;
618
622
  if (model.id != null) this._byId[model.id] = model;
619
623
  }
620
624
 
621
- // Insert models into the collection, re-sorting if needed, and triggering
622
- // `add` events unless silenced.
623
- this.length += length;
624
- index = options.at != null ? options.at : this.models.length;
625
- splice.apply(this.models, [index, 0].concat(models));
626
- if (this.comparator) this.sort({silent: true});
625
+ // See if sorting is needed, update `length` and splice in new models.
626
+ if (add.length) {
627
+ if (sort) doSort = true;
628
+ this.length += add.length;
629
+ if (at != null) {
630
+ splice.apply(this.models, [at, 0].concat(add));
631
+ } else {
632
+ push.apply(this.models, add);
633
+ }
634
+ }
635
+
636
+ // Silently sort the collection if appropriate.
637
+ if (doSort) this.sort({silent: true});
638
+
627
639
  if (options.silent) return this;
628
- for (i = 0, length = this.models.length; i < length; i++) {
629
- if (!cids[(model = this.models[i]).cid]) continue;
630
- options.index = i;
631
- model.trigger('add', model, this, options);
640
+
641
+ // Trigger `add` events.
642
+ for (i = 0, l = add.length; i < l; i++) {
643
+ (model = add[i]).trigger('add', model, this, options);
632
644
  }
645
+
646
+ // Trigger `sort` if the collection was sorted.
647
+ if (doSort) this.trigger('sort', this, options);
648
+
633
649
  return this;
634
650
  },
635
651
 
636
- // Remove a model, or a list of models from the set. Pass silent to avoid
637
- // firing the `remove` event for every model removed.
652
+ // Remove a model, or a list of models from the set.
638
653
  remove: function(models, options) {
639
- var i, l, index, model;
640
- options || (options = {});
641
654
  models = _.isArray(models) ? models.slice() : [models];
655
+ options || (options = {});
656
+ var i, l, index, model;
642
657
  for (i = 0, l = models.length; i < l; i++) {
643
- model = this.getByCid(models[i]) || this.get(models[i]);
658
+ model = this.get(models[i]);
644
659
  if (!model) continue;
645
660
  delete this._byId[model.id];
646
- delete this._byCid[model.cid];
661
+ delete this._byId[model.cid];
647
662
  index = this.indexOf(model);
648
663
  this.models.splice(index, 1);
649
664
  this.length--;
@@ -659,7 +674,7 @@
659
674
  // Add a model to the end of the collection.
660
675
  push: function(model, options) {
661
676
  model = this._prepareModel(model, options);
662
- this.add(model, options);
677
+ this.add(model, _.extend({at: this.length}, options));
663
678
  return model;
664
679
  },
665
680
 
@@ -684,15 +699,16 @@
684
699
  return model;
685
700
  },
686
701
 
687
- // Get a model from the set by id.
688
- get: function(id) {
689
- if (id == null) return void 0;
690
- return this._byId[id.id != null ? id.id : id];
702
+ // Slice out a sub-array of models from the collection.
703
+ slice: function(begin, end) {
704
+ return this.models.slice(begin, end);
691
705
  },
692
706
 
693
- // Get a model from the set by client id.
694
- getByCid: function(cid) {
695
- return cid && this._byCid[cid.cid || cid];
707
+ // Get a model from the set by id.
708
+ get: function(obj) {
709
+ if (obj == null) return void 0;
710
+ this._idAttr || (this._idAttr = this.model.prototype.idAttribute);
711
+ return this._byId[obj.id || obj.cid || obj[this._idAttr] || obj];
696
712
  },
697
713
 
698
714
  // Get the model at the given index.
@@ -715,71 +731,106 @@
715
731
  // normal circumstances, as the set will maintain sort order as each item
716
732
  // is added.
717
733
  sort: function(options) {
734
+ if (!this.comparator) {
735
+ throw new Error('Cannot sort a set without a comparator');
736
+ }
718
737
  options || (options = {});
719
- if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
720
- var boundComparator = _.bind(this.comparator, this);
721
- if (this.comparator.length == 1) {
722
- this.models = this.sortBy(boundComparator);
738
+
739
+ // Run sort based on type of `comparator`.
740
+ if (_.isString(this.comparator) || this.comparator.length === 1) {
741
+ this.models = this.sortBy(this.comparator, this);
723
742
  } else {
724
- this.models.sort(boundComparator);
743
+ this.models.sort(_.bind(this.comparator, this));
725
744
  }
726
- if (!options.silent) this.trigger('reset', this, options);
745
+
746
+ if (!options.silent) this.trigger('sort', this, options);
727
747
  return this;
728
748
  },
729
749
 
730
750
  // Pluck an attribute from each model in the collection.
731
751
  pluck: function(attr) {
732
- return _.map(this.models, function(model){ return model.get(attr); });
752
+ return _.invoke(this.models, 'get', attr);
753
+ },
754
+
755
+ // Smartly update a collection with a change set of models, adding,
756
+ // removing, and merging as necessary.
757
+ update: function(models, options) {
758
+ options = _.extend({add: true, merge: true, remove: true}, options);
759
+ if (options.parse) models = this.parse(models, options);
760
+ var model, i, l, existing;
761
+ var add = [], remove = [], modelMap = {};
762
+
763
+ // Allow a single model (or no argument) to be passed.
764
+ if (!_.isArray(models)) models = models ? [models] : [];
765
+
766
+ // Proxy to `add` for this case, no need to iterate...
767
+ if (options.add && !options.remove) return this.add(models, options);
768
+
769
+ // Determine which models to add and merge, and which to remove.
770
+ for (i = 0, l = models.length; i < l; i++) {
771
+ model = models[i];
772
+ existing = this.get(model);
773
+ if (options.remove && existing) modelMap[existing.cid] = true;
774
+ if ((options.add && !existing) || (options.merge && existing)) {
775
+ add.push(model);
776
+ }
777
+ }
778
+ if (options.remove) {
779
+ for (i = 0, l = this.models.length; i < l; i++) {
780
+ model = this.models[i];
781
+ if (!modelMap[model.cid]) remove.push(model);
782
+ }
783
+ }
784
+
785
+ // Remove models (if applicable) before we add and merge the rest.
786
+ if (remove.length) this.remove(remove, options);
787
+ if (add.length) this.add(add, options);
788
+ return this;
733
789
  },
734
790
 
735
791
  // When you have more items than you want to add or remove individually,
736
792
  // you can reset the entire set with a new list of models, without firing
737
793
  // any `add` or `remove` events. Fires `reset` when finished.
738
794
  reset: function(models, options) {
739
- models || (models = []);
740
795
  options || (options = {});
796
+ if (options.parse) models = this.parse(models, options);
741
797
  for (var i = 0, l = this.models.length; i < l; i++) {
742
798
  this._removeReference(this.models[i]);
743
799
  }
800
+ options.previousModels = this.models.slice();
744
801
  this._reset();
745
- this.add(models, _.extend({silent: true}, options));
802
+ if (models) this.add(models, _.extend({silent: true}, options));
746
803
  if (!options.silent) this.trigger('reset', this, options);
747
804
  return this;
748
805
  },
749
806
 
750
807
  // Fetch the default set of models for this collection, resetting the
751
- // collection when they arrive. If `add: true` is passed, appends the
752
- // models to the collection instead of resetting.
808
+ // collection when they arrive. If `update: true` is passed, the response
809
+ // data will be passed through the `update` method instead of `reset`.
753
810
  fetch: function(options) {
754
811
  options = options ? _.clone(options) : {};
755
- if (options.parse === undefined) options.parse = true;
756
- var collection = this;
812
+ if (options.parse === void 0) options.parse = true;
757
813
  var success = options.success;
758
- options.success = function(resp, status, xhr) {
759
- collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
760
- if (success) success(collection, resp);
814
+ options.success = function(collection, resp, options) {
815
+ var method = options.update ? 'update' : 'reset';
816
+ collection[method](resp, options);
817
+ if (success) success(collection, resp, options);
761
818
  };
762
- options.error = Backbone.wrapError(options.error, collection, options);
763
- return (this.sync || Backbone.sync).call(this, 'read', this, options);
819
+ return this.sync('read', this, options);
764
820
  },
765
821
 
766
822
  // Create a new instance of a model in this collection. Add the model to the
767
823
  // collection immediately, unless `wait: true` is passed, in which case we
768
824
  // wait for the server to agree.
769
825
  create: function(model, options) {
770
- var coll = this;
771
826
  options = options ? _.clone(options) : {};
772
- model = this._prepareModel(model, options);
773
- if (!model) return false;
774
- if (!options.wait) coll.add(model, options);
827
+ if (!(model = this._prepareModel(model, options))) return false;
828
+ if (!options.wait) this.add(model, options);
829
+ var collection = this;
775
830
  var success = options.success;
776
- options.success = function(nextModel, resp, xhr) {
777
- if (options.wait) coll.add(nextModel, options);
778
- if (success) {
779
- success(nextModel, resp);
780
- } else {
781
- nextModel.trigger('sync', model, resp, options);
782
- }
831
+ options.success = function(model, resp, options) {
832
+ if (options.wait) collection.add(model, options);
833
+ if (success) success(model, resp, options);
783
834
  };
784
835
  model.save(null, options);
785
836
  return model;
@@ -787,44 +838,38 @@
787
838
 
788
839
  // **parse** converts a response into a list of models to be added to the
789
840
  // collection. The default implementation is just to pass it through.
790
- parse: function(resp, xhr) {
841
+ parse: function(resp, options) {
791
842
  return resp;
792
843
  },
793
844
 
794
- // Proxy to _'s chain. Can't be proxied the same way the rest of the
795
- // underscore methods are proxied because it relies on the underscore
796
- // constructor.
797
- chain: function () {
798
- return _(this.models).chain();
845
+ // Create a new collection with an identical list of models as this one.
846
+ clone: function() {
847
+ return new this.constructor(this.models);
799
848
  },
800
849
 
801
850
  // Reset all internal state. Called when the collection is reset.
802
- _reset: function(options) {
851
+ _reset: function() {
803
852
  this.length = 0;
804
- this.models = [];
853
+ this.models.length = 0;
805
854
  this._byId = {};
806
- this._byCid = {};
807
855
  },
808
856
 
809
857
  // Prepare a model or hash of attributes to be added to this collection.
810
- _prepareModel: function(model, options) {
811
- options || (options = {});
812
- if (!(model instanceof Model)) {
813
- var attrs = model;
814
- options.collection = this;
815
- model = new this.model(attrs, options);
816
- if (!model._validate(model.attributes, options)) model = false;
817
- } else if (!model.collection) {
818
- model.collection = this;
858
+ _prepareModel: function(attrs, options) {
859
+ if (attrs instanceof Model) {
860
+ if (!attrs.collection) attrs.collection = this;
861
+ return attrs;
819
862
  }
863
+ options || (options = {});
864
+ options.collection = this;
865
+ var model = new this.model(attrs, options);
866
+ if (!model._validate(attrs, options)) return false;
820
867
  return model;
821
868
  },
822
869
 
823
870
  // Internal method to remove a model's ties to a collection.
824
871
  _removeReference: function(model) {
825
- if (this == model.collection) {
826
- delete model.collection;
827
- }
872
+ if (this === model.collection) delete model.collection;
828
873
  model.off('all', this._onModelEvent, this);
829
874
  },
830
875
 
@@ -833,35 +878,57 @@
833
878
  // events simply proxy through. "add" and "remove" events that originate
834
879
  // in other collections are ignored.
835
880
  _onModelEvent: function(event, model, collection, options) {
836
- if ((event == 'add' || event == 'remove') && collection != this) return;
837
- if (event == 'destroy') {
838
- this.remove(model, options);
839
- }
881
+ if ((event === 'add' || event === 'remove') && collection !== this) return;
882
+ if (event === 'destroy') this.remove(model, options);
840
883
  if (model && event === 'change:' + model.idAttribute) {
841
884
  delete this._byId[model.previous(model.idAttribute)];
842
- this._byId[model.id] = model;
885
+ if (model.id != null) this._byId[model.id] = model;
843
886
  }
844
887
  this.trigger.apply(this, arguments);
888
+ },
889
+
890
+ sortedIndex: function (model, value, context) {
891
+ value || (value = this.comparator);
892
+ var iterator = _.isFunction(value) ? value : function(model) {
893
+ return model.get(value);
894
+ };
895
+ return _.sortedIndex(this.models, model, iterator, context);
845
896
  }
846
897
 
847
898
  });
848
899
 
849
900
  // Underscore methods that we want to implement on the Collection.
850
- var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find',
851
- 'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any',
852
- 'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex',
853
- 'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf',
854
- 'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy'];
901
+ var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl',
902
+ 'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select',
903
+ 'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
904
+ 'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest',
905
+ 'tail', 'drop', 'last', 'without', 'indexOf', 'shuffle', 'lastIndexOf',
906
+ 'isEmpty', 'chain'];
855
907
 
856
908
  // Mix in each Underscore method as a proxy to `Collection#models`.
857
909
  _.each(methods, function(method) {
858
910
  Collection.prototype[method] = function() {
859
- return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
911
+ var args = slice.call(arguments);
912
+ args.unshift(this.models);
913
+ return _[method].apply(_, args);
914
+ };
915
+ });
916
+
917
+ // Underscore methods that take a property name as an argument.
918
+ var attributeMethods = ['groupBy', 'countBy', 'sortBy'];
919
+
920
+ // Use attributes instead of properties.
921
+ _.each(attributeMethods, function(method) {
922
+ Collection.prototype[method] = function(value, context) {
923
+ var iterator = _.isFunction(value) ? value : function(model) {
924
+ return model.get(value);
925
+ };
926
+ return _[method](this.models, iterator, context);
860
927
  };
861
928
  });
862
929
 
863
930
  // Backbone.Router
864
- // -------------------
931
+ // ---------------
865
932
 
866
933
  // Routers map faux-URLs to actions, and fire events when routes are
867
934
  // matched. Creating a new one sets its `routes` hash, if not set statically.
@@ -874,9 +941,10 @@
874
941
 
875
942
  // Cached regular expressions for matching named param parts and splatted
876
943
  // parts of route strings.
877
- var namedParam = /:\w+/g;
944
+ var optionalParam = /\((.*?)\)/g;
945
+ var namedParam = /(\(\?)?:\w+/g;
878
946
  var splatParam = /\*\w+/g;
879
- var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g;
947
+ var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g;
880
948
 
881
949
  // Set up all inheritable **Backbone.Router** properties and methods.
882
950
  _.extend(Router.prototype, Events, {
@@ -892,13 +960,13 @@
892
960
  // });
893
961
  //
894
962
  route: function(route, name, callback) {
895
- Backbone.history || (Backbone.history = new History);
896
963
  if (!_.isRegExp(route)) route = this._routeToRegExp(route);
897
964
  if (!callback) callback = this[name];
898
965
  Backbone.history.route(route, _.bind(function(fragment) {
899
966
  var args = this._extractParameters(route, fragment);
900
967
  callback && callback.apply(this, args);
901
968
  this.trigger.apply(this, ['route:' + name].concat(args));
969
+ this.trigger('route', name, args);
902
970
  Backbone.history.trigger('route', this, name, args);
903
971
  }, this));
904
972
  return this;
@@ -907,6 +975,7 @@
907
975
  // Simple proxy to `Backbone.history` to save a fragment into the history.
908
976
  navigate: function(fragment, options) {
909
977
  Backbone.history.navigate(fragment, options);
978
+ return this;
910
979
  },
911
980
 
912
981
  // Bind all defined routes to `Backbone.history`. We have to reverse the
@@ -914,12 +983,9 @@
914
983
  // routes can be defined at the bottom of the route map.
915
984
  _bindRoutes: function() {
916
985
  if (!this.routes) return;
917
- var routes = [];
918
- for (var route in this.routes) {
919
- routes.unshift([route, this.routes[route]]);
920
- }
921
- for (var i = 0, l = routes.length; i < l; i++) {
922
- this.route(routes[i][0], routes[i][1], this[routes[i][1]]);
986
+ var route, routes = _.keys(this.routes);
987
+ while ((route = routes.pop()) != null) {
988
+ this.route(route, this.routes[route]);
923
989
  }
924
990
  },
925
991
 
@@ -927,7 +993,10 @@
927
993
  // against the current location hash.
928
994
  _routeToRegExp: function(route) {
929
995
  route = route.replace(escapeRegExp, '\\$&')
930
- .replace(namedParam, '([^\/]+)')
996
+ .replace(optionalParam, '(?:$1)?')
997
+ .replace(namedParam, function(match, optional){
998
+ return optional ? match : '([^\/]+)';
999
+ })
931
1000
  .replace(splatParam, '(.*?)');
932
1001
  return new RegExp('^' + route + '$');
933
1002
  },
@@ -948,14 +1017,26 @@
948
1017
  var History = Backbone.History = function() {
949
1018
  this.handlers = [];
950
1019
  _.bindAll(this, 'checkUrl');
1020
+
1021
+ // Ensure that `History` can be used outside of the browser.
1022
+ if (typeof window !== 'undefined') {
1023
+ this.location = window.location;
1024
+ this.history = window.history;
1025
+ }
951
1026
  };
952
1027
 
953
- // Cached regex for cleaning leading hashes and slashes .
954
- var routeStripper = /^[#\/]/;
1028
+ // Cached regex for stripping a leading hash/slash and trailing space.
1029
+ var routeStripper = /^[#\/]|\s+$/g;
1030
+
1031
+ // Cached regex for stripping leading and trailing slashes.
1032
+ var rootStripper = /^\/+|\/+$/g;
955
1033
 
956
1034
  // Cached regex for detecting MSIE.
957
1035
  var isExplorer = /msie [\w.]+/;
958
1036
 
1037
+ // Cached regex for removing a trailing slash.
1038
+ var trailingSlash = /\/$/;
1039
+
959
1040
  // Has the history handling already been started?
960
1041
  History.started = false;
961
1042
 
@@ -968,9 +1049,8 @@
968
1049
 
969
1050
  // Gets the true hash value. Cannot use location.hash directly due to bug
970
1051
  // in Firefox where location.hash will always be decoded.
971
- getHash: function(windowOverride) {
972
- var loc = windowOverride ? windowOverride.location : window.location;
973
- var match = loc.href.match(/#(.*)$/);
1052
+ getHash: function(window) {
1053
+ var match = (window || this).location.href.match(/#(.*)$/);
974
1054
  return match ? match[1] : '';
975
1055
  },
976
1056
 
@@ -978,15 +1058,14 @@
978
1058
  // the hash, or the override.
979
1059
  getFragment: function(fragment, forcePushState) {
980
1060
  if (fragment == null) {
981
- if (this._hasPushState || forcePushState) {
982
- fragment = window.location.pathname;
983
- var search = window.location.search;
984
- if (search) fragment += search;
1061
+ if (this._hasPushState || !this._wantsHashChange || forcePushState) {
1062
+ fragment = this.location.pathname;
1063
+ var root = this.root.replace(trailingSlash, '');
1064
+ if (!fragment.indexOf(root)) fragment = fragment.substr(root.length);
985
1065
  } else {
986
1066
  fragment = this.getHash();
987
1067
  }
988
1068
  }
989
- if (!fragment.indexOf(this.options.root)) fragment = fragment.substr(this.options.root.length);
990
1069
  return fragment.replace(routeStripper, '');
991
1070
  },
992
1071
 
@@ -999,24 +1078,28 @@
999
1078
  // Figure out the initial configuration. Do we need an iframe?
1000
1079
  // Is pushState desired ... is it available?
1001
1080
  this.options = _.extend({}, {root: '/'}, this.options, options);
1081
+ this.root = this.options.root;
1002
1082
  this._wantsHashChange = this.options.hashChange !== false;
1003
1083
  this._wantsPushState = !!this.options.pushState;
1004
- this._hasPushState = !!(this.options.pushState && window.history && window.history.pushState);
1084
+ this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState);
1005
1085
  var fragment = this.getFragment();
1006
1086
  var docMode = document.documentMode;
1007
1087
  var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
1008
1088
 
1009
- if (oldIE) {
1010
- this.iframe = $('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
1089
+ // Normalize root to always include a leading and trailing slash.
1090
+ this.root = ('/' + this.root + '/').replace(rootStripper, '/');
1091
+
1092
+ if (oldIE && this._wantsHashChange) {
1093
+ this.iframe = Backbone.$('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
1011
1094
  this.navigate(fragment);
1012
1095
  }
1013
1096
 
1014
1097
  // Depending on whether we're using pushState or hashes, and whether
1015
1098
  // 'onhashchange' is supported, determine how we check the URL state.
1016
1099
  if (this._hasPushState) {
1017
- $(window).bind('popstate', this.checkUrl);
1100
+ Backbone.$(window).on('popstate', this.checkUrl);
1018
1101
  } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
1019
- $(window).bind('hashchange', this.checkUrl);
1102
+ Backbone.$(window).on('hashchange', this.checkUrl);
1020
1103
  } else if (this._wantsHashChange) {
1021
1104
  this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
1022
1105
  }
@@ -1024,14 +1107,14 @@
1024
1107
  // Determine if we need to change the base url, for a pushState link
1025
1108
  // opened by a non-pushState browser.
1026
1109
  this.fragment = fragment;
1027
- var loc = window.location;
1028
- var atRoot = loc.pathname == this.options.root;
1110
+ var loc = this.location;
1111
+ var atRoot = loc.pathname.replace(/[^\/]$/, '$&/') === this.root;
1029
1112
 
1030
1113
  // If we've started off with a route from a `pushState`-enabled browser,
1031
1114
  // but we're currently in a browser that doesn't support it...
1032
1115
  if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) {
1033
1116
  this.fragment = this.getFragment(null, true);
1034
- window.location.replace(this.options.root + '#' + this.fragment);
1117
+ this.location.replace(this.root + this.location.search + '#' + this.fragment);
1035
1118
  // Return immediately as browser will do redirect to new url
1036
1119
  return true;
1037
1120
 
@@ -1039,18 +1122,16 @@
1039
1122
  // in a browser where it could be `pushState`-based instead...
1040
1123
  } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
1041
1124
  this.fragment = this.getHash().replace(routeStripper, '');
1042
- window.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment);
1125
+ this.history.replaceState({}, document.title, this.root + this.fragment + loc.search);
1043
1126
  }
1044
1127
 
1045
- if (!this.options.silent) {
1046
- return this.loadUrl();
1047
- }
1128
+ if (!this.options.silent) return this.loadUrl();
1048
1129
  },
1049
1130
 
1050
1131
  // Disable Backbone.history, perhaps temporarily. Not useful in a real app,
1051
1132
  // but possibly useful for unit testing Routers.
1052
1133
  stop: function() {
1053
- $(window).unbind('popstate', this.checkUrl).unbind('hashchange', this.checkUrl);
1134
+ Backbone.$(window).off('popstate', this.checkUrl).off('hashchange', this.checkUrl);
1054
1135
  clearInterval(this._checkUrlInterval);
1055
1136
  History.started = false;
1056
1137
  },
@@ -1065,8 +1146,10 @@
1065
1146
  // calls `loadUrl`, normalizing across the hidden iframe.
1066
1147
  checkUrl: function(e) {
1067
1148
  var current = this.getFragment();
1068
- if (current == this.fragment && this.iframe) current = this.getFragment(this.getHash(this.iframe));
1069
- if (current == this.fragment) return false;
1149
+ if (current === this.fragment && this.iframe) {
1150
+ current = this.getFragment(this.getHash(this.iframe));
1151
+ }
1152
+ if (current === this.fragment) return false;
1070
1153
  if (this.iframe) this.navigate(current);
1071
1154
  this.loadUrl() || this.loadUrl(this.getHash());
1072
1155
  },
@@ -1095,31 +1178,31 @@
1095
1178
  navigate: function(fragment, options) {
1096
1179
  if (!History.started) return false;
1097
1180
  if (!options || options === true) options = {trigger: options};
1098
- var frag = (fragment || '').replace(routeStripper, '');
1099
- if (this.fragment == frag) return;
1181
+ fragment = this.getFragment(fragment || '');
1182
+ if (this.fragment === fragment) return;
1183
+ this.fragment = fragment;
1184
+ var url = this.root + fragment;
1100
1185
 
1101
1186
  // If pushState is available, we use it to set the fragment as a real URL.
1102
1187
  if (this._hasPushState) {
1103
- if (frag.indexOf(this.options.root) != 0) frag = this.options.root + frag;
1104
- this.fragment = frag;
1105
- window.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, frag);
1188
+ this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url);
1106
1189
 
1107
1190
  // If hash changes haven't been explicitly disabled, update the hash
1108
1191
  // fragment to store history.
1109
1192
  } else if (this._wantsHashChange) {
1110
- this.fragment = frag;
1111
- this._updateHash(window.location, frag, options.replace);
1112
- if (this.iframe && (frag != this.getFragment(this.getHash(this.iframe)))) {
1113
- // Opening and closing the iframe tricks IE7 and earlier to push a history entry on hash-tag change.
1114
- // When replace is true, we don't want this.
1193
+ this._updateHash(this.location, fragment, options.replace);
1194
+ if (this.iframe && (fragment !== this.getFragment(this.getHash(this.iframe)))) {
1195
+ // Opening and closing the iframe tricks IE7 and earlier to push a
1196
+ // history entry on hash-tag change. When replace is true, we don't
1197
+ // want this.
1115
1198
  if(!options.replace) this.iframe.document.open().close();
1116
- this._updateHash(this.iframe.location, frag, options.replace);
1199
+ this._updateHash(this.iframe.location, fragment, options.replace);
1117
1200
  }
1118
1201
 
1119
1202
  // If you've told us that you explicitly don't want fallback hashchange-
1120
1203
  // based history, then `navigate` becomes a page refresh.
1121
1204
  } else {
1122
- window.location.assign(this.options.root + fragment);
1205
+ return this.location.assign(url);
1123
1206
  }
1124
1207
  if (options.trigger) this.loadUrl(fragment);
1125
1208
  },
@@ -1128,13 +1211,19 @@
1128
1211
  // a new one to the browser history.
1129
1212
  _updateHash: function(location, fragment, replace) {
1130
1213
  if (replace) {
1131
- location.replace(location.toString().replace(/(javascript:|#).*$/, '') + '#' + fragment);
1214
+ var href = location.href.replace(/(javascript:|#).*$/, '');
1215
+ location.replace(href + '#' + fragment);
1132
1216
  } else {
1133
- location.hash = fragment;
1217
+ // Some browsers require that `hash` contains a leading #.
1218
+ location.hash = '#' + fragment;
1134
1219
  }
1135
1220
  }
1221
+
1136
1222
  });
1137
1223
 
1224
+ // Create the default Backbone.history.
1225
+ Backbone.history = new History;
1226
+
1138
1227
  // Backbone.View
1139
1228
  // -------------
1140
1229
 
@@ -1152,7 +1241,7 @@
1152
1241
  var delegateEventSplitter = /^(\S+)\s*(.*)$/;
1153
1242
 
1154
1243
  // List of view options to be merged as properties.
1155
- var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];
1244
+ var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
1156
1245
 
1157
1246
  // Set up all inheritable **Backbone.View** properties and methods.
1158
1247
  _.extend(View.prototype, Events, {
@@ -1177,30 +1266,19 @@
1177
1266
  return this;
1178
1267
  },
1179
1268
 
1180
- // Remove this view from the DOM. Note that the view isn't present in the
1181
- // DOM by default, so calling this method may be a no-op.
1269
+ // Remove this view by taking the element out of the DOM, and removing any
1270
+ // applicable Backbone.Events listeners.
1182
1271
  remove: function() {
1183
1272
  this.$el.remove();
1273
+ this.stopListening();
1184
1274
  return this;
1185
1275
  },
1186
1276
 
1187
- // For small amounts of DOM Elements, where a full-blown template isn't
1188
- // needed, use **make** to manufacture elements, one at a time.
1189
- //
1190
- // var el = this.make('li', {'class': 'row'}, this.model.escape('title'));
1191
- //
1192
- make: function(tagName, attributes, content) {
1193
- var el = document.createElement(tagName);
1194
- if (attributes) $(el).attr(attributes);
1195
- if (content) $(el).html(content);
1196
- return el;
1197
- },
1198
-
1199
1277
  // Change the view's element (`this.el` property), including event
1200
1278
  // re-delegation.
1201
1279
  setElement: function(element, delegate) {
1202
1280
  if (this.$el) this.undelegateEvents();
1203
- this.$el = (element instanceof $) ? element : $(element);
1281
+ this.$el = element instanceof Backbone.$ ? element : Backbone.$(element);
1204
1282
  this.el = this.$el[0];
1205
1283
  if (delegate !== false) this.delegateEvents();
1206
1284
  return this;
@@ -1222,7 +1300,7 @@
1222
1300
  // This only works for delegate-able events: not `focus`, `blur`, and
1223
1301
  // not `change`, `submit`, and `reset` in Internet Explorer.
1224
1302
  delegateEvents: function(events) {
1225
- if (!(events || (events = getValue(this, 'events')))) return;
1303
+ if (!(events || (events = _.result(this, 'events')))) return;
1226
1304
  this.undelegateEvents();
1227
1305
  for (var key in events) {
1228
1306
  var method = events[key];
@@ -1233,9 +1311,9 @@
1233
1311
  method = _.bind(method, this);
1234
1312
  eventName += '.delegateEvents' + this.cid;
1235
1313
  if (selector === '') {
1236
- this.$el.bind(eventName, method);
1314
+ this.$el.on(eventName, method);
1237
1315
  } else {
1238
- this.$el.delegate(selector, eventName, method);
1316
+ this.$el.on(eventName, selector, method);
1239
1317
  }
1240
1318
  }
1241
1319
  },
@@ -1244,18 +1322,15 @@
1244
1322
  // You usually don't need to use this, but may wish to if you have multiple
1245
1323
  // Backbone views attached to the same DOM element.
1246
1324
  undelegateEvents: function() {
1247
- this.$el.unbind('.delegateEvents' + this.cid);
1325
+ this.$el.off('.delegateEvents' + this.cid);
1248
1326
  },
1249
1327
 
1250
1328
  // Performs the initial configuration of a View with a set of options.
1251
1329
  // Keys with special meaning *(model, collection, id, className)*, are
1252
1330
  // attached directly to the view.
1253
1331
  _configure: function(options) {
1254
- if (this.options) options = _.extend({}, this.options, options);
1255
- for (var i = 0, l = viewOptions.length; i < l; i++) {
1256
- var attr = viewOptions[i];
1257
- if (options[attr]) this[attr] = options[attr];
1258
- }
1332
+ if (this.options) options = _.extend({}, _.result(this, 'options'), options);
1333
+ _.extend(this, _.pick(options, viewOptions));
1259
1334
  this.options = options;
1260
1335
  },
1261
1336
 
@@ -1265,27 +1340,18 @@
1265
1340
  // an element from the `id`, `className` and `tagName` properties.
1266
1341
  _ensureElement: function() {
1267
1342
  if (!this.el) {
1268
- var attrs = getValue(this, 'attributes') || {};
1269
- if (this.id) attrs.id = this.id;
1270
- if (this.className) attrs['class'] = this.className;
1271
- this.setElement(this.make(this.tagName, attrs), false);
1343
+ var attrs = _.extend({}, _.result(this, 'attributes'));
1344
+ if (this.id) attrs.id = _.result(this, 'id');
1345
+ if (this.className) attrs['class'] = _.result(this, 'className');
1346
+ var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs);
1347
+ this.setElement($el, false);
1272
1348
  } else {
1273
- this.setElement(this.el, false);
1349
+ this.setElement(_.result(this, 'el'), false);
1274
1350
  }
1275
1351
  }
1276
1352
 
1277
1353
  });
1278
1354
 
1279
- // The self-propagating extend function that Backbone classes use.
1280
- var extend = function (protoProps, classProps) {
1281
- var child = inherits(this, protoProps, classProps);
1282
- child.extend = this.extend;
1283
- return child;
1284
- };
1285
-
1286
- // Set up inheritance for the model, collection, and view.
1287
- Model.extend = Collection.extend = Router.extend = View.extend = extend;
1288
-
1289
1355
  // Backbone.sync
1290
1356
  // -------------
1291
1357
 
@@ -1293,6 +1359,7 @@
1293
1359
  var methodMap = {
1294
1360
  'create': 'POST',
1295
1361
  'update': 'PUT',
1362
+ 'patch': 'PATCH',
1296
1363
  'delete': 'DELETE',
1297
1364
  'read': 'GET'
1298
1365
  };
@@ -1316,112 +1383,112 @@
1316
1383
  var type = methodMap[method];
1317
1384
 
1318
1385
  // Default options, unless specified.
1319
- options || (options = {});
1386
+ _.defaults(options || (options = {}), {
1387
+ emulateHTTP: Backbone.emulateHTTP,
1388
+ emulateJSON: Backbone.emulateJSON
1389
+ });
1320
1390
 
1321
1391
  // Default JSON-request options.
1322
1392
  var params = {type: type, dataType: 'json'};
1323
1393
 
1324
1394
  // Ensure that we have a URL.
1325
1395
  if (!options.url) {
1326
- params.url = getValue(model, 'url') || urlError();
1396
+ params.url = _.result(model, 'url') || urlError();
1327
1397
  }
1328
1398
 
1329
1399
  // Ensure that we have the appropriate request data.
1330
- if (!options.data && model && (method == 'create' || method == 'update')) {
1400
+ if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
1331
1401
  params.contentType = 'application/json';
1332
- params.data = JSON.stringify(model.toJSON());
1402
+ params.data = JSON.stringify(options.attrs || model.toJSON(options));
1333
1403
  }
1334
1404
 
1335
1405
  // For older servers, emulate JSON by encoding the request into an HTML-form.
1336
- if (Backbone.emulateJSON) {
1406
+ if (options.emulateJSON) {
1337
1407
  params.contentType = 'application/x-www-form-urlencoded';
1338
1408
  params.data = params.data ? {model: params.data} : {};
1339
1409
  }
1340
1410
 
1341
1411
  // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
1342
1412
  // And an `X-HTTP-Method-Override` header.
1343
- if (Backbone.emulateHTTP) {
1344
- if (type === 'PUT' || type === 'DELETE') {
1345
- if (Backbone.emulateJSON) params.data._method = type;
1346
- params.type = 'POST';
1347
- params.beforeSend = function(xhr) {
1348
- xhr.setRequestHeader('X-HTTP-Method-Override', type);
1349
- };
1350
- }
1413
+ if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
1414
+ params.type = 'POST';
1415
+ if (options.emulateJSON) params.data._method = type;
1416
+ var beforeSend = options.beforeSend;
1417
+ options.beforeSend = function(xhr) {
1418
+ xhr.setRequestHeader('X-HTTP-Method-Override', type);
1419
+ if (beforeSend) return beforeSend.apply(this, arguments);
1420
+ };
1351
1421
  }
1352
1422
 
1353
1423
  // Don't process data on a non-GET request.
1354
- if (params.type !== 'GET' && !Backbone.emulateJSON) {
1424
+ if (params.type !== 'GET' && !options.emulateJSON) {
1355
1425
  params.processData = false;
1356
1426
  }
1357
1427
 
1358
- // Make the request, allowing the user to override any Ajax options.
1359
- return $.ajax(_.extend(params, options));
1360
- };
1428
+ var success = options.success;
1429
+ options.success = function(resp) {
1430
+ if (success) success(model, resp, options);
1431
+ model.trigger('sync', model, resp, options);
1432
+ };
1361
1433
 
1362
- // Wrap an optional error callback with a fallback error event.
1363
- Backbone.wrapError = function(onError, originalModel, options) {
1364
- return function(model, resp) {
1365
- resp = model === originalModel ? resp : model;
1366
- if (onError) {
1367
- onError(originalModel, resp, options);
1368
- } else {
1369
- originalModel.trigger('error', originalModel, resp, options);
1370
- }
1434
+ var error = options.error;
1435
+ options.error = function(xhr) {
1436
+ if (error) error(model, xhr, options);
1437
+ model.trigger('error', model, xhr, options);
1371
1438
  };
1439
+
1440
+ // Make the request, allowing the user to override any Ajax options.
1441
+ var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
1442
+ model.trigger('request', model, xhr, options);
1443
+ return xhr;
1444
+ };
1445
+
1446
+ // Set the default implementation of `Backbone.ajax` to proxy through to `$`.
1447
+ Backbone.ajax = function() {
1448
+ return Backbone.$.ajax.apply(Backbone.$, arguments);
1372
1449
  };
1373
1450
 
1374
1451
  // Helpers
1375
1452
  // -------
1376
1453
 
1377
- // Shared empty constructor function to aid in prototype-chain creation.
1378
- var ctor = function(){};
1379
-
1380
1454
  // Helper function to correctly set up the prototype chain, for subclasses.
1381
1455
  // Similar to `goog.inherits`, but uses a hash of prototype properties and
1382
1456
  // class properties to be extended.
1383
- var inherits = function(parent, protoProps, staticProps) {
1457
+ var extend = function(protoProps, staticProps) {
1458
+ var parent = this;
1384
1459
  var child;
1385
1460
 
1386
1461
  // The constructor function for the new subclass is either defined by you
1387
1462
  // (the "constructor" property in your `extend` definition), or defaulted
1388
1463
  // by us to simply call the parent's constructor.
1389
- if (protoProps && protoProps.hasOwnProperty('constructor')) {
1464
+ if (protoProps && _.has(protoProps, 'constructor')) {
1390
1465
  child = protoProps.constructor;
1391
1466
  } else {
1392
- child = function(){ parent.apply(this, arguments); };
1467
+ child = function(){ return parent.apply(this, arguments); };
1393
1468
  }
1394
1469
 
1395
- // Inherit class (static) properties from parent.
1396
- _.extend(child, parent);
1470
+ // Add static properties to the constructor function, if supplied.
1471
+ _.extend(child, parent, staticProps);
1397
1472
 
1398
1473
  // Set the prototype chain to inherit from `parent`, without calling
1399
1474
  // `parent`'s constructor function.
1400
- ctor.prototype = parent.prototype;
1401
- child.prototype = new ctor();
1475
+ var Surrogate = function(){ this.constructor = child; };
1476
+ Surrogate.prototype = parent.prototype;
1477
+ child.prototype = new Surrogate;
1402
1478
 
1403
1479
  // Add prototype properties (instance properties) to the subclass,
1404
1480
  // if supplied.
1405
1481
  if (protoProps) _.extend(child.prototype, protoProps);
1406
1482
 
1407
- // Add static properties to the constructor function, if supplied.
1408
- if (staticProps) _.extend(child, staticProps);
1409
-
1410
- // Correctly set child's `prototype.constructor`.
1411
- child.prototype.constructor = child;
1412
-
1413
- // Set a convenience property in case the parent's prototype is needed later.
1483
+ // Set a convenience property in case the parent's prototype is needed
1484
+ // later.
1414
1485
  child.__super__ = parent.prototype;
1415
1486
 
1416
1487
  return child;
1417
1488
  };
1418
1489
 
1419
- // Helper function to get a value from a Backbone object as a property
1420
- // or as a function.
1421
- var getValue = function(object, prop) {
1422
- if (!(object && object[prop])) return null;
1423
- return _.isFunction(object[prop]) ? object[prop]() : object[prop];
1424
- };
1490
+ // Set up inheritance for the model, collection, router, view and history.
1491
+ Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;
1425
1492
 
1426
1493
  // Throw an error when a URL is needed, and none is supplied.
1427
1494
  var urlError = function() {
@@ -1430,91 +1497,230 @@
1430
1497
 
1431
1498
  }).call(this);
1432
1499
 
1433
- // Backbone.EventBinder, v0.0.0
1500
+ // Backbone.BabySitter, v0.0.4
1434
1501
  // Copyright (c)2012 Derick Bailey, Muted Solutions, LLC.
1435
1502
  // Distributed under MIT license
1436
- // http://github.com/marionettejs/backbone.eventbinder
1437
- // EventBinder
1438
- // -----------
1439
- //
1440
- // The event binder facilitates the binding and unbinding of events
1441
- // from objects that extend `Backbone.Events`. It makes
1442
- // unbinding events, even with anonymous callback functions,
1443
- // easy.
1503
+ // http://github.com/marionettejs/backbone.babysitter
1504
+ // Backbone.ChildViewContainer
1505
+ // ---------------------------
1444
1506
  //
1445
- // Inspired by [Johnny Oshika](http://stackoverflow.com/questions/7567404/backbone-js-repopulate-or-recreate-the-view/7607853#7607853)
1507
+ // Provide a container to store, retrieve and
1508
+ // shut down child views.
1446
1509
 
1447
- Backbone.EventBinder = (function(Backbone, _){
1510
+ Backbone.ChildViewContainer = (function(Backbone, _){
1448
1511
 
1449
- // Constructor function
1450
- var EventBinder = function(){
1451
- this._eventBindings = [];
1512
+ // Container Constructor
1513
+ // ---------------------
1514
+
1515
+ var Container = function(initialViews){
1516
+ this._views = {};
1517
+ this._indexByModel = {};
1518
+ this._indexByCollection = {};
1519
+ this._indexByCustom = {};
1520
+ this._updateLength();
1521
+
1522
+ this._addInitialViews(initialViews);
1452
1523
  };
1453
1524
 
1454
- // Copy the `extend` function used by Backbone's classes
1455
- EventBinder.extend = Backbone.View.extend;
1456
-
1457
- // Extend the EventBinder with additional methods
1458
- _.extend(EventBinder.prototype, {
1459
- // Store the event binding in array so it can be unbound
1460
- // easily, at a later point in time.
1461
- bindTo: function (obj, eventName, callback, context) {
1462
- context = context || this;
1463
- obj.on(eventName, callback, context);
1464
-
1465
- var binding = {
1466
- obj: obj,
1467
- eventName: eventName,
1468
- callback: callback,
1469
- context: context
1470
- };
1525
+ // Container Methods
1526
+ // -----------------
1527
+
1528
+ _.extend(Container.prototype, {
1529
+
1530
+ // Add a view to this container. Stores the view
1531
+ // by `cid` and makes it searchable by the model
1532
+ // and/or collection of the view. Optionally specify
1533
+ // a custom key to store an retrieve the view.
1534
+ add: function(view, customIndex){
1535
+ var viewCid = view.cid;
1536
+
1537
+ // store the view
1538
+ this._views[viewCid] = view;
1539
+
1540
+ // index it by model
1541
+ if (view.model){
1542
+ this._indexByModel[view.model.cid] = viewCid;
1543
+ }
1544
+
1545
+ // index it by collection
1546
+ if (view.collection){
1547
+ this._indexByCollection[view.collection.cid] = viewCid;
1548
+ }
1549
+
1550
+ // index by custom
1551
+ if (customIndex){
1552
+ this._indexByCustom[customIndex] = viewCid;
1553
+ }
1554
+
1555
+ this._updateLength();
1556
+ },
1471
1557
 
1472
- this._eventBindings.push(binding);
1558
+ // Find a view by the model that was attached to
1559
+ // it. Uses the model's `cid` to find it, and
1560
+ // retrieves the view by it's `cid` from the result
1561
+ findByModel: function(model){
1562
+ var viewCid = this._indexByModel[model.cid];
1563
+ return this.findByCid(viewCid);
1564
+ },
1565
+
1566
+ // Find a view by the collection that was attached to
1567
+ // it. Uses the collection's `cid` to find it, and
1568
+ // retrieves the view by it's `cid` from the result
1569
+ findByCollection: function(col){
1570
+ var viewCid = this._indexByCollection[col.cid];
1571
+ return this.findByCid(viewCid);
1572
+ },
1573
+
1574
+ // Find a view by a custom indexer.
1575
+ findByCustom: function(index){
1576
+ var viewCid = this._indexByCustom[index];
1577
+ return this.findByCid(viewCid);
1578
+ },
1579
+
1580
+ // Find by index. This is not guaranteed to be a
1581
+ // stable index.
1582
+ findByIndex: function(index){
1583
+ return _.values(this._views)[index];
1584
+ },
1585
+
1586
+ // retrieve a view by it's `cid` directly
1587
+ findByCid: function(cid){
1588
+ return this._views[cid];
1589
+ },
1590
+
1591
+ // Remove a view
1592
+ remove: function(view){
1593
+ var viewCid = view.cid;
1594
+
1595
+ // delete model index
1596
+ if (view.model){
1597
+ delete this._indexByModel[view.model.cid];
1598
+ }
1599
+
1600
+ // delete collection index
1601
+ if (view.collection){
1602
+ delete this._indexByCollection[view.collection.cid];
1603
+ }
1604
+
1605
+ // delete custom index
1606
+ var cust;
1607
+
1608
+ for (var key in this._indexByCustom){
1609
+ if (this._indexByCustom.hasOwnProperty(key)){
1610
+ if (this._indexByCustom[key] === viewCid){
1611
+ cust = key;
1612
+ break;
1613
+ }
1614
+ }
1615
+ }
1616
+
1617
+ if (cust){
1618
+ delete this._indexByCustom[cust];
1619
+ }
1620
+
1621
+ // remove the view from the container
1622
+ delete this._views[viewCid];
1473
1623
 
1474
- return binding;
1624
+ // update the length
1625
+ this._updateLength();
1475
1626
  },
1476
1627
 
1477
- // Unbind from a single binding object. Binding objects are
1478
- // returned from the `bindTo` method call.
1479
- unbindFrom: function(binding){
1480
- binding.obj.off(binding.eventName, binding.callback, binding.context);
1481
- this._eventBindings = _.reject(this._eventBindings, function(bind){return bind === binding;});
1628
+ // Call a method on every view in the container,
1629
+ // passing parameters to the call method one at a
1630
+ // time, like `function.call`.
1631
+ call: function(method, args){
1632
+ args = Array.prototype.slice.call(arguments, 1);
1633
+ this.apply(method, args);
1482
1634
  },
1483
1635
 
1484
- // Unbind all of the events that we have stored.
1485
- unbindAll: function () {
1486
- var that = this;
1636
+ // Apply a method on every view in the container,
1637
+ // passing parameters to the call method one at a
1638
+ // time, like `function.apply`.
1639
+ apply: function(method, args){
1640
+ var view;
1487
1641
 
1488
- // The `unbindFrom` call removes elements from the array
1489
- // while it is being iterated, so clone it first.
1490
- var bindings = _.map(this._eventBindings, _.identity);
1491
- _.each(bindings, function (binding, index) {
1492
- that.unbindFrom(binding);
1642
+ // fix for IE < 9
1643
+ args = args || [];
1644
+
1645
+ _.each(this._views, function(view, key){
1646
+ if (_.isFunction(view[method])){
1647
+ view[method].apply(view, args);
1648
+ }
1493
1649
  });
1650
+
1651
+ },
1652
+
1653
+ // Update the `.length` attribute on this container
1654
+ _updateLength: function(){
1655
+ this.length = _.size(this._views);
1656
+ },
1657
+
1658
+ // set up an initial list of views
1659
+ _addInitialViews: function(views){
1660
+ if (!views){ return; }
1661
+
1662
+ var view, i,
1663
+ length = views.length;
1664
+
1665
+ for (i=0; i<length; i++){
1666
+ view = views[i];
1667
+ this.add(view);
1668
+ }
1494
1669
  }
1495
1670
  });
1496
1671
 
1497
- return EventBinder;
1672
+ // Borrowing this code from Backbone.Collection:
1673
+ // http://backbonejs.org/docs/backbone.html#section-106
1674
+ //
1675
+ // Mix in methods from Underscore, for iteration, and other
1676
+ // collection related features.
1677
+ var methods = ['forEach', 'each', 'map', 'find', 'detect', 'filter',
1678
+ 'select', 'reject', 'every', 'all', 'some', 'any', 'include',
1679
+ 'contains', 'invoke', 'toArray', 'first', 'initial', 'rest',
1680
+ 'last', 'without', 'isEmpty', 'pluck'];
1681
+
1682
+ _.each(methods, function(method) {
1683
+ Container.prototype[method] = function() {
1684
+ var views = _.values(this._views);
1685
+ var args = [views].concat(_.toArray(arguments));
1686
+ return _[method].apply(_, args);
1687
+ };
1688
+ });
1689
+
1690
+ // return the public API
1691
+ return Container;
1498
1692
  })(Backbone, _);
1499
- // Backbone.Wreqr, v0.0.0
1500
- // Copyright (c)2012 Derick Bailey, Muted Solutions, LLC.
1693
+ // Backbone.Wreqr, v0.1.1
1694
+ // Copyright (c)2013 Derick Bailey, Muted Solutions, LLC.
1501
1695
  // Distributed under MIT license
1502
1696
  // http://github.com/marionettejs/backbone.wreqr
1503
1697
  Backbone.Wreqr = (function(Backbone, Marionette, _){
1504
- "option strict";
1698
+ "use strict";
1505
1699
  var Wreqr = {};
1506
1700
 
1701
+ // Handlers
1702
+ // --------
1703
+ // A registry of functions to call, given a name
1704
+
1507
1705
  Wreqr.Handlers = (function(Backbone, _){
1508
- "option strict";
1706
+ "use strict";
1509
1707
 
1708
+ // Constructor
1709
+ // -----------
1710
+
1510
1711
  var Handlers = function(){
1511
- "use strict";
1512
1712
  this._handlers = {};
1513
1713
  };
1514
1714
 
1515
1715
  Handlers.extend = Backbone.Model.extend;
1516
1716
 
1717
+ // Instance Members
1718
+ // ----------------
1719
+
1517
1720
  _.extend(Handlers.prototype, {
1721
+
1722
+ // Add a handler for the given name, with an
1723
+ // optional context to run the handler within
1518
1724
  addHandler: function(name, handler, context){
1519
1725
  var config = {
1520
1726
  callback: handler,
@@ -1524,6 +1730,9 @@ Backbone.Wreqr = (function(Backbone, Marionette, _){
1524
1730
  this._handlers[name] = config;
1525
1731
  },
1526
1732
 
1733
+ // Get the currently registered handler for
1734
+ // the specified name. Throws an exception if
1735
+ // no handler is found.
1527
1736
  getHandler: function(name){
1528
1737
  var config = this._handlers[name];
1529
1738
 
@@ -1532,14 +1741,17 @@ Backbone.Wreqr = (function(Backbone, Marionette, _){
1532
1741
  }
1533
1742
 
1534
1743
  return function(){
1535
- return config.callback.apply(config.context, arguments);
1744
+ var args = Array.prototype.slice.apply(arguments);
1745
+ return config.callback.apply(config.context, args);
1536
1746
  };
1537
1747
  },
1538
1748
 
1749
+ // Remove a handler for the specified name
1539
1750
  removeHandler: function(name){
1540
1751
  delete this._handlers[name];
1541
1752
  },
1542
1753
 
1754
+ // Remove all handlers from this registry
1543
1755
  removeAllHandlers: function(){
1544
1756
  this._handlers = {};
1545
1757
  }
@@ -1554,11 +1766,14 @@ Backbone.Wreqr = (function(Backbone, Marionette, _){
1554
1766
  // A simple command pattern implementation. Register a command
1555
1767
  // handler and execute it.
1556
1768
  Wreqr.Commands = (function(Wreqr){
1557
- "option strict";
1769
+ "use strict";
1558
1770
 
1559
1771
  return Wreqr.Handlers.extend({
1560
- execute: function(name, args){
1561
- this.getHandler(name)(args);
1772
+ execute: function(){
1773
+ var name = arguments[0];
1774
+ var args = Array.prototype.slice.call(arguments, 1);
1775
+
1776
+ this.getHandler(name).apply(this, args);
1562
1777
  }
1563
1778
  });
1564
1779
 
@@ -1570,11 +1785,14 @@ Backbone.Wreqr = (function(Backbone, Marionette, _){
1570
1785
  // A simple request/response implementation. Register a
1571
1786
  // request handler, and return a response from it
1572
1787
  Wreqr.RequestResponse = (function(Wreqr){
1573
- "option strict";
1788
+ "use strict";
1574
1789
 
1575
1790
  return Wreqr.Handlers.extend({
1576
- request: function(name, args){
1577
- return this.getHandler(name)(args);
1791
+ request: function(){
1792
+ var name = arguments[0];
1793
+ var args = Array.prototype.slice.call(arguments, 1);
1794
+
1795
+ return this.getHandler(name).apply(this, args);
1578
1796
  }
1579
1797
  });
1580
1798
 
@@ -1586,7 +1804,7 @@ Backbone.Wreqr = (function(Backbone, Marionette, _){
1586
1804
  // of an application through event-driven architecture.
1587
1805
 
1588
1806
  Wreqr.EventAggregator = (function(Backbone, _){
1589
- "option strict";
1807
+ "use strict";
1590
1808
  var EA = function(){};
1591
1809
 
1592
1810
  // Copy the `extend` function used by Backbone's classes
@@ -1601,14 +1819,16 @@ Backbone.Wreqr = (function(Backbone, Marionette, _){
1601
1819
 
1602
1820
  return Wreqr;
1603
1821
  })(Backbone, Backbone.Marionette, _);
1604
- /*!
1605
- * Backbone.Marionette, v1.0.0-beta1
1606
- * Copyright (c)2012 Derick Bailey, Muted Solutions, LLC.
1607
- * Distributed under MIT license
1608
- * http://github.com/marionettejs/backbone.marionette
1609
- */
1610
- Backbone.Marionette = Marionette = (function(Backbone, _, $){
1822
+ // Backbone.Marionette, v1.0.0-rc4
1823
+ // Copyright (c)2013 Derick Bailey, Muted Solutions, LLC.
1824
+ // Distributed under MIT license
1825
+ // http://github.com/marionettejs/backbone.marionette
1826
+
1827
+ var Marionette = (function(Backbone, _, $){
1828
+ "use strict";
1829
+
1611
1830
  var Marionette = {};
1831
+ Backbone.Marionette = Marionette;
1612
1832
 
1613
1833
  // Helpers
1614
1834
  // -------
@@ -1616,9 +1836,79 @@ Backbone.Marionette = Marionette = (function(Backbone, _, $){
1616
1836
  // For slicing `arguments` in functions
1617
1837
  var slice = Array.prototype.slice;
1618
1838
 
1839
+ // Marionette.extend
1840
+ // -----------------
1841
+
1619
1842
  // Borrow the Backbone `extend` method so we can use it as needed
1620
1843
  Marionette.extend = Backbone.Model.extend;
1621
1844
 
1845
+ // Marionette.getOption
1846
+ // --------------------
1847
+
1848
+ // Retrieve an object, function or other value from a target
1849
+ // object or it's `options`, with `options` taking precedence.
1850
+ Marionette.getOption = function(target, optionName){
1851
+ if (!target || !optionName){ return; }
1852
+ var value;
1853
+
1854
+ if (target.options && (optionName in target.options) && (target.options[optionName] !== undefined)){
1855
+ value = target.options[optionName];
1856
+ } else {
1857
+ value = target[optionName];
1858
+ }
1859
+
1860
+ return value;
1861
+ };
1862
+
1863
+ // Mairionette.createObject
1864
+ // ------------------------
1865
+
1866
+ // A wrapper / shim for `Object.create`. Uses native `Object.create`
1867
+ // if available, otherwise shims it in place for Marionette to use.
1868
+ Marionette.createObject = (function(){
1869
+ var createObject;
1870
+
1871
+ // Define this once, and just replace the .prototype on it as needed,
1872
+ // to improve performance in older / less optimized JS engines
1873
+ function F() {}
1874
+
1875
+
1876
+ // Check for existing native / shimmed Object.create
1877
+ if (typeof Object.create === "function"){
1878
+
1879
+ // found native/shim, so use it
1880
+ createObject = Object.create;
1881
+
1882
+ } else {
1883
+
1884
+ // An implementation of the Boodman/Crockford delegation
1885
+ // w/ Cornford optimization, as suggested by @unscriptable
1886
+ // https://gist.github.com/3959151
1887
+
1888
+ // native/shim not found, so shim it ourself
1889
+ createObject = function (o) {
1890
+
1891
+ // set the prototype of the function
1892
+ // so we will get `o` as the prototype
1893
+ // of the new object instance
1894
+ F.prototype = o;
1895
+
1896
+ // create a new object that inherits from
1897
+ // the `o` parameter
1898
+ var child = new F();
1899
+
1900
+ // clean up just in case o is really large
1901
+ F.prototype = null;
1902
+
1903
+ // send it back
1904
+ return child;
1905
+ };
1906
+
1907
+ }
1908
+
1909
+ return createObject;
1910
+ })();
1911
+
1622
1912
  // Trigger an event and a corresponding method name. Examples:
1623
1913
  //
1624
1914
  // `this.triggerMethod("foo")` will trigger the "foo" event and
@@ -1638,184 +1928,668 @@ Marionette.triggerMethod = function(){
1638
1928
  methodName += capLetter + segment.slice(1);
1639
1929
  }
1640
1930
 
1641
- this.trigger.apply(this, arguments);
1931
+ this.trigger.apply(this, args);
1642
1932
 
1643
1933
  if (_.isFunction(this[methodName])){
1644
1934
  args.shift();
1645
- this[methodName].apply(this, args);
1935
+ return this[methodName].apply(this, args);
1646
1936
  }
1647
1937
  };
1648
1938
 
1649
- // EventBinder
1650
- // -----------
1651
- // Import the event binder from it's new home
1652
- // https://github.com/marionettejs/backbone.eventbinder
1653
- Marionette.EventBinder = Backbone.EventBinder;
1654
-
1655
- // Add the EventBinder methods to the view directly,
1656
- // but keep them bound to the EventBinder instance so they work properly.
1657
- // This allows the event binder's implementation to vary independently
1658
- // of it being attached to the view... for example the internal structure
1659
- // used to store the events can change without worry about it interfering
1660
- // with Marionette's views.
1661
- Marionette.addEventBinder = function(target){
1662
- var eventBinder = new Marionette.EventBinder();
1663
- target.eventBinder = eventBinder;
1664
- target.bindTo = _.bind(eventBinder.bindTo, eventBinder);
1665
- target.unbindFrom = _.bind(eventBinder.unbindFrom, eventBinder);
1666
- target.unbindAll = _.bind(eventBinder.unbindAll, eventBinder);
1667
- };
1668
-
1669
- // Marionette.View
1670
- // ---------------
1671
-
1672
- // The core view type that other Marionette views extend from.
1673
- Marionette.View = Backbone.View.extend({
1674
-
1675
- constructor: function(){
1676
- _.bindAll(this, "render");
1677
- Marionette.addEventBinder(this);
1939
+ // DOMRefresh
1940
+ // ----------
1941
+ //
1942
+ // Monitor a view's state, and after it has been rendered and shown
1943
+ // in the DOM, trigger a "dom:refresh" event every time it is
1944
+ // re-rendered.
1945
+
1946
+ Marionette.MonitorDOMRefresh = (function(){
1947
+ // track when the view has been rendered
1948
+ function handleShow(view){
1949
+ view._isShown = true;
1950
+ triggerDOMRefresh(view);
1951
+ }
1678
1952
 
1679
- Backbone.View.prototype.constructor.apply(this, arguments);
1953
+ // track when the view has been shown in the DOM,
1954
+ // using a Marionette.Region (or by other means of triggering "show")
1955
+ function handleRender(view){
1956
+ view._isRendered = true;
1957
+ triggerDOMRefresh(view);
1958
+ }
1680
1959
 
1681
- this.bindBackboneEntityTo(this.model, this.modelEvents);
1682
- this.bindBackboneEntityTo(this.collection, this.collectionEvents);
1960
+ // Trigger the "dom:refresh" event and corresponding "onDomRefresh" method
1961
+ function triggerDOMRefresh(view){
1962
+ if (view._isShown && view._isRendered){
1963
+ if (_.isFunction(view.triggerMethod)){
1964
+ view.triggerMethod("dom:refresh");
1965
+ }
1966
+ }
1967
+ }
1683
1968
 
1684
- this.bindTo(this, "show", this.onShowCalled, this);
1685
- },
1969
+ // Export public API
1970
+ return function(view){
1971
+ view.listenTo(view, "show", function(){
1972
+ handleShow(view);
1973
+ });
1686
1974
 
1687
- // import the "triggerMethod" to trigger events with corresponding
1688
- // methods if the method exists
1689
- triggerMethod: Marionette.triggerMethod,
1975
+ view.listenTo(view, "render", function(){
1976
+ handleRender(view);
1977
+ });
1978
+ };
1979
+ })();
1690
1980
 
1691
- // Get the template for this view
1692
- // instance. You can set a `template` attribute in the view
1693
- // definition or pass a `template: "whatever"` parameter in
1694
- // to the constructor options.
1695
- getTemplate: function(){
1696
- var template;
1697
1981
 
1698
- // Get the template from `this.options.template` or
1699
- // `this.template`. The `options` takes precedence.
1700
- if (this.options && this.options.template){
1701
- template = this.options.template;
1702
- } else {
1703
- template = this.template;
1704
- }
1982
+ // Marionette.bindEntityEvents & unbindEntityEvents
1983
+ // ---------------------------
1984
+ //
1985
+ // These methods are used to bind/unbind a backbone "entity" (collection/model)
1986
+ // to methods on a target object.
1987
+ //
1988
+ // The first paremter, `target`, must have a `listenTo` method from the
1989
+ // EventBinder object.
1990
+ //
1991
+ // The second parameter is the entity (Backbone.Model or Backbone.Collection)
1992
+ // to bind the events from.
1993
+ //
1994
+ // The third parameter is a hash of { "event:name": "eventHandler" }
1995
+ // configuration. Multiple handlers can be separated by a space. A
1996
+ // function can be supplied instead of a string handler name.
1705
1997
 
1706
- return template;
1707
- },
1998
+ (function(Marionette){
1999
+ "use strict";
1708
2000
 
1709
- // Mix in template helper methods. Looks for a
1710
- // `templateHelpers` attribute, which can either be an
1711
- // object literal, or a function that returns an object
1712
- // literal. All methods and attributes from this object
1713
- // are copies to the object passed in.
1714
- mixinTemplateHelpers: function(target){
1715
- target = target || {};
1716
- var templateHelpers = this.templateHelpers;
1717
- if (_.isFunction(templateHelpers)){
1718
- templateHelpers = templateHelpers.call(this);
1719
- }
1720
- return _.extend(target, templateHelpers);
1721
- },
2001
+ // Bind the event to handlers specified as a string of
2002
+ // handler names on the target object
2003
+ function bindFromStrings(target, entity, evt, methods){
2004
+ var methodNames = methods.split(/\s+/);
1722
2005
 
1723
- // Configure `triggers` to forward DOM events to view
1724
- // events. `triggers: {"click .foo": "do:foo"}`
1725
- configureTriggers: function(){
1726
- if (!this.triggers) { return; }
2006
+ _.each(methodNames,function(methodName) {
1727
2007
 
1728
- var triggers = this.triggers;
1729
- var that = this;
1730
- var triggerEvents = {};
2008
+ var method = target[methodName];
2009
+ if(!method) {
2010
+ throw new Error("Method '"+ methodName +"' was configured as an event handler, but does not exist.");
2011
+ }
1731
2012
 
1732
- // Allow `triggers` to be configured as a function
1733
- if (_.isFunction(triggers)){ triggers = triggers.call(this); }
2013
+ target.listenTo(entity, evt, method, target);
2014
+ });
2015
+ }
1734
2016
 
1735
- // Configure the triggers, prevent default
1736
- // action and stop propagation of DOM events
1737
- _.each(triggers, function(value, key){
2017
+ // Bind the event to a supplied callback function
2018
+ function bindToFunction(target, entity, evt, method){
2019
+ target.listenTo(entity, evt, method, target);
2020
+ }
1738
2021
 
1739
- triggerEvents[key] = function(e){
1740
- if (e && e.preventDefault){ e.preventDefault(); }
1741
- if (e && e.stopPropagation){ e.stopPropagation(); }
1742
- that.trigger(value);
1743
- };
2022
+ // Bind the event to handlers specified as a string of
2023
+ // handler names on the target object
2024
+ function unbindFromStrings(target, entity, evt, methods){
2025
+ var methodNames = methods.split(/\s+/);
1744
2026
 
2027
+ _.each(methodNames,function(methodName) {
2028
+ var method = target[method];
2029
+ target.stopListening(entity, evt, method, target);
1745
2030
  });
2031
+ }
1746
2032
 
1747
- return triggerEvents;
1748
- },
2033
+ // Bind the event to a supplied callback function
2034
+ function unbindToFunction(target, entity, evt, method){
2035
+ target.stopListening(entity, evt, method, target);
2036
+ }
1749
2037
 
1750
- // Overriding Backbone.View's delegateEvents specifically
1751
- // to handle the `triggers` configuration
1752
- delegateEvents: function(events){
1753
- events = events || this.events;
1754
- if (_.isFunction(events)){ events = events.call(this); }
2038
+
2039
+ // generic looping function
2040
+ function iterateEvents(target, entity, bindings, functionCallback, stringCallback){
2041
+ if (!entity || !bindings) { return; }
1755
2042
 
1756
- var combinedEvents = {};
1757
- var triggers = this.configureTriggers();
1758
- _.extend(combinedEvents, events, triggers);
2043
+ // allow the bindings to be a function
2044
+ if (_.isFunction(bindings)){
2045
+ bindings = bindings.call(target);
2046
+ }
1759
2047
 
1760
- Backbone.View.prototype.delegateEvents.call(this, combinedEvents);
1761
- },
2048
+ // iterate the bindings and bind them
2049
+ _.each(bindings, function(methods, evt){
1762
2050
 
1763
- // Internal method, handles the `show` event.
1764
- onShowCalled: function(){},
2051
+ // allow for a function as the handler,
2052
+ // or a list of event names as a string
2053
+ if (_.isFunction(methods)){
2054
+ functionCallback(target, entity, evt, methods);
2055
+ } else {
2056
+ stringCallback(target, entity, evt, methods);
2057
+ }
1765
2058
 
1766
- // Default `close` implementation, for removing a view from the
1767
- // DOM and unbinding it. Regions will call this method
1768
- // for you. You can specify an `onClose` method in your view to
1769
- // add custom code that is called after the view is closed.
1770
- close: function(){
1771
- if (this.isClosed) { return; }
2059
+ });
2060
+ }
2061
+
2062
+ // Export Public API
2063
+ Marionette.bindEntityEvents = function(target, entity, bindings){
2064
+ iterateEvents(target, entity, bindings, bindToFunction, bindFromStrings);
2065
+ };
1772
2066
 
1773
- this.triggerMethod("before:close");
2067
+ Marionette.unbindEntityEvents = function(target, entity, bindings){
2068
+ iterateEvents(target, entity, bindings, unbindToFunction, unbindFromStrings);
2069
+ };
1774
2070
 
1775
- this.remove();
1776
- this.unbindAll();
2071
+ })(Marionette);
1777
2072
 
1778
- this.triggerMethod("close");
1779
- this.isClosed = true;
1780
- },
2073
+
2074
+ // Callbacks
2075
+ // ---------
1781
2076
 
1782
- // This method binds the elements specified in the "ui" hash inside the view's code with
1783
- // the associated jQuery selectors.
1784
- bindUIElements: function(){
1785
- if (!this.ui) { return; }
2077
+ // A simple way of managing a collection of callbacks
2078
+ // and executing them at a later point in time, using jQuery's
2079
+ // `Deferred` object.
2080
+ Marionette.Callbacks = function(){
2081
+ this._deferred = $.Deferred();
2082
+ this._callbacks = [];
2083
+ };
1786
2084
 
1787
- var that = this;
2085
+ _.extend(Marionette.Callbacks.prototype, {
1788
2086
 
1789
- if (!this.uiBindings) {
1790
- // We want to store the ui hash in uiBindings, since afterwards the values in the ui hash
1791
- // will be overridden with jQuery selectors.
1792
- this.uiBindings = this.ui;
1793
- }
2087
+ // Add a callback to be executed. Callbacks added here are
2088
+ // guaranteed to execute, even if they are added after the
2089
+ // `run` method is called.
2090
+ add: function(callback, contextOverride){
2091
+ this._callbacks.push({cb: callback, ctx: contextOverride});
1794
2092
 
1795
- // refreshing the associated selectors since they should point to the newly rendered elements.
1796
- this.ui = {};
1797
- _.each(_.keys(this.uiBindings), function(key) {
1798
- var selector = that.uiBindings[key];
1799
- that.ui[key] = that.$(selector);
2093
+ this._deferred.done(function(context, options){
2094
+ if (contextOverride){ context = contextOverride; }
2095
+ callback.call(context, options);
1800
2096
  });
1801
2097
  },
1802
2098
 
1803
- // This method is used to bind a backbone "entity" (collection/model) to methods on the view.
1804
- bindBackboneEntityTo: function(entity, bindings){
1805
- if (!entity || !bindings) { return; }
1806
-
1807
- var view = this;
1808
- _.each(bindings, function(methodName, evt){
1809
-
1810
- var method = view[methodName];
1811
- if(!method) {
1812
- throw new Error("View method '"+ methodName +"' was configured as an event handler, but does not exist.");
1813
- }
2099
+ // Run all registered callbacks with the context specified.
2100
+ // Additional callbacks can be added after this has been run
2101
+ // and they will still be executed.
2102
+ run: function(options, context){
2103
+ this._deferred.resolve(context, options);
2104
+ },
1814
2105
 
1815
- view.bindTo(entity, evt, method, view);
1816
- });
1817
- }
1818
- });
2106
+ // Resets the list of callbacks to be run, allowing the same list
2107
+ // to be run multiple times - whenever the `run` method is called.
2108
+ reset: function(){
2109
+ var that = this;
2110
+ var callbacks = this._callbacks;
2111
+ this._deferred = $.Deferred();
2112
+ this._callbacks = [];
2113
+ _.each(callbacks, function(cb){
2114
+ that.add(cb.cb, cb.ctx);
2115
+ });
2116
+ }
2117
+ });
2118
+
2119
+
2120
+ // Marionette Controller
2121
+ // ---------------------
2122
+ //
2123
+ // A multi-purpose object to use as a controller for
2124
+ // modules and routers, and as a mediator for workflow
2125
+ // and coordination of other objects, views, and more.
2126
+ Marionette.Controller = function(options){
2127
+ this.triggerMethod = Marionette.triggerMethod;
2128
+ this.options = options || {};
2129
+
2130
+ if (_.isFunction(this.initialize)){
2131
+ this.initialize(this.options);
2132
+ }
2133
+ };
2134
+
2135
+ Marionette.Controller.extend = Marionette.extend;
2136
+
2137
+ // Controller Methods
2138
+ // --------------
2139
+
2140
+ // Ensure it can trigger events with Backbone.Events
2141
+ _.extend(Marionette.Controller.prototype, Backbone.Events, {
2142
+ close: function(){
2143
+ this.stopListening();
2144
+ this.triggerMethod("close");
2145
+ this.unbind();
2146
+ }
2147
+ });
2148
+
2149
+ // Region
2150
+ // ------
2151
+ //
2152
+ // Manage the visual regions of your composite application. See
2153
+ // http://lostechies.com/derickbailey/2011/12/12/composite-js-apps-regions-and-region-managers/
2154
+
2155
+ Marionette.Region = function(options){
2156
+ this.options = options || {};
2157
+
2158
+ this.el = Marionette.getOption(this, "el");
2159
+
2160
+ if (!this.el){
2161
+ var err = new Error("An 'el' must be specified for a region.");
2162
+ err.name = "NoElError";
2163
+ throw err;
2164
+ }
2165
+
2166
+ if (this.initialize){
2167
+ var args = Array.prototype.slice.apply(arguments);
2168
+ this.initialize.apply(this, args);
2169
+ }
2170
+ };
2171
+
2172
+
2173
+ // Region Type methods
2174
+ // -------------------
2175
+
2176
+ _.extend(Marionette.Region, {
2177
+
2178
+ // Build an instance of a region by passing in a configuration object
2179
+ // and a default region type to use if none is specified in the config.
2180
+ //
2181
+ // The config object should either be a string as a jQuery DOM selector,
2182
+ // a Region type directly, or an object literal that specifies both
2183
+ // a selector and regionType:
2184
+ //
2185
+ // ```js
2186
+ // {
2187
+ // selector: "#foo",
2188
+ // regionType: MyCustomRegion
2189
+ // }
2190
+ // ```
2191
+ //
2192
+ buildRegion: function(regionConfig, defaultRegionType){
2193
+ var regionIsString = (typeof regionConfig === "string");
2194
+ var regionSelectorIsString = (typeof regionConfig.selector === "string");
2195
+ var regionTypeIsUndefined = (typeof regionConfig.regionType === "undefined");
2196
+ var regionIsType = (typeof regionConfig === "function");
2197
+
2198
+ if (!regionIsType && !regionIsString && !regionSelectorIsString) {
2199
+ throw new Error("Region must be specified as a Region type, a selector string or an object with selector property");
2200
+ }
2201
+
2202
+ var selector, RegionType;
2203
+
2204
+ // get the selector for the region
2205
+
2206
+ if (regionIsString) {
2207
+ selector = regionConfig;
2208
+ }
2209
+
2210
+ if (regionConfig.selector) {
2211
+ selector = regionConfig.selector;
2212
+ }
2213
+
2214
+ // get the type for the region
2215
+
2216
+ if (regionIsType){
2217
+ RegionType = regionConfig;
2218
+ }
2219
+
2220
+ if (!regionIsType && regionTypeIsUndefined) {
2221
+ RegionType = defaultRegionType;
2222
+ }
2223
+
2224
+ if (regionConfig.regionType) {
2225
+ RegionType = regionConfig.regionType;
2226
+ }
2227
+
2228
+ // build the region instance
2229
+
2230
+ var regionManager = new RegionType({
2231
+ el: selector
2232
+ });
2233
+
2234
+ return regionManager;
2235
+ }
2236
+
2237
+ });
2238
+
2239
+ // Region Instance Methods
2240
+ // -----------------------
2241
+
2242
+ _.extend(Marionette.Region.prototype, Backbone.Events, {
2243
+
2244
+ // Displays a backbone view instance inside of the region.
2245
+ // Handles calling the `render` method for you. Reads content
2246
+ // directly from the `el` attribute. Also calls an optional
2247
+ // `onShow` and `close` method on your view, just after showing
2248
+ // or just before closing the view, respectively.
2249
+ show: function(view){
2250
+
2251
+ this.ensureEl();
2252
+ this.close();
2253
+
2254
+ view.render();
2255
+ this.open(view);
2256
+
2257
+ Marionette.triggerMethod.call(view, "show");
2258
+ Marionette.triggerMethod.call(this, "show", view);
2259
+
2260
+ this.currentView = view;
2261
+ },
2262
+
2263
+ ensureEl: function(){
2264
+ if (!this.$el || this.$el.length === 0){
2265
+ this.$el = this.getEl(this.el);
2266
+ }
2267
+ },
2268
+
2269
+ // Override this method to change how the region finds the
2270
+ // DOM element that it manages. Return a jQuery selector object.
2271
+ getEl: function(selector){
2272
+ return $(selector);
2273
+ },
2274
+
2275
+ // Override this method to change how the new view is
2276
+ // appended to the `$el` that the region is managing
2277
+ open: function(view){
2278
+ this.$el.empty().append(view.el);
2279
+ },
2280
+
2281
+ // Close the current view, if there is one. If there is no
2282
+ // current view, it does nothing and returns immediately.
2283
+ close: function(){
2284
+ var view = this.currentView;
2285
+ if (!view || view.isClosed){ return; }
2286
+
2287
+ if (view.close) { view.close(); }
2288
+ Marionette.triggerMethod.call(this, "close");
2289
+
2290
+ delete this.currentView;
2291
+ },
2292
+
2293
+ // Attach an existing view to the region. This
2294
+ // will not call `render` or `onShow` for the new view,
2295
+ // and will not replace the current HTML for the `el`
2296
+ // of the region.
2297
+ attachView: function(view){
2298
+ this.currentView = view;
2299
+ },
2300
+
2301
+ // Reset the region by closing any existing view and
2302
+ // clearing out the cached `$el`. The next time a view
2303
+ // is shown via this region, the region will re-query the
2304
+ // DOM for the region's `el`.
2305
+ reset: function(){
2306
+ this.close();
2307
+ delete this.$el;
2308
+ }
2309
+ });
2310
+
2311
+ // Copy the `extend` function used by Backbone's classes
2312
+ Marionette.Region.extend = Marionette.extend;
2313
+
2314
+
2315
+ // Template Cache
2316
+ // --------------
2317
+
2318
+ // Manage templates stored in `<script>` blocks,
2319
+ // caching them for faster access.
2320
+ Marionette.TemplateCache = function(templateId){
2321
+ this.templateId = templateId;
2322
+ };
2323
+
2324
+ // TemplateCache object-level methods. Manage the template
2325
+ // caches from these method calls instead of creating
2326
+ // your own TemplateCache instances
2327
+ _.extend(Marionette.TemplateCache, {
2328
+ templateCaches: {},
2329
+
2330
+ // Get the specified template by id. Either
2331
+ // retrieves the cached version, or loads it
2332
+ // from the DOM.
2333
+ get: function(templateId){
2334
+ var that = this;
2335
+ var cachedTemplate = this.templateCaches[templateId];
2336
+
2337
+ if (!cachedTemplate){
2338
+ cachedTemplate = new Marionette.TemplateCache(templateId);
2339
+ this.templateCaches[templateId] = cachedTemplate;
2340
+ }
2341
+
2342
+ return cachedTemplate.load();
2343
+ },
2344
+
2345
+ // Clear templates from the cache. If no arguments
2346
+ // are specified, clears all templates:
2347
+ // `clear()`
2348
+ //
2349
+ // If arguments are specified, clears each of the
2350
+ // specified templates from the cache:
2351
+ // `clear("#t1", "#t2", "...")`
2352
+ clear: function(){
2353
+ var i;
2354
+ var args = Array.prototype.slice.apply(arguments);
2355
+ var length = args.length;
2356
+
2357
+ if (length > 0){
2358
+ for(i=0; i<length; i++){
2359
+ delete this.templateCaches[args[i]];
2360
+ }
2361
+ } else {
2362
+ this.templateCaches = {};
2363
+ }
2364
+ }
2365
+ });
2366
+
2367
+ // TemplateCache instance methods, allowing each
2368
+ // template cache object to manage it's own state
2369
+ // and know whether or not it has been loaded
2370
+ _.extend(Marionette.TemplateCache.prototype, {
2371
+
2372
+ // Internal method to load the template
2373
+ load: function(){
2374
+ var that = this;
2375
+
2376
+ // Guard clause to prevent loading this template more than once
2377
+ if (this.compiledTemplate){
2378
+ return this.compiledTemplate;
2379
+ }
2380
+
2381
+ // Load the template and compile it
2382
+ var template = this.loadTemplate(this.templateId);
2383
+ this.compiledTemplate = this.compileTemplate(template);
2384
+
2385
+ return this.compiledTemplate;
2386
+ },
2387
+
2388
+ // Load a template from the DOM, by default. Override
2389
+ // this method to provide your own template retrieval
2390
+ // For asynchronous loading with AMD/RequireJS, consider
2391
+ // using a template-loader plugin as described here:
2392
+ // https://github.com/marionettejs/backbone.marionette/wiki/Using-marionette-with-requirejs
2393
+ loadTemplate: function(templateId){
2394
+ var template = $(templateId).html();
2395
+
2396
+ if (!template || template.length === 0){
2397
+ var msg = "Could not find template: '" + templateId + "'";
2398
+ var err = new Error(msg);
2399
+ err.name = "NoTemplateError";
2400
+ throw err;
2401
+ }
2402
+
2403
+ return template;
2404
+ },
2405
+
2406
+ // Pre-compile the template before caching it. Override
2407
+ // this method if you do not need to pre-compile a template
2408
+ // (JST / RequireJS for example) or if you want to change
2409
+ // the template engine used (Handebars, etc).
2410
+ compileTemplate: function(rawTemplate){
2411
+ return _.template(rawTemplate);
2412
+ }
2413
+ });
2414
+
2415
+
2416
+ // Renderer
2417
+ // --------
2418
+
2419
+ // Render a template with data by passing in the template
2420
+ // selector and the data to render.
2421
+ Marionette.Renderer = {
2422
+
2423
+ // Render a template with data. The `template` parameter is
2424
+ // passed to the `TemplateCache` object to retrieve the
2425
+ // template function. Override this method to provide your own
2426
+ // custom rendering and template handling for all of Marionette.
2427
+ render: function(template, data){
2428
+ var templateFunc = typeof template === 'function' ? template : Marionette.TemplateCache.get(template);
2429
+ var html = templateFunc(data);
2430
+ return html;
2431
+ }
2432
+ };
2433
+
2434
+
2435
+
2436
+ // Marionette.View
2437
+ // ---------------
2438
+
2439
+ // The core view type that other Marionette views extend from.
2440
+ Marionette.View = Backbone.View.extend({
2441
+
2442
+ constructor: function(){
2443
+ _.bindAll(this, "render");
2444
+
2445
+ var args = Array.prototype.slice.apply(arguments);
2446
+ Backbone.View.prototype.constructor.apply(this, args);
2447
+
2448
+ Marionette.MonitorDOMRefresh(this);
2449
+ this.listenTo(this, "show", this.onShowCalled, this);
2450
+ },
2451
+
2452
+ // import the "triggerMethod" to trigger events with corresponding
2453
+ // methods if the method exists
2454
+ triggerMethod: Marionette.triggerMethod,
2455
+
2456
+ // Get the template for this view
2457
+ // instance. You can set a `template` attribute in the view
2458
+ // definition or pass a `template: "whatever"` parameter in
2459
+ // to the constructor options.
2460
+ getTemplate: function(){
2461
+ return Marionette.getOption(this, "template");
2462
+ },
2463
+
2464
+ // Mix in template helper methods. Looks for a
2465
+ // `templateHelpers` attribute, which can either be an
2466
+ // object literal, or a function that returns an object
2467
+ // literal. All methods and attributes from this object
2468
+ // are copies to the object passed in.
2469
+ mixinTemplateHelpers: function(target){
2470
+ target = target || {};
2471
+ var templateHelpers = this.templateHelpers;
2472
+ if (_.isFunction(templateHelpers)){
2473
+ templateHelpers = templateHelpers.call(this);
2474
+ }
2475
+ return _.extend(target, templateHelpers);
2476
+ },
2477
+
2478
+ // Configure `triggers` to forward DOM events to view
2479
+ // events. `triggers: {"click .foo": "do:foo"}`
2480
+ configureTriggers: function(){
2481
+ if (!this.triggers) { return; }
2482
+
2483
+ var that = this;
2484
+ var triggerEvents = {};
2485
+
2486
+ // Allow `triggers` to be configured as a function
2487
+ var triggers = _.result(this, "triggers");
2488
+
2489
+ // Configure the triggers, prevent default
2490
+ // action and stop propagation of DOM events
2491
+ _.each(triggers, function(value, key){
2492
+
2493
+ // build the event handler function for the DOM event
2494
+ triggerEvents[key] = function(e){
2495
+
2496
+ // stop the event in it's tracks
2497
+ if (e && e.preventDefault){ e.preventDefault(); }
2498
+ if (e && e.stopPropagation){ e.stopPropagation(); }
2499
+
2500
+ // buil the args for the event
2501
+ var args = {
2502
+ view: this,
2503
+ model: this.model,
2504
+ collection: this.collection
2505
+ };
2506
+
2507
+ // trigger the event
2508
+ that.triggerMethod(value, args);
2509
+ };
2510
+
2511
+ });
2512
+
2513
+ return triggerEvents;
2514
+ },
2515
+
2516
+ // Overriding Backbone.View's delegateEvents to handle
2517
+ // the `triggers`, `modelEvents`, and `collectionEvents` configuration
2518
+ delegateEvents: function(events){
2519
+ this._delegateDOMEvents(events);
2520
+ Marionette.bindEntityEvents(this, this.model, Marionette.getOption(this, "modelEvents"));
2521
+ Marionette.bindEntityEvents(this, this.collection, Marionette.getOption(this, "collectionEvents"));
2522
+ },
2523
+
2524
+ // internal method to delegate DOM events and triggers
2525
+ _delegateDOMEvents: function(events){
2526
+ events = events || this.events;
2527
+ if (_.isFunction(events)){ events = events.call(this); }
2528
+
2529
+ var combinedEvents = {};
2530
+ var triggers = this.configureTriggers();
2531
+ _.extend(combinedEvents, events, triggers);
2532
+
2533
+ Backbone.View.prototype.delegateEvents.call(this, combinedEvents);
2534
+ },
2535
+
2536
+ // Overriding Backbone.View's undelegateEvents to handle unbinding
2537
+ // the `triggers`, `modelEvents`, and `collectionEvents` config
2538
+ undelegateEvents: function(){
2539
+ var args = Array.prototype.slice.call(arguments);
2540
+ Backbone.View.prototype.undelegateEvents.apply(this, args);
2541
+
2542
+ Marionette.unbindEntityEvents(this, this.model, Marionette.getOption(this, "modelEvents"));
2543
+ Marionette.unbindEntityEvents(this, this.collection, Marionette.getOption(this, "collectionEvents"));
2544
+ },
2545
+
2546
+ // Internal method, handles the `show` event.
2547
+ onShowCalled: function(){},
2548
+
2549
+ // Default `close` implementation, for removing a view from the
2550
+ // DOM and unbinding it. Regions will call this method
2551
+ // for you. You can specify an `onClose` method in your view to
2552
+ // add custom code that is called after the view is closed.
2553
+ close: function(){
2554
+ if (this.isClosed) { return; }
2555
+
2556
+ // allow the close to be stopped by returning `false`
2557
+ // from the `onBeforeClose` method
2558
+ var shouldClose = this.triggerMethod("before:close");
2559
+ if (shouldClose === false){
2560
+ return;
2561
+ }
2562
+
2563
+ // mark as closed before doing the actual close, to
2564
+ // prevent infinite loops within "close" event handlers
2565
+ // that are trying to close other views
2566
+ this.isClosed = true;
2567
+ this.triggerMethod("close");
2568
+
2569
+ this.remove();
2570
+ },
2571
+
2572
+ // This method binds the elements specified in the "ui" hash inside the view's code with
2573
+ // the associated jQuery selectors.
2574
+ bindUIElements: function(){
2575
+ if (!this.ui) { return; }
2576
+
2577
+ var that = this;
2578
+
2579
+ if (!this.uiBindings) {
2580
+ // We want to store the ui hash in uiBindings, since afterwards the values in the ui hash
2581
+ // will be overridden with jQuery selectors.
2582
+ this.uiBindings = _.result(this, "ui");
2583
+ }
2584
+
2585
+ // refreshing the associated selectors since they should point to the newly rendered elements.
2586
+ this.ui = {};
2587
+ _.each(_.keys(this.uiBindings), function(key) {
2588
+ var selector = that.uiBindings[key];
2589
+ that.ui[key] = that.$(selector);
2590
+ });
2591
+ }
2592
+ });
1819
2593
 
1820
2594
  // Item View
1821
2595
  // ---------
@@ -1825,11 +2599,8 @@ Marionette.View = Backbone.View.extend({
1825
2599
  // and calling several methods on extended views, such as `onRender`.
1826
2600
  Marionette.ItemView = Marionette.View.extend({
1827
2601
  constructor: function(){
1828
- Marionette.View.prototype.constructor.apply(this, arguments);
1829
-
1830
- if (this.initialEvents){
1831
- this.initialEvents();
1832
- }
2602
+ var args = Array.prototype.slice.apply(arguments);
2603
+ Marionette.View.prototype.constructor.apply(this, args);
1833
2604
  },
1834
2605
 
1835
2606
  // Serialize the model or collection for the view. If a model is
@@ -1839,7 +2610,7 @@ Marionette.ItemView = Marionette.View.extend({
1839
2610
  // You can override the `serializeData` method in your own view
1840
2611
  // definition, to provide custom serialization for your view's data.
1841
2612
  serializeData: function(){
1842
- var data;
2613
+ var data = {};
1843
2614
 
1844
2615
  if (this.model) {
1845
2616
  data = this.model.toJSON();
@@ -1848,8 +2619,6 @@ Marionette.ItemView = Marionette.View.extend({
1848
2619
  data = { items: this.collection.toJSON() };
1849
2620
  }
1850
2621
 
1851
- data = this.mixinTemplateHelpers(data);
1852
-
1853
2622
  return data;
1854
2623
  },
1855
2624
 
@@ -1865,6 +2634,8 @@ Marionette.ItemView = Marionette.View.extend({
1865
2634
  this.triggerMethod("item:before:render", this);
1866
2635
 
1867
2636
  var data = this.serializeData();
2637
+ data = this.mixinTemplateHelpers(data);
2638
+
1868
2639
  var template = this.getTemplate();
1869
2640
  var html = Marionette.Renderer.render(template, data);
1870
2641
  this.$el.html(html);
@@ -1882,7 +2653,10 @@ Marionette.ItemView = Marionette.View.extend({
1882
2653
  if (this.isClosed){ return; }
1883
2654
 
1884
2655
  this.triggerMethod('item:before:close');
1885
- Marionette.View.prototype.close.apply(this, arguments);
2656
+
2657
+ var args = Array.prototype.slice.apply(arguments);
2658
+ Marionette.View.prototype.close.apply(this, args);
2659
+
1886
2660
  this.triggerMethod('item:closed');
1887
2661
  }
1888
2662
  });
@@ -1893,21 +2667,28 @@ Marionette.ItemView = Marionette.View.extend({
1893
2667
  // A view that iterates over a Backbone.Collection
1894
2668
  // and renders an individual ItemView for each model.
1895
2669
  Marionette.CollectionView = Marionette.View.extend({
1896
- constructor: function(){
1897
- this.initChildViewStorage();
1898
- Marionette.View.prototype.constructor.apply(this, arguments);
1899
- this.initialEvents();
1900
- this.onShowCallbacks = new Marionette.Callbacks();
2670
+ // used as the prefix for item view events
2671
+ // that are forwarded through the collectionview
2672
+ itemViewEventPrefix: "itemview",
2673
+
2674
+ // constructor
2675
+ constructor: function(options){
2676
+ this._initChildViewStorage();
2677
+
2678
+ var args = Array.prototype.slice.apply(arguments);
2679
+ Marionette.View.prototype.constructor.apply(this, args);
2680
+
2681
+ this._initialEvents();
1901
2682
  },
1902
2683
 
1903
2684
  // Configured the initial events that the collection view
1904
2685
  // binds to. Override this method to prevent the initial
1905
2686
  // events, or to add your own initial events.
1906
- initialEvents: function(){
2687
+ _initialEvents: function(){
1907
2688
  if (this.collection){
1908
- this.bindTo(this.collection, "add", this.addChildView, this);
1909
- this.bindTo(this.collection, "remove", this.removeItemView, this);
1910
- this.bindTo(this.collection, "reset", this.render, this);
2689
+ this.listenTo(this.collection, "add", this.addChildView, this);
2690
+ this.listenTo(this.collection, "remove", this.removeItemView, this);
2691
+ this.listenTo(this.collection, "reset", this.render, this);
1911
2692
  }
1912
2693
  },
1913
2694
 
@@ -1915,21 +2696,16 @@ Marionette.CollectionView = Marionette.View.extend({
1915
2696
  addChildView: function(item, collection, options){
1916
2697
  this.closeEmptyView();
1917
2698
  var ItemView = this.getItemView(item);
1918
-
1919
- var index;
1920
- if(options && options.index){
1921
- index = options.index;
1922
- } else {
1923
- index = 0;
1924
- }
1925
-
1926
- return this.addItemView(item, ItemView, index);
2699
+ var index = this.collection.indexOf(item);
2700
+ this.addItemView(item, ItemView, index);
1927
2701
  },
1928
2702
 
1929
2703
  // Override from `Marionette.View` to guarantee the `onShow` method
1930
2704
  // of child views is called.
1931
2705
  onShowCalled: function(){
1932
- this.onShowCallbacks.run();
2706
+ this.children.each(function(child){
2707
+ Marionette.triggerMethod.call(child, "show");
2708
+ });
1933
2709
  },
1934
2710
 
1935
2711
  // Internal method to trigger the before render callbacks
@@ -1981,7 +2757,8 @@ Marionette.CollectionView = Marionette.View.extend({
1981
2757
  // a collection of item views, when the collection is
1982
2758
  // empty
1983
2759
  showEmptyView: function(){
1984
- var EmptyView = this.options.emptyView || this.emptyView;
2760
+ var EmptyView = Marionette.getOption(this, "emptyView");
2761
+
1985
2762
  if (EmptyView && !this._showingEmptyView){
1986
2763
  this._showingEmptyView = true;
1987
2764
  var model = new Backbone.Model();
@@ -2003,7 +2780,7 @@ Marionette.CollectionView = Marionette.View.extend({
2003
2780
  // or from the `itemView` in the object definition. The "options"
2004
2781
  // takes precedence.
2005
2782
  getItemView: function(item){
2006
- var itemView = this.options.itemView || this.itemView;
2783
+ var itemView = Marionette.getOption(this, "itemView");
2007
2784
 
2008
2785
  if (!itemView){
2009
2786
  var err = new Error("An `itemView` must be specified");
@@ -2019,37 +2796,52 @@ Marionette.CollectionView = Marionette.View.extend({
2019
2796
  addItemView: function(item, ItemView, index){
2020
2797
  var that = this;
2021
2798
 
2022
- var view = this.buildItemView(item, ItemView);
2799
+ // get the itemViewOptions if any were specified
2800
+ var itemViewOptions = Marionette.getOption(this, "itemViewOptions");
2801
+ if (_.isFunction(itemViewOptions)){
2802
+ itemViewOptions = itemViewOptions.call(this, item);
2803
+ }
2804
+
2805
+ // build the view
2806
+ var view = this.buildItemView(item, ItemView, itemViewOptions);
2807
+
2808
+ // set up the child view event forwarding
2809
+ this.addChildViewEventForwarding(view);
2810
+
2811
+ // this view is about to be added
2812
+ this.triggerMethod("before:item:added", view);
2023
2813
 
2024
2814
  // Store the child view itself so we can properly
2025
2815
  // remove and/or close it later
2026
- this.storeChild(view);
2027
- this.triggerMethod("item:added", view);
2816
+ this.children.add(view);
2817
+
2818
+ // call the "show" method if the collection view
2819
+ // has already been shown
2820
+ if (this._isShown){
2821
+ Marionette.triggerMethod.call(view, "show");
2822
+ }
2028
2823
 
2029
2824
  // Render it and show it
2030
2825
  var renderResult = this.renderItemView(view, index);
2031
2826
 
2032
- // call onShow for child item views
2033
- if (view.onShow){
2034
- this.onShowCallbacks.add(view.onShow, view);
2035
- }
2827
+ // this view was added
2828
+ this.triggerMethod("after:item:added", view);
2829
+ },
2830
+
2831
+ // Set up the child view event forwarding. Uses an "itemview:"
2832
+ // prefix in front of all forwarded events.
2833
+ addChildViewEventForwarding: function(view){
2834
+ var prefix = Marionette.getOption(this, "itemViewEventPrefix");
2036
2835
 
2037
2836
  // Forward all child item view events through the parent,
2038
2837
  // prepending "itemview:" to the event name
2039
- var childBinding = this.bindTo(view, "all", function(){
2838
+ this.listenTo(view, "all", function(){
2040
2839
  var args = slice.call(arguments);
2041
- args[0] = "itemview:" + args[0];
2840
+ args[0] = prefix + ":" + args[0];
2042
2841
  args.splice(1, 0, view);
2043
2842
 
2044
- that.triggerMethod.apply(that, args);
2045
- });
2046
-
2047
- // Store all child event bindings so we can unbind
2048
- // them when removing / closing the child view
2049
- this.childBindings = this.childBindings || {};
2050
- this.childBindings[view.cid] = childBinding;
2051
-
2052
- return renderResult;
2843
+ Marionette.triggerMethod.apply(this, args);
2844
+ }, this);
2053
2845
  },
2054
2846
 
2055
2847
  // render the item view
@@ -2059,38 +2851,44 @@ Marionette.CollectionView = Marionette.View.extend({
2059
2851
  },
2060
2852
 
2061
2853
  // Build an `itemView` for every model in the collection.
2062
- buildItemView: function(item, ItemView){
2063
- var itemViewOptions;
2064
-
2065
- if (_.isFunction(this.itemViewOptions)){
2066
- itemViewOptions = this.itemViewOptions(item);
2067
- } else {
2068
- itemViewOptions = this.itemViewOptions;
2069
- }
2070
-
2854
+ buildItemView: function(item, ItemViewType, itemViewOptions){
2071
2855
  var options = _.extend({model: item}, itemViewOptions);
2072
- var view = new ItemView(options);
2856
+ var view = new ItemViewType(options);
2073
2857
  return view;
2074
2858
  },
2075
2859
 
2076
- // Remove the child view and close it
2860
+ // get the child view by item it holds, and remove it
2077
2861
  removeItemView: function(item){
2078
- var view = this.children[item.cid];
2862
+ var view = this.children.findByModel(item);
2863
+ this.removeChildView(view);
2864
+ this.checkEmpty();
2865
+ },
2866
+
2867
+ // Remove the child view and close it
2868
+ removeChildView: function(view){
2869
+
2870
+ // shut down the child view properly,
2871
+ // including events that the collection has from it
2079
2872
  if (view){
2080
- var childBinding = this.childBindings[view.cid];
2081
- if (childBinding) {
2082
- this.unbindFrom(childBinding);
2083
- delete this.childBindings[view.cid];
2873
+ this.stopListening(view);
2874
+
2875
+ if (view.close){
2876
+ view.close();
2084
2877
  }
2085
- view.close();
2086
- delete this.children[item.cid];
2878
+
2879
+ this.children.remove(view);
2087
2880
  }
2088
2881
 
2882
+ this.triggerMethod("item:removed", view);
2883
+ },
2884
+
2885
+ // helper to show the empty view if the collection is empty
2886
+ checkEmpty: function() {
2887
+ // check if we're empty now, and if we are, show the
2888
+ // empty view
2089
2889
  if (!this.collection || this.collection.length === 0){
2090
2890
  this.showEmptyView();
2091
2891
  }
2092
-
2093
- this.triggerMethod("item:removed", view);
2094
2892
  },
2095
2893
 
2096
2894
  // Append the HTML to the collection's `el`.
@@ -2100,16 +2898,10 @@ Marionette.CollectionView = Marionette.View.extend({
2100
2898
  collectionView.$el.append(itemView.el);
2101
2899
  },
2102
2900
 
2103
- // Store references to all of the child `itemView`
2104
- // instances so they can be managed and cleaned up, later.
2105
- storeChild: function(view){
2106
- this.children[view.model.cid] = view;
2107
- },
2108
-
2109
2901
  // Internal method to set up the `children` object for
2110
2902
  // storing all of the child views
2111
- initChildViewStorage: function(){
2112
- this.children = {};
2903
+ _initChildViewStorage: function(){
2904
+ this.children = new Backbone.ChildViewContainer();
2113
2905
  },
2114
2906
 
2115
2907
  // Handle cleanup and other closing needs for
@@ -2120,18 +2912,18 @@ Marionette.CollectionView = Marionette.View.extend({
2120
2912
  this.triggerMethod("collection:before:close");
2121
2913
  this.closeChildren();
2122
2914
  this.triggerMethod("collection:closed");
2123
- Marionette.View.prototype.close.apply(this, arguments);
2915
+
2916
+ var args = Array.prototype.slice.apply(arguments);
2917
+ Marionette.View.prototype.close.apply(this, args);
2124
2918
  },
2125
2919
 
2126
2920
  // Close the child views that this collection view
2127
2921
  // is holding on to, if any
2128
2922
  closeChildren: function(){
2129
- var that = this;
2130
- if (this.children){
2131
- _.each(_.clone(this.children), function(childView){
2132
- that.removeItemView(childView.model);
2133
- });
2134
- }
2923
+ this.children.each(function(child){
2924
+ this.removeChildView(child);
2925
+ }, this);
2926
+ this.checkEmpty();
2135
2927
  }
2136
2928
  });
2137
2929
 
@@ -2144,18 +2936,20 @@ Marionette.CollectionView = Marionette.View.extend({
2144
2936
  // an item view as `modelView`, for the top leaf
2145
2937
  Marionette.CompositeView = Marionette.CollectionView.extend({
2146
2938
  constructor: function(options){
2147
- Marionette.CollectionView.apply(this, arguments);
2939
+ var args = Array.prototype.slice.apply(arguments);
2940
+ Marionette.CollectionView.apply(this, args);
2941
+
2148
2942
  this.itemView = this.getItemView();
2149
2943
  },
2150
2944
 
2151
2945
  // Configured the initial events that the composite view
2152
2946
  // binds to. Override this method to prevent the initial
2153
2947
  // events, or to add your own initial events.
2154
- initialEvents: function(){
2948
+ _initialEvents: function(){
2155
2949
  if (this.collection){
2156
- this.bindTo(this.collection, "add", this.addChildView, this);
2157
- this.bindTo(this.collection, "remove", this.removeItemView, this);
2158
- this.bindTo(this.collection, "reset", this.renderCollection, this);
2950
+ this.listenTo(this.collection, "add", this.addChildView, this);
2951
+ this.listenTo(this.collection, "remove", this.removeItemView, this);
2952
+ this.listenTo(this.collection, "reset", this.renderCollection, this);
2159
2953
  }
2160
2954
  },
2161
2955
 
@@ -2164,7 +2958,7 @@ Marionette.CompositeView = Marionette.CollectionView.extend({
2164
2958
  // `this.itemView` or Marionette.CompositeView if no `itemView`
2165
2959
  // has been defined
2166
2960
  getItemView: function(item){
2167
- var itemView = this.options.itemView || this.itemView || this.constructor;
2961
+ var itemView = Marionette.getOption(this, "itemView") || this.constructor;
2168
2962
 
2169
2963
  if (!itemView){
2170
2964
  var err = new Error("An `itemView` must be specified");
@@ -2183,271 +2977,97 @@ Marionette.CompositeView = Marionette.CollectionView.extend({
2183
2977
 
2184
2978
  if (this.model){
2185
2979
  data = this.model.toJSON();
2186
- }
2187
-
2188
- data = this.mixinTemplateHelpers(data);
2189
-
2190
- return data;
2191
- },
2192
-
2193
- // Renders the model once, and the collection once. Calling
2194
- // this again will tell the model's view to re-render itself
2195
- // but the collection will not re-render.
2196
- render: function(){
2197
- this.isClosed = false;
2198
-
2199
- this.resetItemViewContainer();
2200
-
2201
- var html = this.renderModel();
2202
- this.$el.html(html);
2203
-
2204
- // the ui bindings is done here and not at the end of render since they
2205
- // will not be available until after the model is rendered, but should be
2206
- // available before the collection is rendered.
2207
- this.bindUIElements();
2208
-
2209
- this.triggerMethod("composite:model:rendered");
2210
- this.triggerMethod("render");
2211
-
2212
- this.renderCollection();
2213
- this.triggerMethod("composite:rendered");
2214
- return this;
2215
- },
2216
-
2217
- // Render the collection for the composite view
2218
- renderCollection: function(){
2219
- Marionette.CollectionView.prototype.render.apply(this, arguments);
2220
- this.triggerMethod("composite:collection:rendered");
2221
- },
2222
-
2223
- // Render an individual model, if we have one, as
2224
- // part of a composite view (branch / leaf). For example:
2225
- // a treeview.
2226
- renderModel: function(){
2227
- var data = {};
2228
- data = this.serializeData();
2229
-
2230
- var template = this.getTemplate();
2231
- return Marionette.Renderer.render(template, data);
2232
- },
2233
-
2234
- // Appends the `el` of itemView instances to the specified
2235
- // `itemViewContainer` (a jQuery selector). Override this method to
2236
- // provide custom logic of how the child item view instances have their
2237
- // HTML appended to the composite view instance.
2238
- appendHtml: function(cv, iv){
2239
- var $container = this.getItemViewContainer(cv);
2240
- $container.append(iv.el);
2241
- },
2242
-
2243
- // Internal method to ensure an `$itemViewContainer` exists, for the
2244
- // `appendHtml` method to use.
2245
- getItemViewContainer: function(containerView){
2246
- if ("$itemViewContainer" in containerView){
2247
- return containerView.$itemViewContainer;
2248
- }
2249
-
2250
- var container;
2251
- if (containerView.itemViewContainer){
2252
-
2253
- var selector = _.result(containerView, "itemViewContainer");
2254
- container = containerView.$(selector);
2255
- if (container.length <= 0) {
2256
- var err = new Error("The specified `itemViewContainer` was not found: " + containerView.itemViewContainer);
2257
- err.name = "ItemViewContainerMissingError";
2258
- throw err;
2259
- }
2260
-
2261
- } else {
2262
- container = containerView.$el;
2263
- }
2264
-
2265
- containerView.$itemViewContainer = container;
2266
- return container;
2267
- },
2268
-
2269
- // Internal method to reset the `$itemViewContainer` on render
2270
- resetItemViewContainer: function(){
2271
- if (this.$itemViewContainer){
2272
- delete this.$itemViewContainer;
2273
- }
2274
- }
2275
- });
2276
-
2277
-
2278
- // Region
2279
- // ------
2280
- //
2281
- // Manage the visual regions of your composite application. See
2282
- // http://lostechies.com/derickbailey/2011/12/12/composite-js-apps-regions-and-region-managers/
2283
-
2284
- Marionette.Region = function(options){
2285
- this.options = options || {};
2286
-
2287
- var el = this.options.el;
2288
- delete this.options.el;
2289
-
2290
- Marionette.addEventBinder(this);
2291
-
2292
- if (el){
2293
- this.el = el;
2294
- }
2295
-
2296
- if (!this.el){
2297
- var err = new Error("An 'el' must be specified for a region.");
2298
- err.name = "NoElError";
2299
- throw err;
2300
- }
2301
-
2302
- if (this.initialize){
2303
- this.initialize.apply(this, arguments);
2304
- }
2305
- };
2306
-
2307
-
2308
- // Region Type methods
2309
- // -------------------
2310
-
2311
- _.extend(Marionette.Region, {
2312
-
2313
- // Build an instance of a region by passing in a configuration object
2314
- // and a default region type to use if none is specified in the config.
2315
- //
2316
- // The config object should either be a string as a jQuery DOM selector,
2317
- // a Region type directly, or an object literal that specifies both
2318
- // a selector and regionType:
2319
- //
2320
- // ```js
2321
- // {
2322
- // selector: "#foo",
2323
- // regionType: MyCustomRegion
2324
- // }
2325
- // ```
2326
- //
2327
- buildRegion: function(regionConfig, defaultRegionType){
2328
- var regionIsString = (typeof regionConfig === "string");
2329
- var regionSelectorIsString = (typeof regionConfig.selector === "string");
2330
- var regionTypeIsUndefined = (typeof regionConfig.regionType === "undefined");
2331
- var regionIsType = (typeof regionConfig === "function");
2332
-
2333
- if (!regionIsType && !regionIsString && !regionSelectorIsString) {
2334
- throw new Error("Region must be specified as a Region type, a selector string or an object with selector property");
2335
- }
2336
-
2337
- var selector, RegionType;
2338
-
2339
- // get the selector for the region
2340
-
2341
- if (regionIsString) {
2342
- selector = regionConfig;
2343
- }
2344
-
2345
- if (regionConfig.selector) {
2346
- selector = regionConfig.selector;
2347
- }
2348
-
2349
- // get the type for the region
2350
-
2351
- if (regionIsType){
2352
- RegionType = regionConfig;
2353
- }
2354
-
2355
- if (!regionIsType && regionTypeIsUndefined) {
2356
- RegionType = defaultRegionType;
2357
- }
2358
-
2359
- if (regionConfig.regionType) {
2360
- RegionType = regionConfig.regionType;
2361
- }
2362
-
2363
- // build the region instance
2364
-
2365
- var regionManager = new RegionType({
2366
- el: selector
2367
- });
2368
-
2369
- return regionManager;
2370
- }
2371
-
2372
- });
2373
-
2374
- // Region Instance Methods
2375
- // -----------------------
2980
+ }
2376
2981
 
2377
- _.extend(Marionette.Region.prototype, Backbone.Events, {
2982
+ return data;
2983
+ },
2378
2984
 
2379
- // Displays a backbone view instance inside of the region.
2380
- // Handles calling the `render` method for you. Reads content
2381
- // directly from the `el` attribute. Also calls an optional
2382
- // `onShow` and `close` method on your view, just after showing
2383
- // or just before closing the view, respectively.
2384
- show: function(view){
2985
+ // Renders the model once, and the collection once. Calling
2986
+ // this again will tell the model's view to re-render itself
2987
+ // but the collection will not re-render.
2988
+ render: function(){
2989
+ this.isClosed = false;
2385
2990
 
2386
- this.ensureEl();
2387
- this.close();
2991
+ this.resetItemViewContainer();
2388
2992
 
2389
- view.render();
2390
- this.open(view);
2993
+ var html = this.renderModel();
2994
+ this.$el.html(html);
2391
2995
 
2392
- if (view.onShow) { view.onShow(); }
2393
- view.trigger("show");
2996
+ // the ui bindings is done here and not at the end of render since they
2997
+ // will not be available until after the model is rendered, but should be
2998
+ // available before the collection is rendered.
2999
+ this.bindUIElements();
2394
3000
 
2395
- if (this.onShow) { this.onShow(view); }
2396
- this.trigger("view:show", view);
3001
+ this.triggerMethod("composite:model:rendered");
2397
3002
 
2398
- this.currentView = view;
3003
+ this.renderCollection();
3004
+ this.triggerMethod("composite:rendered");
3005
+ return this;
2399
3006
  },
2400
3007
 
2401
- ensureEl: function(){
2402
- if (!this.$el || this.$el.length === 0){
2403
- this.$el = this.getEl(this.el);
2404
- }
3008
+ // Render the collection for the composite view
3009
+ renderCollection: function(){
3010
+ var args = Array.prototype.slice.apply(arguments);
3011
+ Marionette.CollectionView.prototype.render.apply(this, args);
3012
+
3013
+ this.triggerMethod("composite:collection:rendered");
2405
3014
  },
2406
3015
 
2407
- // Override this method to change how the region finds the
2408
- // DOM element that it manages. Return a jQuery selector object.
2409
- getEl: function(selector){
2410
- return $(selector);
3016
+ // Render an individual model, if we have one, as
3017
+ // part of a composite view (branch / leaf). For example:
3018
+ // a treeview.
3019
+ renderModel: function(){
3020
+ var data = {};
3021
+ data = this.serializeData();
3022
+ data = this.mixinTemplateHelpers(data);
3023
+
3024
+ var template = this.getTemplate();
3025
+ return Marionette.Renderer.render(template, data);
2411
3026
  },
2412
3027
 
2413
- // Override this method to change how the new view is
2414
- // appended to the `$el` that the region is managing
2415
- open: function(view){
2416
- this.$el.html(view.el);
3028
+ // Appends the `el` of itemView instances to the specified
3029
+ // `itemViewContainer` (a jQuery selector). Override this method to
3030
+ // provide custom logic of how the child item view instances have their
3031
+ // HTML appended to the composite view instance.
3032
+ appendHtml: function(cv, iv){
3033
+ var $container = this.getItemViewContainer(cv);
3034
+ $container.append(iv.el);
2417
3035
  },
2418
3036
 
2419
- // Close the current view, if there is one. If there is no
2420
- // current view, it does nothing and returns immediately.
2421
- close: function(){
2422
- var view = this.currentView;
2423
- if (!view || view.isClosed){ return; }
3037
+ // Internal method to ensure an `$itemViewContainer` exists, for the
3038
+ // `appendHtml` method to use.
3039
+ getItemViewContainer: function(containerView){
3040
+ if ("$itemViewContainer" in containerView){
3041
+ return containerView.$itemViewContainer;
3042
+ }
2424
3043
 
2425
- if (view.close) { view.close(); }
2426
- this.trigger("view:closed", view);
3044
+ var container;
3045
+ if (containerView.itemViewContainer){
2427
3046
 
2428
- delete this.currentView;
2429
- },
3047
+ var selector = _.result(containerView, "itemViewContainer");
3048
+ container = containerView.$(selector);
3049
+ if (container.length <= 0) {
3050
+ var err = new Error("The specified `itemViewContainer` was not found: " + containerView.itemViewContainer);
3051
+ err.name = "ItemViewContainerMissingError";
3052
+ throw err;
3053
+ }
2430
3054
 
2431
- // Attach an existing view to the region. This
2432
- // will not call `render` or `onShow` for the new view,
2433
- // and will not replace the current HTML for the `el`
2434
- // of the region.
2435
- attachView: function(view){
2436
- this.currentView = view;
3055
+ } else {
3056
+ container = containerView.$el;
3057
+ }
3058
+
3059
+ containerView.$itemViewContainer = container;
3060
+ return container;
2437
3061
  },
2438
3062
 
2439
- // Reset the region by closing any existing view and
2440
- // clearing out the cached `$el`. The next time a view
2441
- // is shown via this region, the region will re-query the
2442
- // DOM for the region's `el`.
2443
- reset: function(){
2444
- this.close();
2445
- delete this.$el;
3063
+ // Internal method to reset the `$itemViewContainer` on render
3064
+ resetItemViewContainer: function(){
3065
+ if (this.$itemViewContainer){
3066
+ delete this.$itemViewContainer;
3067
+ }
2446
3068
  }
2447
3069
  });
2448
3070
 
2449
- // Copy the `extend` function used by Backbone's classes
2450
- Marionette.Region.extend = Marionette.extend;
2451
3071
 
2452
3072
  // Layout
2453
3073
  // ------
@@ -2464,8 +3084,11 @@ Marionette.Layout = Marionette.ItemView.extend({
2464
3084
  // Ensure the regions are avialable when the `initialize` method
2465
3085
  // is called.
2466
3086
  constructor: function () {
3087
+ this._firstRender = true;
2467
3088
  this.initializeRegions();
2468
- Backbone.Marionette.ItemView.apply(this, arguments);
3089
+
3090
+ var args = Array.prototype.slice.apply(arguments);
3091
+ Marionette.ItemView.apply(this, args);
2469
3092
  },
2470
3093
 
2471
3094
  // Layout's render will use the existing region objects the
@@ -2473,16 +3096,21 @@ Marionette.Layout = Marionette.ItemView.extend({
2473
3096
  // views that the regions are showing and then reset the `el`
2474
3097
  // for the regions to the newly rendered DOM elements.
2475
3098
  render: function(){
2476
- // If this is not the first render call, then we need to
2477
- // re-initializing the `el` for each region
2478
- if (!this._firstRender){
3099
+
3100
+ if (this._firstRender){
3101
+ // if this is the first render, don't do anything to
3102
+ // reset the regions
3103
+ this._firstRender = false;
3104
+ } else {
3105
+ // If this is not the first render call, then we need to
3106
+ // re-initializing the `el` for each region
2479
3107
  this.closeRegions();
2480
3108
  this.reInitializeRegions();
2481
- } else {
2482
- this._firstRender = false;
2483
3109
  }
2484
3110
 
2485
- var result = Marionette.ItemView.prototype.render.apply(this, arguments);
3111
+ var args = Array.prototype.slice.apply(arguments);
3112
+ var result = Marionette.ItemView.prototype.render.apply(this, args);
3113
+
2486
3114
  return result;
2487
3115
  },
2488
3116
 
@@ -2492,7 +3120,9 @@ Marionette.Layout = Marionette.ItemView.extend({
2492
3120
 
2493
3121
  this.closeRegions();
2494
3122
  this.destroyRegions();
2495
- Backbone.Marionette.ItemView.prototype.close.call(this, arguments);
3123
+
3124
+ var args = Array.prototype.slice.apply(arguments);
3125
+ Marionette.ItemView.prototype.close.apply(this, args);
2496
3126
  },
2497
3127
 
2498
3128
  // Initialize the regions that have been defined in a
@@ -2555,87 +3185,6 @@ Marionette.Layout = Marionette.ItemView.extend({
2555
3185
  });
2556
3186
 
2557
3187
 
2558
- // Application
2559
- // -----------
2560
-
2561
- // Contain and manage the composite application as a whole.
2562
- // Stores and starts up `Region` objects, includes an
2563
- // event aggregator as `app.vent`
2564
- Marionette.Application = function(options){
2565
- this.initCallbacks = new Marionette.Callbacks();
2566
- this.vent = new Marionette.EventAggregator();
2567
- this.commands = new Backbone.Wreqr.Commands();
2568
- this.reqres = new Backbone.Wreqr.RequestResponse();
2569
- this.submodules = {};
2570
-
2571
- _.extend(this, options);
2572
-
2573
- Marionette.addEventBinder(this);
2574
- };
2575
-
2576
- _.extend(Marionette.Application.prototype, Backbone.Events, {
2577
- // Command execution, facilitated by Backbone.Wreqr.Commands
2578
- execute: function(){
2579
- this.commands.execute.apply(this.commands, arguments);
2580
- },
2581
-
2582
- // Request/response, facilitated by Backbone.Wreqr.RequestResponse
2583
- request: function(){
2584
- return this.reqres.request.apply(this.reqres, arguments);
2585
- },
2586
-
2587
- // Add an initializer that is either run at when the `start`
2588
- // method is called, or run immediately if added after `start`
2589
- // has already been called.
2590
- addInitializer: function(initializer){
2591
- this.initCallbacks.add(initializer);
2592
- },
2593
-
2594
- // kick off all of the application's processes.
2595
- // initializes all of the regions that have been added
2596
- // to the app, and runs all of the initializer functions
2597
- start: function(options){
2598
- this.trigger("initialize:before", options);
2599
- this.initCallbacks.run(options, this);
2600
- this.trigger("initialize:after", options);
2601
-
2602
- this.trigger("start", options);
2603
- },
2604
-
2605
- // Add regions to your app.
2606
- // Accepts a hash of named strings or Region objects
2607
- // addRegions({something: "#someRegion"})
2608
- // addRegions{{something: Region.extend({el: "#someRegion"}) });
2609
- addRegions: function(regions){
2610
- var that = this;
2611
- _.each(regions, function (region, name) {
2612
- var regionManager = Marionette.Region.buildRegion(region, Marionette.Region);
2613
- that[name] = regionManager;
2614
- });
2615
- },
2616
-
2617
- // Removes a region from your app.
2618
- // Accepts the regions name
2619
- // removeRegion('myRegion')
2620
- removeRegion: function(region) {
2621
- this[region].close();
2622
- delete this[region];
2623
- },
2624
-
2625
- // Create a module, attached to the application
2626
- module: function(moduleNames, moduleDefinition){
2627
- // slice the args, and add this application object as the
2628
- // first argument of the array
2629
- var args = slice.call(arguments);
2630
- args.unshift(this);
2631
-
2632
- // see the Marionette.Module object for more information
2633
- return Marionette.Module.create.apply(Marionette.Module, args);
2634
- }
2635
- });
2636
-
2637
- // Copy the `extend` function used by Backbone's classes
2638
- Marionette.Application.extend = Marionette.extend;
2639
3188
 
2640
3189
  // AppRouter
2641
3190
  // ---------
@@ -2658,13 +3207,13 @@ Marionette.Application.extend = Marionette.extend;
2658
3207
  Marionette.AppRouter = Backbone.Router.extend({
2659
3208
 
2660
3209
  constructor: function(options){
2661
- Backbone.Router.prototype.constructor.call(this, options);
3210
+ var args = Array.prototype.slice.apply(arguments);
3211
+ Backbone.Router.prototype.constructor.apply(this, args);
3212
+
3213
+ this.options = options;
2662
3214
 
2663
3215
  if (this.appRoutes){
2664
- var controller = this.controller;
2665
- if (options && options.controller) {
2666
- controller = options.controller;
2667
- }
3216
+ var controller = Marionette.getOption(this, "controller");
2668
3217
  this.processAppRoutes(controller, this.appRoutes);
2669
3218
  }
2670
3219
  },
@@ -2684,25 +3233,109 @@ Marionette.AppRouter = Backbone.Router.extend({
2684
3233
  }
2685
3234
  }
2686
3235
 
2687
- routesLength = routes.length;
2688
- for (i = 0; i < routesLength; i++){
2689
- route = routes[i][0];
2690
- methodName = routes[i][1];
2691
- method = controller[methodName];
3236
+ routesLength = routes.length;
3237
+ for (i = 0; i < routesLength; i++){
3238
+ route = routes[i][0];
3239
+ methodName = routes[i][1];
3240
+ method = controller[methodName];
3241
+
3242
+ if (!method){
3243
+ var msg = "Method '" + methodName + "' was not found on the controller";
3244
+ var err = new Error(msg);
3245
+ err.name = "NoMethodError";
3246
+ throw err;
3247
+ }
3248
+
3249
+ method = _.bind(method, controller);
3250
+ router.route(route, methodName, method);
3251
+ }
3252
+ }
3253
+ });
3254
+
3255
+
3256
+ // Application
3257
+ // -----------
3258
+
3259
+ // Contain and manage the composite application as a whole.
3260
+ // Stores and starts up `Region` objects, includes an
3261
+ // event aggregator as `app.vent`
3262
+ Marionette.Application = function(options){
3263
+ this.initCallbacks = new Marionette.Callbacks();
3264
+ this.vent = new Backbone.Wreqr.EventAggregator();
3265
+ this.commands = new Backbone.Wreqr.Commands();
3266
+ this.reqres = new Backbone.Wreqr.RequestResponse();
3267
+ this.submodules = {};
3268
+
3269
+ _.extend(this, options);
3270
+
3271
+ this.triggerMethod = Marionette.triggerMethod;
3272
+ };
3273
+
3274
+ _.extend(Marionette.Application.prototype, Backbone.Events, {
3275
+ // Command execution, facilitated by Backbone.Wreqr.Commands
3276
+ execute: function(){
3277
+ var args = Array.prototype.slice.apply(arguments);
3278
+ this.commands.execute.apply(this.commands, args);
3279
+ },
3280
+
3281
+ // Request/response, facilitated by Backbone.Wreqr.RequestResponse
3282
+ request: function(){
3283
+ var args = Array.prototype.slice.apply(arguments);
3284
+ return this.reqres.request.apply(this.reqres, args);
3285
+ },
3286
+
3287
+ // Add an initializer that is either run at when the `start`
3288
+ // method is called, or run immediately if added after `start`
3289
+ // has already been called.
3290
+ addInitializer: function(initializer){
3291
+ this.initCallbacks.add(initializer);
3292
+ },
3293
+
3294
+ // kick off all of the application's processes.
3295
+ // initializes all of the regions that have been added
3296
+ // to the app, and runs all of the initializer functions
3297
+ start: function(options){
3298
+ this.triggerMethod("initialize:before", options);
3299
+ this.initCallbacks.run(options, this);
3300
+ this.triggerMethod("initialize:after", options);
3301
+
3302
+ this.triggerMethod("start", options);
3303
+ },
3304
+
3305
+ // Add regions to your app.
3306
+ // Accepts a hash of named strings or Region objects
3307
+ // addRegions({something: "#someRegion"})
3308
+ // addRegions{{something: Region.extend({el: "#someRegion"}) });
3309
+ addRegions: function(regions){
3310
+ var that = this;
3311
+ _.each(regions, function (region, name) {
3312
+ var regionManager = Marionette.Region.buildRegion(region, Marionette.Region);
3313
+ that[name] = regionManager;
3314
+ });
3315
+ },
2692
3316
 
2693
- if (!method){
2694
- var msg = "Method '" + methodName + "' was not found on the controller";
2695
- var err = new Error(msg);
2696
- err.name = "NoMethodError";
2697
- throw err;
2698
- }
3317
+ // Removes a region from your app.
3318
+ // Accepts the regions name
3319
+ // removeRegion('myRegion')
3320
+ removeRegion: function(region) {
3321
+ this[region].close();
3322
+ delete this[region];
3323
+ },
2699
3324
 
2700
- method = _.bind(method, controller);
2701
- router.route(route, methodName, method);
2702
- }
3325
+ // Create a module, attached to the application
3326
+ module: function(moduleNames, moduleDefinition){
3327
+ // slice the args, and add this application object as the
3328
+ // first argument of the array
3329
+ var args = slice.call(arguments);
3330
+ args.unshift(this);
3331
+
3332
+ // see the Marionette.Module object for more information
3333
+ return Marionette.Module.create.apply(Marionette.Module, args);
2703
3334
  }
2704
3335
  });
2705
3336
 
3337
+ // Copy the `extend` function used by Backbone's classes
3338
+ Marionette.Application.extend = Marionette.extend;
2706
3339
 
2707
3340
  // Module
2708
3341
  // ------
@@ -2718,14 +3351,13 @@ Marionette.Module = function(moduleName, app){
2718
3351
  this._setupInitializersAndFinalizers();
2719
3352
 
2720
3353
  // store the configuration for this module
2721
- this.config = {};
2722
- this.config.app = app;
3354
+ this.app = app;
3355
+ this.startWithParent = true;
2723
3356
 
2724
- // extend this module with an event binder
2725
- Marionette.addEventBinder(this);
3357
+ this.triggerMethod = Marionette.triggerMethod;
2726
3358
  };
2727
3359
 
2728
- // Extend the Module prototype with events / bindTo, so that the module
3360
+ // Extend the Module prototype with events / listenTo, so that the module
2729
3361
  // can be used as an event aggregator or pub/sub.
2730
3362
  _.extend(Marionette.Module.prototype, Backbone.Events, {
2731
3363
 
@@ -2744,19 +3376,28 @@ _.extend(Marionette.Module.prototype, Backbone.Events, {
2744
3376
 
2745
3377
  // Start the module, and run all of it's initializers
2746
3378
  start: function(options){
2747
- // Prevent re-start the module
3379
+ // Prevent re-starting a module that is already started
2748
3380
  if (this._isInitialized){ return; }
2749
3381
 
2750
3382
  // start the sub-modules (depth-first hierarchy)
2751
3383
  _.each(this.submodules, function(mod){
2752
- if (mod.config.options.startWithParent){
3384
+ // check to see if we should start the sub-module with this parent
3385
+ var startWithParent = true;
3386
+ startWithParent = mod.startWithParent;
3387
+
3388
+ // start the sub-module
3389
+ if (startWithParent){
2753
3390
  mod.start(options);
2754
3391
  }
2755
3392
  });
2756
3393
 
2757
3394
  // run the callbacks to "start" the current module
3395
+ this.triggerMethod("before:start", options);
3396
+
2758
3397
  this._initializerCallbacks.run(options, this);
2759
3398
  this._isInitialized = true;
3399
+
3400
+ this.triggerMethod("start", options);
2760
3401
  },
2761
3402
 
2762
3403
  // Stop this module by running its finalizers and then stop all of
@@ -2766,16 +3407,20 @@ _.extend(Marionette.Module.prototype, Backbone.Events, {
2766
3407
  if (!this._isInitialized){ return; }
2767
3408
  this._isInitialized = false;
2768
3409
 
3410
+ Marionette.triggerMethod.call(this, "before:stop");
3411
+
2769
3412
  // stop the sub-modules; depth-first, to make sure the
2770
3413
  // sub-modules are stopped / finalized before parents
2771
3414
  _.each(this.submodules, function(mod){ mod.stop(); });
2772
3415
 
2773
3416
  // run the finalizers
2774
- this._finalizerCallbacks.run();
3417
+ this._finalizerCallbacks.run(undefined,this);
2775
3418
 
2776
3419
  // reset the initializers and finalizers
2777
3420
  this._initializerCallbacks.reset();
2778
3421
  this._finalizerCallbacks.reset();
3422
+
3423
+ Marionette.triggerMethod.call(this, "stop");
2779
3424
  },
2780
3425
 
2781
3426
  // Configure the module with a definition function and any custom args
@@ -2791,11 +3436,11 @@ _.extend(Marionette.Module.prototype, Backbone.Events, {
2791
3436
 
2792
3437
  // build the correct list of arguments for the module definition
2793
3438
  var args = _.flatten([
2794
- this,
2795
- this.config.app,
2796
- Backbone,
2797
- Marionette,
2798
- $, _,
3439
+ this,
3440
+ this.app,
3441
+ Backbone,
3442
+ Marionette,
3443
+ $, _,
2799
3444
  customArgs
2800
3445
  ]);
2801
3446
 
@@ -2811,71 +3456,43 @@ _.extend(Marionette.Module.prototype, Backbone.Events, {
2811
3456
  }
2812
3457
  });
2813
3458
 
2814
- // Function level methods to create modules
3459
+ // Type methods to create modules
2815
3460
  _.extend(Marionette.Module, {
2816
3461
 
2817
- // Create a module, hanging off the app parameter as the parent object.
3462
+ // Create a module, hanging off the app parameter as the parent object.
2818
3463
  create: function(app, moduleNames, moduleDefinition){
2819
3464
  var that = this;
2820
- var parentModule = app;
2821
- moduleNames = moduleNames.split(".");
3465
+ var module = app;
2822
3466
 
2823
3467
  // get the custom args passed in after the module definition and
2824
3468
  // get rid of the module name and definition function
2825
3469
  var customArgs = slice.apply(arguments);
2826
3470
  customArgs.splice(0, 3);
2827
3471
 
2828
- // Loop through all the parts of the module definition
3472
+ // split the module names and get the length
3473
+ moduleNames = moduleNames.split(".");
2829
3474
  var length = moduleNames.length;
2830
- _.each(moduleNames, function(moduleName, i){
2831
- var isLastModuleInChain = (i === length-1);
2832
3475
 
2833
- var module = that._getModuleDefinition(parentModule, moduleName, app);
2834
- module.config.options = that._getModuleOptions(parentModule, moduleDefinition);
2835
-
2836
- // if it's the first module in the chain, configure it
2837
- // for auto-start, as specified by the options
2838
- if (isLastModuleInChain){
2839
- that._configureAutoStart(app, module);
2840
- }
2841
-
2842
- // Only add a module definition and initializer when this is
2843
- // the last module in a "parent.child.grandchild" hierarchy of
2844
- // module names
2845
- if (isLastModuleInChain && module.config.options.hasDefinition){
2846
- module.addDefinition(module.config.options.definition, customArgs);
2847
- }
3476
+ // store the module definition for the last module in the chain
3477
+ var moduleDefinitions = [];
3478
+ moduleDefinitions[length-1] = moduleDefinition;
2848
3479
 
2849
- // Reset the parent module so that the next child
2850
- // in the list will be added to the correct parent
2851
- parentModule = module;
3480
+ // Loop through all the parts of the module definition
3481
+ _.each(moduleNames, function(moduleName, i){
3482
+ var parentModule = module;
3483
+ module = that._getModule(parentModule, moduleName, app);
3484
+ that._addModuleDefinition(parentModule, module, moduleDefinitions[i], customArgs);
2852
3485
  });
2853
3486
 
2854
3487
  // Return the last module in the definition chain
2855
- return parentModule;
2856
- },
2857
-
2858
- _configureAutoStart: function(app, module){
2859
- // Only add the initializer if it's the first module, and
2860
- // if it is set to auto-start, and if it has not yet been added
2861
- if (module.config.options.startWithParent && !module.config.autoStartConfigured){
2862
- // start the module when the app starts
2863
- app.addInitializer(function(options){
2864
- module.start(options);
2865
- });
2866
- }
2867
-
2868
- // prevent this module from being configured for
2869
- // auto start again. the first time the module
2870
- // is defined, determines it's auto-start
2871
- module.config.autoStartConfigured = true;
3488
+ return module;
2872
3489
  },
2873
3490
 
2874
- _getModuleDefinition: function(parentModule, moduleName, app){
3491
+ _getModule: function(parentModule, moduleName, app, def, args){
2875
3492
  // Get an existing module of this name if we have one
2876
3493
  var module = parentModule[moduleName];
2877
3494
 
2878
- if (!module){
3495
+ if (!module){
2879
3496
  // Create a new module if we don't have one
2880
3497
  module = new Marionette.Module(moduleName, app);
2881
3498
  parentModule[moduleName] = module;
@@ -2886,223 +3503,67 @@ _.extend(Marionette.Module, {
2886
3503
  return module;
2887
3504
  },
2888
3505
 
2889
- _getModuleOptions: function(parentModule, moduleDefinition){
2890
- // default to starting the module with the app
2891
- var options = {
2892
- startWithParent: true,
2893
- hasDefinition: !!moduleDefinition
2894
- };
2895
-
2896
- // short circuit if we don't have a module definition
2897
- if (!options.hasDefinition){ return options; }
2898
-
2899
- if (_.isFunction(moduleDefinition)){
2900
- // if the definition is a function, assign it directly
2901
- // and use the defaults
2902
- options.definition = moduleDefinition;
2903
-
2904
- } else {
3506
+ _addModuleDefinition: function(parentModule, module, def, args){
3507
+ var fn;
3508
+ var startWithParent;
2905
3509
 
2906
- // the definition is an object.
3510
+ if (_.isFunction(def)){
3511
+ // if a function is supplied for the module definition
3512
+ fn = def;
3513
+ startWithParent = true;
2907
3514
 
2908
- // grab the "define" attribute
2909
- options.hasDefinition = !!moduleDefinition.define;
2910
- options.definition = moduleDefinition.define;
3515
+ } else if (_.isObject(def)){
3516
+ // if an object is supplied
3517
+ fn = def.define;
3518
+ startWithParent = def.startWithParent;
2911
3519
 
2912
- // grab the "startWithParent" attribute if one exists
2913
- if (moduleDefinition.hasOwnProperty("startWithParent")){
2914
- options.startWithParent = moduleDefinition.startWithParent;
2915
- }
2916
- }
2917
-
2918
- return options;
2919
- }
2920
- });
2921
-
2922
- // Template Cache
2923
- // --------------
2924
-
2925
- // Manage templates stored in `<script>` blocks,
2926
- // caching them for faster access.
2927
- Marionette.TemplateCache = function(templateId){
2928
- this.templateId = templateId;
2929
- };
2930
-
2931
- // TemplateCache object-level methods. Manage the template
2932
- // caches from these method calls instead of creating
2933
- // your own TemplateCache instances
2934
- _.extend(Marionette.TemplateCache, {
2935
- templateCaches: {},
2936
-
2937
- // Get the specified template by id. Either
2938
- // retrieves the cached version, or loads it
2939
- // from the DOM.
2940
- get: function(templateId){
2941
- var that = this;
2942
- var cachedTemplate = this.templateCaches[templateId];
2943
-
2944
- if (!cachedTemplate){
2945
- cachedTemplate = new Marionette.TemplateCache(templateId);
2946
- this.templateCaches[templateId] = cachedTemplate;
2947
- }
2948
-
2949
- return cachedTemplate.load();
2950
- },
2951
-
2952
- // Clear templates from the cache. If no arguments
2953
- // are specified, clears all templates:
2954
- // `clear()`
2955
- //
2956
- // If arguments are specified, clears each of the
2957
- // specified templates from the cache:
2958
- // `clear("#t1", "#t2", "...")`
2959
- clear: function(){
2960
- var i;
2961
- var length = arguments.length;
2962
-
2963
- if (length > 0){
2964
- for(i=0; i<length; i++){
2965
- delete this.templateCaches[arguments[i]];
2966
- }
2967
3520
  } else {
2968
- this.templateCaches = {};
2969
- }
2970
- }
2971
- });
2972
-
2973
- // TemplateCache instance methods, allowing each
2974
- // template cache object to manage it's own state
2975
- // and know whether or not it has been loaded
2976
- _.extend(Marionette.TemplateCache.prototype, {
2977
-
2978
- // Internal method to load the template asynchronously.
2979
- load: function(){
2980
- var that = this;
2981
-
2982
- // Guard clause to prevent loading this template more than once
2983
- if (this.compiledTemplate){
2984
- return this.compiledTemplate;
3521
+ // if nothing is supplied
3522
+ startWithParent = true;
2985
3523
  }
2986
3524
 
2987
- // Load the template and compile it
2988
- var template = this.loadTemplate(this.templateId);
2989
- this.compiledTemplate = this.compileTemplate(template);
2990
-
2991
- return this.compiledTemplate;
2992
- },
2993
-
2994
- // Load a template from the DOM, by default. Override
2995
- // this method to provide your own template retrieval,
2996
- // such as asynchronous loading from a server.
2997
- loadTemplate: function(templateId){
2998
- var template = $(templateId).html();
2999
-
3000
- if (!template || template.length === 0){
3001
- var msg = "Could not find template: '" + templateId + "'";
3002
- var err = new Error(msg);
3003
- err.name = "NoTemplateError";
3004
- throw err;
3525
+ // add module definition if needed
3526
+ if (fn){
3527
+ module.addDefinition(fn, args);
3005
3528
  }
3006
3529
 
3007
- return template;
3008
- },
3009
-
3010
- // Pre-compile the template before caching it. Override
3011
- // this method if you do not need to pre-compile a template
3012
- // (JST / RequireJS for example) or if you want to change
3013
- // the template engine used (Handebars, etc).
3014
- compileTemplate: function(rawTemplate){
3015
- return _.template(rawTemplate);
3016
- }
3017
- });
3018
-
3019
-
3020
- // Renderer
3021
- // --------
3022
-
3023
- // Render a template with data by passing in the template
3024
- // selector and the data to render.
3025
- Marionette.Renderer = {
3026
-
3027
- // Render a template with data. The `template` parameter is
3028
- // passed to the `TemplateCache` object to retrieve the
3029
- // template function. Override this method to provide your own
3030
- // custom rendering and template handling for all of Marionette.
3031
- render: function(template, data){
3032
- var templateFunc = typeof template === 'function' ? template : Marionette.TemplateCache.get(template);
3033
- var html = templateFunc(data);
3034
- return html;
3035
- }
3036
- };
3037
-
3038
-
3039
- // Callbacks
3040
- // ---------
3041
-
3042
- // A simple way of managing a collection of callbacks
3043
- // and executing them at a later point in time, using jQuery's
3044
- // `Deferred` object.
3045
- Marionette.Callbacks = function(){
3046
- this._deferred = $.Deferred();
3047
- this._callbacks = [];
3048
- };
3530
+ // `and` the two together, ensuring a single `false` will prevent it
3531
+ // from starting with the parent
3532
+ var tmp = module.startWithParent;
3533
+ module.startWithParent = module.startWithParent && startWithParent;
3049
3534
 
3050
- _.extend(Marionette.Callbacks.prototype, {
3535
+ // setup auto-start if needed
3536
+ if (module.startWithParent && !module.startWithParentIsConfigured){
3051
3537
 
3052
- // Add a callback to be executed. Callbacks added here are
3053
- // guaranteed to execute, even if they are added after the
3054
- // `run` method is called.
3055
- add: function(callback, contextOverride){
3056
- this._callbacks.push({cb: callback, ctx: contextOverride});
3538
+ // only configure this once
3539
+ module.startWithParentIsConfigured = true;
3057
3540
 
3058
- this._deferred.done(function(context, options){
3059
- if (contextOverride){ context = contextOverride; }
3060
- callback.call(context, options);
3061
- });
3062
- },
3541
+ // add the module initializer config
3542
+ parentModule.addInitializer(function(options){
3543
+ if (module.startWithParent){
3544
+ module.start(options);
3545
+ }
3546
+ });
3063
3547
 
3064
- // Run all registered callbacks with the context specified.
3065
- // Additional callbacks can be added after this has been run
3066
- // and they will still be executed.
3067
- run: function(options, context){
3068
- this._deferred.resolve(context, options);
3069
- },
3548
+ }
3070
3549
 
3071
- // Resets the list of callbacks to be run, allowing the same list
3072
- // to be run multiple times - whenever the `run` method is called.
3073
- reset: function(){
3074
- var that = this;
3075
- var callbacks = this._callbacks;
3076
- this._deferred = $.Deferred();
3077
- this._callbacks = [];
3078
- _.each(callbacks, function(cb){
3079
- that.add(cb.cb, cb.ctx);
3080
- });
3081
3550
  }
3082
3551
  });
3083
3552
 
3084
3553
 
3085
- // Event Aggregator
3086
- // ----------------
3087
- // A pub-sub object that can be used to decouple various parts
3088
- // of an application through event-driven architecture.
3089
- //
3090
- // https://github.com/marionettejs/backbone.wreqr
3091
- Marionette.EventAggregator = Backbone.Wreqr.EventAggregator;
3092
-
3093
3554
 
3094
3555
  return Marionette;
3095
3556
  })(Backbone, _, $ || window.jQuery || window.Zepto || window.ender);
3096
3557
  /*!
3097
- * Tableling v0.0.10
3098
- * Copyright (c) 2012 Simon Oulevay (Alpha Hydrae) <hydrae.alpha@gmail.com>
3558
+ * Tableling v0.0.12
3559
+ * Copyright (c) 2012-2013 Simon Oulevay (Alpha Hydrae) <hydrae.alpha@gmail.com>
3099
3560
  * Distributed under MIT license
3100
3561
  * https://github.com/AlphaHydrae/tableling
3101
3562
  */
3102
3563
  Backbone.Tableling = Tableling = (function(Backbone, _, $){
3103
3564
 
3104
3565
  var Tableling = {
3105
- version : "0.0.10"
3566
+ version : "0.0.12"
3106
3567
  };
3107
3568
 
3108
3569
  // Tableling
@@ -3115,7 +3576,7 @@ Backbone.Tableling = Tableling = (function(Backbone, _, $){
3115
3576
  className: 'tableling',
3116
3577
 
3117
3578
  // Default table options can be overriden by subclasses.
3118
- tableling : {
3579
+ config : {
3119
3580
  page : 1
3120
3581
  },
3121
3582
 
@@ -3123,13 +3584,13 @@ Backbone.Tableling = Tableling = (function(Backbone, _, $){
3123
3584
  options = options || {};
3124
3585
 
3125
3586
  // Table options can also be overriden for each instance at construction.
3126
- this.tableling = _.extend(_.clone(this.tableling), this.filterConfig(options));
3587
+ this.config = _.extend(_.clone(this.config || {}), _.result(options, 'config') || {});
3127
3588
 
3128
3589
  // We use an event aggregator to manage the layout and its components.
3129
3590
  // You can use your own by passing a `vent` option.
3130
- this.vent = options.vent || new Backbone.Marionette.EventAggregator();
3591
+ this.vent = options.vent || new Backbone.Wreqr.EventAggregator();
3131
3592
 
3132
- this.fetchOptions = _.extend(_.clone(this.fetchOptions || {}), options.fetchOptions || {});
3593
+ this.fetchOptions = _.extend(_.clone(this.fetchOptions || {}), _.result(options, 'fetchOptions') || {});
3133
3594
 
3134
3595
  // Components should trigger the `table:update` event to update
3135
3596
  // the table (e.g. change page size, sort) and fetch the new data.
@@ -3140,7 +3601,7 @@ Backbone.Tableling = Tableling = (function(Backbone, _, $){
3140
3601
 
3141
3602
  // Called once rendering is complete. By default, it updates the table.
3142
3603
  setup : function() {
3143
- this.ventTrigger('table:setup', this.filterConfig(this.tableling, true));
3604
+ this.ventTrigger('table:setup', this.config);
3144
3605
  this.ventTrigger('table:update');
3145
3606
  },
3146
3607
 
@@ -3152,7 +3613,7 @@ Backbone.Tableling = Tableling = (function(Backbone, _, $){
3152
3613
  // ### Refreshing the table
3153
3614
  update : function(config, options) {
3154
3615
 
3155
- _.each(this.filterConfig(config || {}), _.bind(this.updateValue, this));
3616
+ _.each(config || {}, _.bind(this.updateValue, this));
3156
3617
 
3157
3618
  // Set the `refresh` option to false to update the table configuration
3158
3619
  // without refreshing.
@@ -3163,10 +3624,10 @@ Backbone.Tableling = Tableling = (function(Backbone, _, $){
3163
3624
 
3164
3625
  updateValue : function(value, key) {
3165
3626
  if (value && value.toString().length) {
3166
- this.tableling[key] = value;
3627
+ this.config[key] = value;
3167
3628
  } else {
3168
3629
  // Blank values are deleted to avoid sending them in ajax requests.
3169
- delete this.tableling[key];
3630
+ delete this.config[key];
3170
3631
  }
3171
3632
  },
3172
3633
 
@@ -3200,35 +3661,24 @@ Backbone.Tableling = Tableling = (function(Backbone, _, $){
3200
3661
 
3201
3662
  // ### Request
3202
3663
  requestData : function() {
3203
- return this.filterConfig(this.tableling);
3664
+ return this.config;
3204
3665
  },
3205
3666
 
3206
3667
  // ### Response
3207
3668
  processResponse : function(collection, response) {
3208
3669
 
3209
- this.tableling.length = collection.length;
3670
+ this.config.length = collection.length;
3210
3671
 
3211
3672
  // Tableling expects the response from a fetch to have a `total` property
3212
3673
  // which is the total number of items (not just in the current page).
3213
- this.tableling.total = response.total;
3674
+ this.config.total = response.total;
3214
3675
 
3215
3676
  // `tableling:refreshed` is triggered after every refresh. The first argument
3216
3677
  // is the current table configuration with the following additional meta data:
3217
3678
  //
3218
3679
  // * `total` - the total number of items
3219
3680
  // * `length` - the number of items in the current page
3220
- this.ventTrigger('table:refreshed', this.filterConfig(this.tableling, true));
3221
- },
3222
-
3223
- // ### Utilities
3224
- // Whitelists the given configuration to contain only table configuration properties.
3225
- // Pass `true` as the second argument to include meta data (i.e. total & length).
3226
- filterConfig : function(config, all) {
3227
- if (all) {
3228
- return _.pick(config, 'page', 'pageSize', 'quickSearch', 'sort', 'length', 'total');
3229
- } else {
3230
- return _.pick(config, 'page', 'pageSize', 'quickSearch', 'sort');
3231
- }
3681
+ this.ventTrigger('table:refreshed', this.config);
3232
3682
  },
3233
3683
 
3234
3684
  // Triggers an event in the event aggregator. If `Tableling.debug` is set, it also
@@ -3462,6 +3912,7 @@ Backbone.Tableling = Tableling = (function(Backbone, _, $){
3462
3912
  // TODO: add auto-sort
3463
3913
  this.vent = options.vent;
3464
3914
  this.sort = [];
3915
+ this.vent.on('table:setup', this.setSort, this);
3465
3916
  },
3466
3917
 
3467
3918
  updateSort : function(ev) {
@@ -3475,33 +3926,61 @@ Backbone.Tableling = Tableling = (function(Backbone, _, $){
3475
3926
 
3476
3927
  if (ev.shiftKey || this.sort.length == 1) {
3477
3928
 
3478
- var existing = _.find(this.sort, function(item) {
3479
- return item.field == field;
3929
+ var index = -1;
3930
+ _.find(this.sort, function(item, i) {
3931
+ if (item.split(' ')[0] == field) {
3932
+ index = i;
3933
+ }
3480
3934
  });
3481
3935
 
3482
- if (existing) {
3483
- existing.direction = existing.direction == 'asc' ? 'desc' : 'asc';
3484
- el.removeClass('sorting sorting-asc sorting-desc');
3485
- el.addClass('sorting-' + existing.direction);
3936
+ if (index >= 0) {
3937
+
3938
+ var parts = this.sort[index].split(' ');
3939
+ this.sort[index] = parts[0] + ' ' + (parts[1] == 'asc' ? 'desc' : 'asc');
3940
+ this.showSort();
3486
3941
  return this.vent.trigger('table:update', this.config());
3487
3942
  }
3488
3943
  }
3489
3944
 
3490
3945
  if (!ev.shiftKey) {
3491
3946
  this.sort.length = 0;
3492
- this.$el.find('thead th').removeClass('sorting sorting-asc sorting-desc').addClass('sorting');
3493
3947
  }
3494
3948
 
3495
- this.sort.push({
3496
- field: field,
3497
- direction: 'asc'
3498
- });
3949
+ this.sort.push(field + ' asc');
3499
3950
 
3500
- el.removeClass('sorting sorting-asc sorting-desc').addClass('sorting-asc');
3951
+ this.showSort();
3501
3952
 
3502
3953
  this.vent.trigger('table:update', this.config());
3503
3954
  },
3504
3955
 
3956
+ setSort : function(config) {
3957
+ if (config && config.sort) {
3958
+ this.sort = config.sort.slice(0);
3959
+ this.showSort();
3960
+ }
3961
+ },
3962
+
3963
+ showSort : function() {
3964
+
3965
+ this.$el.find('thead th').removeClass('sorting sorting-asc sorting-desc').addClass('sorting');
3966
+
3967
+ for (var i = 0; i < this.sort.length; i++) {
3968
+
3969
+ var parts = this.sort[i].split(' ');
3970
+ var name = parts[0];
3971
+ var direction = parts[1];
3972
+
3973
+ field = this.$el.find('thead [data-field="' + name + '"]');
3974
+ if (!field.length) {
3975
+ field = this.$el.find('thead th:contains("' + name + '")');
3976
+ }
3977
+
3978
+ if (field.length) {
3979
+ field.removeClass('sorting').addClass(direction == 'desc' ? 'sorting-desc' : 'sorting-asc');
3980
+ }
3981
+ }
3982
+ },
3983
+
3505
3984
  config : function() {
3506
3985
  return {
3507
3986
  page : 1,
@@ -3510,16 +3989,11 @@ Backbone.Tableling = Tableling = (function(Backbone, _, $){
3510
3989
  },
3511
3990
 
3512
3991
  sortConfig : function() {
3513
- if (!this.sort.length) {
3514
- return null;
3515
- }
3516
- return _.map(this.sort, function(item) {
3517
- return item.field + ' ' + item.direction;
3518
- });
3992
+ return this.sort.length ? this.sort : null;
3519
3993
  },
3520
3994
 
3521
3995
  fieldName : function(el) {
3522
- return el.data('field') || el.text().toLowerCase();
3996
+ return el.data('field') || el.text();
3523
3997
  }
3524
3998
  });
3525
3999
 
@@ -3545,12 +4019,25 @@ Backbone.Tableling = Tableling = (function(Backbone, _, $){
3545
4019
 
3546
4020
  addSize : function(size) {
3547
4021
  $('<option />').text(size).appendTo(this.ui.field);
4022
+ },
4023
+
4024
+ config : function() {
4025
+ var config = Tableling.FieldModule.prototype.config.call(this);
4026
+ config.page = 1;
4027
+ return config;
3548
4028
  }
3549
4029
  });
3550
4030
 
3551
4031
  Tableling.Plain.QuickSearchView = Tableling.Plain.Table.prototype.quickSearchView = Tableling.FieldModule.extend({
4032
+
3552
4033
  name : 'quickSearch',
3553
- template : _.template('<input type="text" name="quickSearch" placeholder="Quick search..." />')
4034
+ template : _.template('<input type="text" name="quickSearch" placeholder="Quick search..." />'),
4035
+
4036
+ config : function() {
4037
+ var config = Tableling.FieldModule.prototype.config.call(this);
4038
+ config.page = 1;
4039
+ return config;
4040
+ }
3554
4041
  });
3555
4042
 
3556
4043
  Tableling.Plain.InfoView = Tableling.Plain.Table.prototype.infoView = Tableling.Module.extend({
@@ -3572,7 +4059,7 @@ Backbone.Tableling = Tableling = (function(Backbone, _, $){
3572
4059
  },
3573
4060
 
3574
4061
  firstRecord : function(data) {
3575
- return data.length ? (data.page - 1) * data.pageSize + 1 : 0;
4062
+ return data.length ? ((data.page || 1) - 1) * data.pageSize + 1 : 0;
3576
4063
  },
3577
4064
 
3578
4065
  lastRecord : function(data) {
@@ -3606,10 +4093,10 @@ Backbone.Tableling = Tableling = (function(Backbone, _, $){
3606
4093
  this.ui.last.addClass('disabled');
3607
4094
  } else {
3608
4095
  this.data = data;
3609
- this.enable(this.ui.first, data.page > 1);
3610
- this.enable(this.ui.previous, data.page > 1);
3611
- this.enable(this.ui.next, data.page < this.numberOfPages(data));
3612
- this.enable(this.ui.last, data.page < this.numberOfPages(data));
4096
+ this.enable(this.ui.first, this.getPage(data) > 1);
4097
+ this.enable(this.ui.previous, this.getPage(data) > 1);
4098
+ this.enable(this.ui.next, this.getPage(data) < this.numberOfPages(data));
4099
+ this.enable(this.ui.last, this.getPage(data) < this.numberOfPages(data));
3613
4100
  }
3614
4101
  },
3615
4102
 
@@ -3629,11 +4116,11 @@ Backbone.Tableling = Tableling = (function(Backbone, _, $){
3629
4116
  },
3630
4117
 
3631
4118
  goToPreviousPage : function() {
3632
- this.goToPage(this.data.page - 1);
4119
+ this.goToPage(this.getPage(this.data) - 1);
3633
4120
  },
3634
4121
 
3635
4122
  goToNextPage : function() {
3636
- this.goToPage(this.data.page + 1);
4123
+ this.goToPage(this.getPage(this.data) + 1);
3637
4124
  },
3638
4125
 
3639
4126
  goToLastPage : function() {
@@ -3642,6 +4129,10 @@ Backbone.Tableling = Tableling = (function(Backbone, _, $){
3642
4129
 
3643
4130
  goToPage : function(n) {
3644
4131
  this.vent.trigger('table:update', { page : n });
4132
+ },
4133
+
4134
+ getPage : function(data) {
4135
+ return data.page || 1;
3645
4136
  }
3646
4137
  });
3647
4138