batman-rails 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Batman-Rails
2
2
 
3
- Easily setup and use batman.js (0.6.0) with rails 3.1
3
+ Easily setup and use batman.js (0.7.5) with rails 3.1
4
4
 
5
5
  ## Rails 3.1 setup
6
6
  This gem requires the use of rails 3.1, coffeescript and the new rails asset pipeline provided by sprockets.
@@ -70,4 +70,3 @@ Install the gem and generate scaffolding.
70
70
 
71
71
  You now have installed the batman-rails gem, setup a default directory structure for your frontend batman code.
72
72
  Then you generated the usual rails server side crud scaffolding and finally generated batman.js code to provide a simple single page crud app.
73
- You have one last step:
@@ -1,6 +1,6 @@
1
1
  module Batman
2
2
  module Rails
3
- VERSION = "0.0.1"
4
- BATMAN_VERSION = "0.7.0"
3
+ VERSION = "0.0.2"
4
+ BATMAN_VERSION = "0.7.5"
5
5
  end
6
6
  end
@@ -1,4 +1,5 @@
1
1
  require 'generators/batman/common'
2
+
2
3
  module Batman
3
4
  module Generators
4
5
  class InstallGenerator < ::Rails::Generators::Base
@@ -25,10 +26,49 @@ module Batman
25
26
 
26
27
  def inject_batman
27
28
  with_app_name do
28
- require_tree_pattern = /\/\/=(?!.*\/\/=).*?$/m
29
+ application_file = File.join(js_path, "application.js")
30
+ file_type = :javascript
31
+ pattern = /\/\/=(?!.*\/\/=).*?$/m
32
+
33
+ unless exists?(application_file)
34
+ application_file = "#{application_file}.coffee"
35
+ file_type = :coffeescript
36
+ pattern = /#=(?!.*#=).*?$/m
37
+ end
38
+
39
+ raise Thor::Error, "Couldn't find either application.js or application.js.coffee files for use with batman!" unless exists?(application_file)
40
+
41
+ inject_into_file application_file, :before=>pattern do
42
+ batman_requires(file_type)
43
+ end
44
+
45
+ inject_into_file application_file, :after=>pattern do
46
+ ready_function(file_type)
47
+ end
48
+ end
49
+ end
29
50
 
30
- inject_into_file "#{js_path}/application.js", :before=>require_tree_pattern do
51
+ private
52
+
53
+ def ready_function(file_type=:javascript)
54
+ if file_type == :coffeescript
55
+ <<-CODE
56
+ \n# Run the Batman app
57
+ $(document).ready ->
58
+ #{js_app_name}.run()
59
+ CODE
60
+ else
31
61
  <<-CODE
62
+ \n// Run the Batman app
63
+ $(document).ready(function(){
64
+ #{js_app_name}.run();
65
+ });
66
+ CODE
67
+ end
68
+ end
69
+
70
+ def batman_requires(file_type=:javascript)
71
+ code = <<-CODE
32
72
  \n// Batman.js and its adapters
33
73
  //= require batman/batman
34
74
  //= require batman/batman.jquery
@@ -41,17 +81,11 @@ module Batman
41
81
  //= require_tree ./helpers
42
82
  \n
43
83
  CODE
44
- end
84
+ file_type == :coffeescript ? code.gsub('//', '#') : code
85
+ end
45
86
 
46
- inject_into_file "#{js_path}/application.js", :after=>require_tree_pattern do
47
- <<-CODE
48
- \n// Run the Batman app
49
- $(document).ready(function(){
50
- #{js_app_name}.run();
51
- });
52
- CODE
53
- end
54
- end
87
+ def exists?(file)
88
+ File.exist?(File.join(destination_root, file))
55
89
  end
56
90
  end
57
91
  end
@@ -3,12 +3,11 @@ window.<%= js_app_name %> = class <%= js_app_name %> extends Batman.App
3
3
  # @root 'controller#all'
4
4
  # @route '/controller/:id', 'controller#show', resource: 'model', action: 'show'
5
5
 
6
- @run ->
7
- console.log "Running..."
8
- true
6
+ @on 'run', ->
7
+ console?.log "Running ...."
9
8
 
10
- @ready ->
11
- console.log "<%= js_app_name %> ready for use."
9
+ @on 'ready', ->
10
+ console?.log "<%= js_app_name %> ready for use."
12
11
 
13
12
  @flash: Batman()
14
13
  @flash.accessor
@@ -0,0 +1,9 @@
1
+ # This is a manifest file that'll be compiled into including all the files listed below.
2
+ # Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
3
+ # be included in the compiled file accessible from http://example.com/assets/application.js
4
+ # It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
5
+ # the compiled file.
6
+ #
7
+ #= require jquery
8
+ #= require jquery_ujs
9
+ #= require_tree .
@@ -2,75 +2,78 @@ require 'mocha'
2
2
  require 'test_helper'
3
3
  require 'generators/batman/install_generator'
4
4
 
5
- class InstallGeneratorTest < Rails::Generators::TestCase
6
- tests Batman::Generators::InstallGenerator
7
-
5
+ module InstallGeneratorTests
6
+
8
7
  def setup
9
8
  mkdir_p "#{destination_root}/app/assets/javascripts"
10
- cp fixture("application.js"), "#{destination_root}/app/assets/javascripts"
9
+ cp fixture(application_javascript_path), "#{destination_root}/app/assets/javascripts"
11
10
  Rails.application.class.stubs(:name).returns("Dummy::Application")
12
-
13
11
  super
14
12
  end
15
13
 
16
- test "Assert Batman application file is created" do
14
+ def teardown
15
+ Rails.application.class.unstub(:name)
16
+ end
17
+
18
+ def test_batman_application_file_is_created
17
19
  run_generator
18
-
20
+
19
21
  assert_file "#{javascripts_path}/dummy.js.coffee" do |app|
20
22
  assert_match /window\.Dummy = class Dummy extends Batman\.App/, app
21
- assert_match /@run ->/, app
23
+ assert_match /@on 'ready', ->/, app
24
+ assert_match /@on 'run', ->/, app
22
25
  end
23
26
  end
24
-
25
- test "Assert Batman application file is created for two word application name" do
27
+
28
+ def test_batman_application_file_is_created_for_two_word_application_name
26
29
  Rails.application.class.stubs(:name).returns("FooBar::Application")
27
30
  run_generator
28
-
31
+
29
32
  assert_file "#{javascripts_path}/foo_bar.js.coffee" do |app|
30
33
  assert_match /window\.FooBar = class FooBar extends Batman\.App/, app
31
34
  end
32
35
  end
33
-
34
- test "Assert application require is properly setup for two word application name" do
36
+
37
+ def test_application_require_is_properly_setup_for_two_word_application_name
35
38
  Rails.application.class.stubs(:name).returns("FooBar::Application")
36
39
  run_generator
37
-
38
- assert_file "#{javascripts_path}/application.js", /require foo_bar/
40
+
41
+ assert_file "#{javascripts_path}/#{application_javascript_path}", /require foo_bar/
39
42
  end
40
-
41
- test "Assert Batman directory structure is created" do
43
+
44
+ def test_batman_directory_structure_is_created
42
45
  run_generator
43
-
46
+
44
47
  %W{controllers models helpers}.each do |dir|
45
48
  assert_directory "#{javascripts_path}/#{dir}"
46
49
  assert_file "#{javascripts_path}/#{dir}/.gitkeep"
47
50
  end
48
51
  end
49
-
50
- test "Assert no gitkeep files are created when skipping git" do
52
+
53
+ def test_no_gitkeep_files_are_created_when_skipping_git
51
54
  run_generator [destination_root, "--skip-git"]
52
-
55
+
53
56
  %W{controllers models helpers}.each do |dir|
54
57
  assert_directory "#{javascripts_path}/#{dir}"
55
58
  assert_no_file "#{javascripts_path}/#{dir}/.gitkeep"
56
59
  end
57
60
  end
58
-
59
- test "Assert application.js require batman, batman.jquery, batman.rails and dummy.js" do
61
+
62
+ def test_applicationjs_require_batman_jquery_rails_and_dummy
60
63
  run_generator
61
-
62
- assert_file "#{javascripts_path}/application.js" do |app|
64
+
65
+ assert_file "#{javascripts_path}/#{application_javascript_path}" do |app|
63
66
  %W{batman batman.jquery batman.rails}.each do |require|
64
- assert_match /require batman\/#{require}/, app
67
+ assert_equal 1, app.scan(%r{require batman\/#{require}$}).length
65
68
  end
66
69
 
67
70
  assert_match /require dummy/, app
68
71
 
69
72
  %W{models controllers helpers}.each do |require|
70
- assert_match /require_tree \.\/#{require}/, app
73
+ assert_equal 1, app.scan(/require_tree \.\/#{require}/).length
71
74
  end
72
75
 
73
- assert_match /Dummy\.run\(\)/, app
76
+ assert_equal 1, app.scan(/Dummy\.run\(\)/).length
74
77
  end
75
78
  end
76
79
 
@@ -80,3 +83,23 @@ class InstallGeneratorTest < Rails::Generators::TestCase
80
83
  File.expand_path("fixtures/#{file}", File.dirname(__FILE__))
81
84
  end
82
85
  end
86
+
87
+ class InstallGeneratorWithApplicationJavascriptTest < Rails::Generators::TestCase
88
+ tests Batman::Generators::InstallGenerator
89
+
90
+ def application_javascript_path
91
+ "application.js"
92
+ end
93
+
94
+ include InstallGeneratorTests
95
+ end
96
+
97
+ class InstallGeneratorWithApplicationCoffeescriptTest < Rails::Generators::TestCase
98
+ tests Batman::Generators::InstallGenerator
99
+
100
+ def application_javascript_path
101
+ "application.js.coffee"
102
+ end
103
+
104
+ include InstallGeneratorTests
105
+ end
@@ -1,6 +1,11 @@
1
1
  (function() {
2
- var $addEventListener, $block, $extendsEnumerable, $findName, $functionName, $get, $hasAddEventListener, $isChildOf, $mixin, $passError, $preventDefault, $redirect, $removeEventListener, $removeNode, $setInnerHTML, $typeOf, $unbindNode, $unbindTree, $unmixin, Batman, BatmanObject, Binding, Validators, buntUndefined, camelize_rx, capitalize_rx, container, developer, div, filters, helpers, isEmptyDataObject, k, mixins, t, underscore_rx1, underscore_rx2, _Batman, _i, _len, _objectToString, _ref, _stateMachine_setState;
3
- var __slice = Array.prototype.slice, __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) {
2
+ var $addEventListener, $appendChild, $block, $clearImmediate, $extendsEnumerable, $findName, $functionName, $get, $hasAddEventListener, $isChildOf, $mixin, $passError, $preventDefault, $redirect, $removeEventListener, $removeNode, $setImmediate, $setInnerHTML, $trackBinding, $typeOf, $unbindNode, $unbindTree, $unmixin, Batman, BatmanObject, Binding, Validators, buntUndefined, camelize_rx, capitalize_rx, container, developer, div, filters, helpers, isEmptyDataObject, k, mixins, t, underscore_rx1, underscore_rx2, _Batman, _i, _implementImmediates, _len, _objectToString, _ref, _stateMachine_setState;
3
+ var __slice = Array.prototype.slice, __hasProp = Object.prototype.hasOwnProperty, __indexOf = Array.prototype.indexOf || function(item) {
4
+ for (var i = 0, l = this.length; i < l; i++) {
5
+ if (this[i] === item) return i;
6
+ }
7
+ return -1;
8
+ }, __extends = function(child, parent) {
4
9
  for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; }
5
10
  function ctor() { this.constructor = child; }
6
11
  ctor.prototype = parent.prototype;
@@ -133,6 +138,97 @@
133
138
  }
134
139
  return false;
135
140
  };
141
+ _implementImmediates = function() {
142
+ var $clearImmediate, $setImmediate, canUsePostMessage, count, functions, getHandle, handler, prefix, tasks;
143
+ canUsePostMessage = function() {
144
+ var async, oldMessage;
145
+ if (!container.postMessage) {
146
+ return false;
147
+ }
148
+ async = true;
149
+ oldMessage = container.onmessage;
150
+ container.onmessage = function() {
151
+ return async = false;
152
+ };
153
+ container.postMessage("", "*");
154
+ container.onmessage = oldMessage;
155
+ return async;
156
+ };
157
+ tasks = new Batman.SimpleHash;
158
+ count = 0;
159
+ getHandle = function() {
160
+ return "go" + (++count);
161
+ };
162
+ if (container.setImmediate) {
163
+ $setImmediate = container.setImmediate;
164
+ $clearImmediate = container.clearImmediate;
165
+ } else if (container.msSetImmediate) {
166
+ $setImmediate = msSetImmediate;
167
+ $clearImmediate = msClearImmediate;
168
+ } else if (canUsePostMessage()) {
169
+ prefix = 'com.batman.';
170
+ functions = new Batman.SimpleHash;
171
+ handler = function(e) {
172
+ var handle, _base;
173
+ if (!~e.data.search(prefix)) {
174
+ return;
175
+ }
176
+ handle = e.data.substring(prefix.length);
177
+ return typeof (_base = tasks.unset(handle)) === "function" ? _base() : void 0;
178
+ };
179
+ if (container.addEventListener) {
180
+ container.addEventListener('message', handler, false);
181
+ } else {
182
+ container.attachEvent('onmessage', handler);
183
+ }
184
+ $setImmediate = function(f) {
185
+ var handle;
186
+ tasks.set(handle = getHandle(), f);
187
+ container.postMessage(prefix + handle, "*");
188
+ return handle;
189
+ };
190
+ $clearImmediate = function(handle) {
191
+ return tasks.unset(handle);
192
+ };
193
+ } else if (typeof document !== 'undefined' && __indexOf.call(document.createElement("script"), "onreadystatechange") >= 0) {
194
+ $setImmediate = function(f) {
195
+ var handle, script;
196
+ handle = getHandle();
197
+ script = document.createElement("script");
198
+ script.onreadystatechange = function() {
199
+ var _base;
200
+ if (typeof (_base = tasks.get(handle)) === "function") {
201
+ _base();
202
+ }
203
+ script.onreadystatechange = null;
204
+ script.parentNode.removeChild(script);
205
+ return script = null;
206
+ };
207
+ document.documentElement.appendChild(script);
208
+ return handle;
209
+ };
210
+ $clearImmediate = function(handle) {
211
+ return tasks.unset(handle);
212
+ };
213
+ } else {
214
+ $setImmediate = function(f) {
215
+ return setTimeout(f, 0);
216
+ };
217
+ $clearImmediate = function(handle) {
218
+ return clearTimeout(handle);
219
+ };
220
+ }
221
+ Batman.setImmediate = $setImmediate;
222
+ return Batman.clearImmediate = $clearImmediate;
223
+ };
224
+ Batman.setImmediate = $setImmediate = function() {
225
+ _implementImmediates();
226
+ return Batman.setImmediate.apply(this, arguments);
227
+ };
228
+ Batman.clearImmediate = $clearImmediate = function() {
229
+ _implementImmediates();
230
+ return Batman.clearImmediate.apply(this, arguments);
231
+ };
136
232
  Batman.translate = function(x, values) {
137
233
  if (values == null) {
138
234
  values = {};
@@ -231,6 +327,7 @@
231
327
  }
232
328
  };
233
329
  Batman.developer = developer;
330
+ developer.assert((function() {}).bind, "Error! Batman needs Function.bind to work! Please shim it using something like es5-shim or augmentjs!");
234
331
  camelize_rx = /(?:^|_|\-)(.)/g;
235
332
  capitalize_rx = /(^|\s)([a-z])/g;
236
333
  underscore_rx1 = /([A-Z]+)([A-Z][a-z])/g;
@@ -351,7 +448,7 @@
351
448
  key = this.key;
352
449
  return this.base._batman.ancestors(function(ancestor) {
353
450
  var handlers;
354
- if (ancestor.isEventEmitter) {
451
+ if (ancestor.isEventEmitter && ancestor.hasEvent(key)) {
355
452
  handlers = ancestor.event(key).handlers;
356
453
  return handlers.forEach(iterator);
357
454
  }
@@ -401,13 +498,17 @@
401
498
  })();
402
499
  Batman.EventEmitter = {
403
500
  isEventEmitter: true,
501
+ hasEvent: function(key) {
502
+ var _ref, _ref2;
503
+ return (_ref = this._batman) != null ? typeof _ref.get === "function" ? (_ref2 = _ref.get('events')) != null ? _ref2.hasKey(key) : void 0 : void 0 : void 0;
504
+ },
404
505
  event: function(key) {
405
506
  var eventClass, events, existingEvent, existingEvents, newEvent, _base, _ref;
406
507
  Batman.initializeObject(this);
407
508
  eventClass = this.eventClass || Batman.Event;
408
509
  events = (_base = this._batman).events || (_base.events = new Batman.SimpleHash);
409
- if (existingEvent = events.get(key)) {
410
- return existingEvent;
510
+ if (events.hasKey(key)) {
511
+ return existingEvent = events.get(key);
411
512
  } else {
412
513
  existingEvents = this._batman.get('events');
413
514
  newEvent = events.set(key, new eventClass(this, key));
@@ -538,7 +639,7 @@
538
639
  if (this.base.isObservable) {
539
640
  return this.base._batman.ancestors(function(ancestor) {
540
641
  var handlers, property;
541
- if (ancestor.isObservable) {
642
+ if (ancestor.isObservable && ancestor.hasProperty(key)) {
542
643
  property = ancestor.property(key);
543
644
  handlers = property.event('change').handlers;
544
645
  return handlers.forEach(iterator);
@@ -549,9 +650,15 @@
549
650
  Property.prototype.pushSourceTracker = function() {
550
651
  return Batman.Property._sourceTrackerStack.push(new Batman.SimpleSet);
551
652
  };
653
+ Property.prototype.pushDummySourceTracker = function() {
654
+ return Batman.Property._sourceTrackerStack.push(null);
655
+ };
656
+ Property.prototype.popSourceTracker = function() {
657
+ return Batman.Property._sourceTrackerStack.pop();
658
+ };
552
659
  Property.prototype.updateSourcesFromTracker = function() {
553
660
  var handler, newSources;
554
- newSources = Batman.Property._sourceTrackerStack.pop();
661
+ newSources = this.popSourceTracker();
555
662
  handler = this.sourceChangeHandler();
556
663
  this._eachSourceChangeEvent(function(e) {
557
664
  return e.removeHandler(handler);
@@ -613,15 +720,19 @@
613
720
  };
614
721
  Property.prototype.setValue = function(val) {
615
722
  var result, _ref;
723
+ this.pushDummySourceTracker();
616
724
  this.cached = false;
617
725
  result = (_ref = this.accessor().set) != null ? _ref.call(this.base, this.key, val) : void 0;
618
726
  this.refresh();
727
+ this.popSourceTracker();
619
728
  return result;
620
729
  };
621
730
  Property.prototype.unsetValue = function() {
622
731
  var result, _ref;
732
+ this.pushDummySourceTracker();
623
733
  result = (_ref = this.accessor().unset) != null ? _ref.call(this.base, this.key) : void 0;
624
734
  this.refresh();
735
+ this.popSourceTracker();
625
736
  return result;
626
737
  };
627
738
  Property.prototype.forget = function(handler) {
@@ -640,6 +751,14 @@
640
751
  this.getValue();
641
752
  return this;
642
753
  };
754
+ Property.prototype.removeSourceHandlers = function() {
755
+ var handler;
756
+ handler = this.sourceChangeHandler();
757
+ this._eachSourceChangeEvent(function(e) {
758
+ return e.removeHandler(handler);
759
+ });
760
+ return this.forget();
761
+ };
643
762
  Property.prototype.fire = function() {
644
763
  var _ref;
645
764
  return (_ref = this.changeEvent()).fire.apply(_ref, arguments);
@@ -727,6 +846,10 @@
727
846
  })();
728
847
  Batman.Observable = {
729
848
  isObservable: true,
849
+ hasProperty: function(key) {
850
+ var _ref, _ref2;
851
+ return (_ref = this._batman) != null ? (_ref2 = _ref.properties) != null ? typeof _ref2.hasKey === "function" ? _ref2.hasKey(key) : void 0 : void 0 : void 0;
852
+ },
730
853
  property: function(key) {
731
854
  var properties, propertyClass, _base;
732
855
  Batman.initializeObject(this);
@@ -914,7 +1037,7 @@
914
1037
  for (key in this) {
915
1038
  if (!__hasProp.call(this, key)) continue;
916
1039
  value = this[key];
917
- if (key !== "_batman") {
1040
+ if (key !== "_batman" && key !== "hashKey" && key !== "_objectID") {
918
1041
  obj[key] = value.toJSON ? value.toJSON() : value;
919
1042
  }
920
1043
  }
@@ -1121,12 +1244,16 @@
1121
1244
  return val;
1122
1245
  };
1123
1246
  SimpleHash.prototype.unset = function(key) {
1124
- var index, obj, pair, pairs, value, _len, _ref;
1125
- if (pairs = this._storage[this.hashKeyFor(key)]) {
1247
+ var hashKey, index, obj, pair, pairs, value, _len, _ref;
1248
+ hashKey = this.hashKeyFor(key);
1249
+ if (pairs = this._storage[hashKey]) {
1126
1250
  for (index = 0, _len = pairs.length; index < _len; index++) {
1127
1251
  _ref = pairs[index], obj = _ref[0], value = _ref[1];
1128
1252
  if (this.equality(obj, key)) {
1129
1253
  pair = pairs.splice(index, 1);
1254
+ if (!pairs.length) {
1255
+ delete this._storage[hashKey];
1256
+ }
1130
1257
  this.length--;
1131
1258
  return pair[0][1];
1132
1259
  }
@@ -1425,8 +1552,8 @@
1425
1552
  __extends(SetObserver, Batman.Object);
1426
1553
  function SetObserver(base) {
1427
1554
  this.base = base;
1428
- this._itemObservers = new Batman.Hash;
1429
- this._setObservers = new Batman.Hash;
1555
+ this._itemObservers = new Batman.SimpleHash;
1556
+ this._setObservers = new Batman.SimpleHash;
1430
1557
  this._setObservers.set("itemsWereAdded", __bind(function() {
1431
1558
  return this.fire.apply(this, ['itemsWereAdded'].concat(__slice.call(arguments)));
1432
1559
  }, this));
@@ -1441,7 +1568,7 @@
1441
1568
  SetObserver.prototype._getOrSetObserverForItemAndKey = function(item, key) {
1442
1569
  return this._itemObservers.getOrSet(item, __bind(function() {
1443
1570
  var observersByKey;
1444
- observersByKey = new Batman.Hash;
1571
+ observersByKey = new Batman.SimpleHash;
1445
1572
  return observersByKey.getOrSet(key, __bind(function() {
1446
1573
  return this.observerForItemAndKey(item, key);
1447
1574
  }, this));
@@ -1679,7 +1806,7 @@
1679
1806
  this.base = base;
1680
1807
  this.key = key;
1681
1808
  SetIndex.__super__.constructor.call(this);
1682
- this._storage = new Batman.Hash;
1809
+ this._storage = new Batman.SimpleHash;
1683
1810
  if (this.base.isEventEmitter) {
1684
1811
  this._setObserver = new Batman.SetObserver(this.base);
1685
1812
  this._setObserver.observedItemKeys = [this.key];
@@ -1735,7 +1862,12 @@
1735
1862
  return this._removeItemFromKey(item, this._keyForItem(item));
1736
1863
  };
1737
1864
  SetIndex.prototype._removeItemFromKey = function(item, key) {
1738
- return this._resultSetForKey(key).remove(item);
1865
+ var results;
1866
+ results = this._resultSetForKey(key);
1867
+ results.remove(item);
1868
+ if (results.isEmpty()) {
1869
+ return this._storage.unset(key);
1870
+ }
1739
1871
  };
1740
1872
  SetIndex.prototype._resultSetForKey = function(key) {
1741
1873
  return this._storage.getOrSet(key, function() {
@@ -1765,8 +1897,8 @@
1765
1897
  UniqueSetIndex.prototype._removeItemFromKey = function(item, key) {
1766
1898
  var resultSet;
1767
1899
  resultSet = this._resultSetForKey(key);
1768
- resultSet.remove(item);
1769
- if (resultSet.length === 0) {
1900
+ UniqueSetIndex.__super__._removeItemFromKey.apply(this, arguments);
1901
+ if (resultSet.isEmpty()) {
1770
1902
  return this._uniqueIndex.unset(key);
1771
1903
  } else {
1772
1904
  return this._uniqueIndex.set(key, resultSet.toArray()[0]);
@@ -1915,9 +2047,9 @@
1915
2047
  }
1916
2048
  }
1917
2049
  Request.observeAll('url', function() {
1918
- return this._autosendTimeout = setTimeout((__bind(function() {
2050
+ return this._autosendTimeout = $setImmediate(__bind(function() {
1919
2051
  return this.send();
1920
- }, this)), 0);
2052
+ }, this));
1921
2053
  });
1922
2054
  Request.prototype.send = function() {
1923
2055
  return developer.error("Please source a dependency file for a request implementation");
@@ -1935,8 +2067,8 @@
1935
2067
  App.__super__.constructor.apply(this, arguments);
1936
2068
  }
1937
2069
  App.requirePath = '';
1938
- developer["do"](function() {
1939
- return App.require = function() {
2070
+ developer["do"](__bind(function() {
2071
+ App.require = function() {
1940
2072
  var base, name, names, path, _i, _len;
1941
2073
  path = arguments[0], names = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
1942
2074
  base = this.requirePath + path;
@@ -1961,22 +2093,23 @@
1961
2093
  }
1962
2094
  return this;
1963
2095
  };
1964
- });
1965
- App.controller = function() {
1966
- var names;
1967
- names = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
1968
- names = names.map(function(n) {
1969
- return n + '_controller';
1970
- });
1971
- return this.require.apply(this, ['controllers'].concat(__slice.call(names)));
1972
- };
1973
- App.model = function() {
1974
- return this.require.apply(this, ['models'].concat(__slice.call(arguments)));
1975
- };
1976
- App.view = function() {
1977
- return this.require.apply(this, ['views'].concat(__slice.call(arguments)));
1978
- };
2096
+ this.controller = function() {
2097
+ var names;
2098
+ names = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
2099
+ names = names.map(function(n) {
2100
+ return n + '_controller';
2101
+ });
2102
+ return this.require.apply(this, ['controllers'].concat(__slice.call(names)));
2103
+ };
2104
+ this.model = function() {
2105
+ return this.require.apply(this, ['models'].concat(__slice.call(arguments)));
2106
+ };
2107
+ return this.view = function() {
2108
+ return this.require.apply(this, ['views'].concat(__slice.call(arguments)));
2109
+ };
2110
+ }, App));
1979
2111
  App.layout = void 0;
2112
+ App.event('ready').oneShot = true;
1980
2113
  App.event('run').oneShot = true;
1981
2114
  App.run = function() {
1982
2115
  if (Batman.currentApp) {
@@ -1998,14 +2131,18 @@
1998
2131
  if (typeof this.dispatcher === 'undefined') {
1999
2132
  this.dispatcher || (this.dispatcher = new Batman.Dispatcher(this));
2000
2133
  }
2134
+ this.observe('layout', __bind(function(layout) {
2135
+ return layout != null ? layout.on('ready', __bind(function() {
2136
+ return this.fire('ready');
2137
+ }, this)) : void 0;
2138
+ }, this));
2001
2139
  if (typeof this.layout === 'undefined') {
2002
2140
  this.set('layout', new Batman.View({
2003
2141
  contexts: [this],
2004
2142
  node: document
2005
2143
  }));
2006
- this.get('layout').on('ready', __bind(function() {
2007
- return this.fire('ready');
2008
- }, this));
2144
+ } else if (typeof this.layout === 'string') {
2145
+ this.set('layout', new this[helpers.camelize(this.layout) + 'View']);
2009
2146
  }
2010
2147
  if (typeof this.historyManager === 'undefined' && this.dispatcher.routeMap) {
2011
2148
  this.on('run', __bind(function() {
@@ -2030,7 +2167,7 @@
2030
2167
  return this;
2031
2168
  };
2032
2169
  return App;
2033
- })();
2170
+ }).call(this);
2034
2171
  Batman.Route = (function() {
2035
2172
  var escapeRegExp, namedOrSplat, namedParam, queryParam, splatParam;
2036
2173
  __extends(Route, Batman.Object);
@@ -2181,7 +2318,7 @@
2181
2318
  }
2182
2319
  };
2183
2320
  Dispatcher.prototype.findUrl = function(params) {
2184
- var action, controller, key, matches, options, route, url, value, _ref, _ref2;
2321
+ var action, controller, key, matches, options, paramsCopy, queryString, regex, route, url, value, _ref, _ref2;
2185
2322
  _ref = this.routeMap;
2186
2323
  for (url in _ref) {
2187
2324
  route = _ref[url];
@@ -2192,21 +2329,43 @@
2192
2329
  } else {
2193
2330
  action = route.get('action');
2194
2331
  if (typeof action === 'function') {
2195
- continue;
2196
- }
2197
- _ref2 = action, controller = _ref2.controller, action = _ref2.action;
2198
- if (controller === params.controller && action === (params.action || 'index')) {
2199
2332
  matches = true;
2333
+ } else {
2334
+ _ref2 = action, controller = _ref2.controller, action = _ref2.action;
2335
+ if (controller === params.controller && action === (params.action || 'index')) {
2336
+ matches = true;
2337
+ }
2200
2338
  }
2201
2339
  }
2202
2340
  if (!matches) {
2203
2341
  continue;
2204
2342
  }
2343
+ $mixin(paramsCopy = {}, params);
2344
+ $unmixin(paramsCopy, {
2345
+ controller: null,
2346
+ action: null,
2347
+ resource: null,
2348
+ url: null,
2349
+ signature: null,
2350
+ target: null
2351
+ });
2205
2352
  for (key in params) {
2206
2353
  value = params[key];
2207
- url = url.replace(new RegExp('[:|\*]' + key), value);
2354
+ regex = new RegExp('[:|\*]' + key);
2355
+ if (!regex.test(url)) {
2356
+ continue;
2357
+ }
2358
+ url = url.replace(regex, value);
2359
+ paramsCopy[key] = null;
2360
+ delete paramsCopy[key];
2361
+ }
2362
+ queryString = '';
2363
+ for (key in paramsCopy) {
2364
+ value = paramsCopy[key];
2365
+ queryString += !queryString ? '?' : '&';
2366
+ queryString += key + '=' + value;
2208
2367
  }
2209
- return url;
2368
+ return url + queryString;
2210
2369
  }
2211
2370
  };
2212
2371
  Dispatcher.prototype.dispatch = function(url) {
@@ -2265,7 +2424,7 @@
2265
2424
  }
2266
2425
  this.first = true;
2267
2426
  Batman.currentApp.prevent('ready');
2268
- return setTimeout(this.parseHash, 0);
2427
+ return $setImmediate(this.parseHash);
2269
2428
  };
2270
2429
  HashHistory.prototype.stop = function() {
2271
2430
  if (this.interval) {
@@ -2347,6 +2506,9 @@
2347
2506
  }
2348
2507
  resource = helpers.pluralize(resource);
2349
2508
  controller = options.controller || resource;
2509
+ if (options.parentResource) {
2510
+ resource = "" + options.parentResource + "/:" + (helpers.singularize(options.parentResource)) + "Id/" + resource;
2511
+ }
2350
2512
  if (options.index !== false) {
2351
2513
  this.route(resource, "" + controller + "#index", {
2352
2514
  resource: controller,
@@ -2387,7 +2549,18 @@
2387
2549
  return app.route("" + resource + "/:id/" + url, "" + controller + "#" + (methodName || url));
2388
2550
  }
2389
2551
  }) : void 0;
2390
- }
2552
+ },
2553
+ resources: __bind(function(childResource, options, callback) {
2554
+ if (options == null) {
2555
+ options = {};
2556
+ }
2557
+ if (typeof options === 'function') {
2558
+ callback = options;
2559
+ options = {};
2560
+ }
2561
+ options.parentResource = resource;
2562
+ return this.resources(childResource, options, callback);
2563
+ }, this)
2391
2564
  };
2392
2565
  return callback.call(ops);
2393
2566
  }
@@ -2509,6 +2682,8 @@
2509
2682
  return;
2510
2683
  }
2511
2684
  if (!options.view) {
2685
+ options.contexts || (options.contexts = []);
2686
+ options.contexts.push(this);
2512
2687
  options.source || (options.source = helpers.underscore($functionName(this.constructor).replace('Controller', '')) + '/' + this._currentAction + '.html');
2513
2688
  options.view = new Batman.View(options);
2514
2689
  }
@@ -2516,10 +2691,9 @@
2516
2691
  if ((_ref = Batman.currentApp) != null) {
2517
2692
  _ref.prevent('ready');
2518
2693
  }
2519
- view.contexts.push(this);
2520
2694
  view.on('ready', function() {
2521
2695
  var _ref2, _ref3;
2522
- Batman.DOM.replace('main', view.get('node'));
2696
+ Batman.DOM.replace(options.into || 'main', view.get('node'));
2523
2697
  if ((_ref2 = Batman.currentApp) != null) {
2524
2698
  _ref2.allow('ready');
2525
2699
  }
@@ -2737,7 +2911,7 @@
2737
2911
  Model.accessor('id', {
2738
2912
  get: function() {
2739
2913
  var pk;
2740
- pk = this.constructor.get('primaryKey');
2914
+ pk = this.constructor.primaryKey;
2741
2915
  if (pk === 'id') {
2742
2916
  return this.id;
2743
2917
  } else {
@@ -2749,7 +2923,7 @@
2749
2923
  if (typeof v === "string" && !isNaN(intId = parseInt(v, 10))) {
2750
2924
  v = intId;
2751
2925
  }
2752
- pk = this.constructor.get('primaryKey');
2926
+ pk = this.constructor.primaryKey;
2753
2927
  if (pk === 'id') {
2754
2928
  return this.id = v;
2755
2929
  } else {
@@ -2856,6 +3030,13 @@
2856
3030
  }
2857
3031
  });
2858
3032
  }
3033
+ developer["do"](function() {
3034
+ if ((!decoders) || decoders.reduce((function(sum, x) {
3035
+ return sum + x;
3036
+ }), 0) <= 1) {
3037
+ return developer.warn("Warning: Model " + (this.get('storageKey')) + " has suspiciously few decoders!");
3038
+ }
3039
+ });
2859
3040
  return this.mixin(obj);
2860
3041
  };
2861
3042
  Model.actsAsStateMachine(true);
@@ -3379,6 +3560,7 @@
3379
3560
  RestStorage.prototype.serializeAsForm = true;
3380
3561
  function RestStorage() {
3381
3562
  RestStorage.__super__.constructor.apply(this, arguments);
3563
+ this.defaultOptions = $mixin({}, this.defaultOptions);
3382
3564
  this.recordJsonNamespace = helpers.singularize(this.modelKey);
3383
3565
  this.collectionJsonNamespace = helpers.pluralize(this.modelKey);
3384
3566
  }
@@ -3567,63 +3749,93 @@
3567
3749
  };
3568
3750
  return RestStorage;
3569
3751
  })();
3752
+ Batman.ViewSourceCache = (function() {
3753
+ __extends(ViewSourceCache, Batman.Object);
3754
+ function ViewSourceCache() {
3755
+ ViewSourceCache.__super__.constructor.apply(this, arguments);
3756
+ this.sources = {};
3757
+ this.requests = {};
3758
+ }
3759
+ ViewSourceCache.prototype.propertyClass = Batman.Property;
3760
+ ViewSourceCache.accessor({
3761
+ get: function(k) {
3762
+ var url;
3763
+ if (this.sources[k] != null) {
3764
+ return this.sources[k];
3765
+ }
3766
+ if (this.requests[k] == null) {
3767
+ this.requests = new Batman.Request({
3768
+ url: url = "" + Batman.View.prototype.prefix + "/" + k,
3769
+ type: 'html',
3770
+ success: __bind(function(response) {
3771
+ return this.set(k, response);
3772
+ }, this),
3773
+ error: function(response) {
3774
+ throw new Error("Could not load view from " + url);
3775
+ }
3776
+ });
3777
+ }
3778
+ },
3779
+ set: function(k, v) {
3780
+ return this.sources[k] = v;
3781
+ }
3782
+ });
3783
+ return ViewSourceCache;
3784
+ })();
3570
3785
  Batman.View = (function() {
3571
3786
  __extends(View, Batman.Object);
3572
3787
  function View(options) {
3573
- var context;
3788
+ var context, node;
3574
3789
  this.contexts = [];
3575
3790
  View.__super__.constructor.call(this, options);
3576
3791
  if (context = this.get('context')) {
3577
3792
  this.contexts.push(context);
3578
3793
  this.unset('context');
3579
3794
  }
3795
+ if (node = this.get('node')) {
3796
+ this.render(node);
3797
+ } else {
3798
+ this.observe('node', __bind(function(node) {
3799
+ return this.render(node);
3800
+ }, this));
3801
+ }
3580
3802
  }
3581
- View.viewSources = {};
3803
+ View.sourceCache = new Batman.ViewSourceCache();
3582
3804
  View.prototype.source = '';
3583
3805
  View.prototype.html = '';
3584
3806
  View.prototype.node = null;
3585
3807
  View.prototype.contentFor = null;
3586
3808
  View.prototype.event('ready').oneShot = true;
3587
3809
  View.prototype.prefix = 'views';
3588
- View.observeAll('source', function() {
3589
- return setTimeout((__bind(function() {
3590
- return this.reloadSource();
3591
- }, this)), 0);
3592
- });
3593
- View.prototype.reloadSource = function() {
3594
- var source, url;
3810
+ View.accessor('html', function() {
3811
+ var source;
3812
+ if (this.html && this.html.length > 0) {
3813
+ return this.html;
3814
+ }
3595
3815
  source = this.get('source');
3596
3816
  if (!source) {
3597
3817
  return;
3598
3818
  }
3599
- if (Batman.View.viewSources[source]) {
3600
- return this.set('html', Batman.View.viewSources[source]);
3601
- } else {
3602
- return new Batman.Request({
3603
- url: url = "" + this.prefix + "/" + this.source,
3604
- type: 'html',
3605
- success: __bind(function(response) {
3606
- Batman.View.viewSources[source] = response;
3607
- return this.set('html', response);
3608
- }, this),
3609
- error: function(response) {
3610
- throw new Error("Could not load view from " + url);
3819
+ return this.html = this.constructor.sourceCache.get(source);
3820
+ });
3821
+ View.accessor('node', {
3822
+ get: function() {
3823
+ var html;
3824
+ if (!this.node) {
3825
+ html = this.get('html');
3826
+ if (!(html && html.length > 0)) {
3827
+ return;
3611
3828
  }
3612
- });
3613
- }
3614
- };
3615
- View.observeAll('html', function(html) {
3616
- var node;
3617
- node = this.node || document.createElement('div');
3618
- $setInnerHTML(node, html);
3619
- if (this.node !== node) {
3620
- return this.set('node', node);
3829
+ this.node = document.createElement('div');
3830
+ $setInnerHTML(this.node, html);
3831
+ }
3832
+ return this.node;
3833
+ },
3834
+ set: function(_, node) {
3835
+ return this.node = node;
3621
3836
  }
3622
3837
  });
3623
- View.observeAll('node', function(node) {
3624
- if (!node) {
3625
- return;
3626
- }
3838
+ View.prototype.render = function(node) {
3627
3839
  this.event('ready').resetOneShot();
3628
3840
  if (this._renderer) {
3629
3841
  this._renderer.forgetAll();
@@ -3637,7 +3849,7 @@
3637
3849
  }
3638
3850
  if (this.contentFor && node) {
3639
3851
  $setInnerHTML(this.contentFor, '');
3640
- return this.contentFor.appendChild(node);
3852
+ return $appendChild(this.contentFor, node);
3641
3853
  } else if (yieldTo) {
3642
3854
  if (contents = Batman.DOM._yieldContents[yieldTo]) {
3643
3855
  return contents.push(node);
@@ -3650,12 +3862,13 @@
3650
3862
  return this.fire('ready', node);
3651
3863
  }, this));
3652
3864
  }
3653
- });
3865
+ };
3654
3866
  return View;
3655
3867
  })();
3656
3868
  Batman.Renderer = (function() {
3657
3869
  var bindingRegexp, k, sortBindings, _i, _len, _ref;
3658
3870
  __extends(Renderer, Batman.Object);
3871
+ Renderer.prototype.deferEvery = 50;
3659
3872
  function Renderer(node, callback, contexts) {
3660
3873
  var _ref;
3661
3874
  this.node = node;
@@ -3669,7 +3882,7 @@
3669
3882
  this.on('parsed', callback);
3670
3883
  }
3671
3884
  this.context = contexts instanceof Batman.RenderContext ? contexts : (_ref = Batman.RenderContext).start.apply(_ref, contexts);
3672
- this.timeout = setTimeout(this.start, 0);
3885
+ this.timeout = $setImmediate(this.start);
3673
3886
  }
3674
3887
  Renderer.prototype.start = function() {
3675
3888
  this.startTime = new Date;
@@ -3714,9 +3927,9 @@
3714
3927
  };
3715
3928
  Renderer.prototype.parseNode = function(node) {
3716
3929
  var attr, bindings, key, name, nextNode, readerArgs, result, skipChildren, varIndex, _base, _base2, _j, _len2, _name, _name2, _ref2;
3717
- if (new Date - this.startTime > 50) {
3930
+ if (this.deferEvery && (new Date - this.startTime) > this.deferEvery) {
3718
3931
  this.resumeNode = node;
3719
- this.timeout = setTimeout(this.resume, 0);
3932
+ this.timeout = $setImmediate(this.resume);
3720
3933
  return;
3721
3934
  }
3722
3935
  if (node.getAttribute && node.attributes) {
@@ -3871,6 +4084,19 @@
3871
4084
  }
3872
4085
  this;
3873
4086
  }
4087
+ Binding.prototype.destroy = function() {
4088
+ var k, _results;
4089
+ this.forget();
4090
+ this._batman.properties.forEach(function(key, property) {
4091
+ return property.removeSourceHandlers();
4092
+ });
4093
+ _results = [];
4094
+ for (k in this) {
4095
+ if (!__hasProp.call(this, k)) continue;
4096
+ _results.push(delete this[k]);
4097
+ }
4098
+ return _results;
4099
+ };
3874
4100
  Binding.prototype.parseFilter = function() {
3875
4101
  var args, filter, filterName, filterString, filters, key, keyPath, orig, split;
3876
4102
  this.filterFunctions = [];
@@ -4159,21 +4385,21 @@
4159
4385
  return true;
4160
4386
  },
4161
4387
  yield: function(node, key) {
4162
- setTimeout((function() {
4388
+ $setImmediate(function() {
4163
4389
  return Batman.DOM.yield(key, node);
4164
- }), 0);
4390
+ });
4165
4391
  return true;
4166
4392
  },
4167
4393
  contentfor: function(node, key) {
4168
- setTimeout((function() {
4394
+ $setImmediate(function() {
4169
4395
  return Batman.DOM.contentFor(key, node);
4170
- }), 0);
4396
+ });
4171
4397
  return true;
4172
4398
  },
4173
4399
  replace: function(node, key) {
4174
- setTimeout((function() {
4400
+ $setImmediate(function() {
4175
4401
  return Batman.DOM.replace(key, node);
4176
- }), 0);
4402
+ });
4177
4403
  return true;
4178
4404
  }
4179
4405
  },
@@ -4315,75 +4541,7 @@
4315
4541
  },
4316
4542
  binders: {
4317
4543
  select: function(node, key, context, renderer, only) {
4318
- var boundValue, container, updateOptionBindings, updateSelectBinding, _ref;
4319
- _ref = context.findKey(key), boundValue = _ref[0], container = _ref[1];
4320
- updateSelectBinding = __bind(function() {
4321
- var c, selections;
4322
- selections = node.multiple ? (function() {
4323
- var _i, _len, _ref2, _results;
4324
- _ref2 = node.children;
4325
- _results = [];
4326
- for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
4327
- c = _ref2[_i];
4328
- if (c.selected) {
4329
- _results.push(c.value);
4330
- }
4331
- }
4332
- return _results;
4333
- })() : node.value;
4334
- if (selections.length === 1) {
4335
- selections = selections[0];
4336
- }
4337
- return container.set(key, selections);
4338
- }, this);
4339
- updateOptionBindings = __bind(function() {
4340
- var child, data, subBoundValue, subContainer, subContext, subKey, _i, _len, _ref2, _ref3, _results;
4341
- _ref2 = node.children;
4342
- _results = [];
4343
- for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
4344
- child = _ref2[_i];
4345
- _results.push((data = Batman.data(child, 'selected')) ? (subContext = data.context) && (subKey = data.key) ? ((_ref3 = subContext.findKey(subKey), subBoundValue = _ref3[0], subContainer = _ref3[1], _ref3), child.selected !== subBoundValue ? subContainer.set(subKey, child.selected) : void 0) : void 0 : void 0);
4346
- }
4347
- return _results;
4348
- }, this);
4349
- renderer.on('rendered', function() {
4350
- var dataChange, nodeChange;
4351
- dataChange = function(newValue) {
4352
- var child, match, matches, value, valueToChild, _i, _j, _k, _len, _len2, _len3, _ref2, _ref3;
4353
- if (newValue instanceof Array) {
4354
- valueToChild = {};
4355
- _ref2 = node.children;
4356
- for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
4357
- child = _ref2[_i];
4358
- child.selected = false;
4359
- matches = valueToChild[child.value];
4360
- if (matches) {
4361
- matches.push(child);
4362
- } else {
4363
- matches = [child];
4364
- }
4365
- valueToChild[child.value] = matches;
4366
- }
4367
- for (_j = 0, _len2 = newValue.length; _j < _len2; _j++) {
4368
- value = newValue[_j];
4369
- _ref3 = valueToChild[value];
4370
- for (_k = 0, _len3 = _ref3.length; _k < _len3; _k++) {
4371
- match = _ref3[_k];
4372
- match.selected = true;
4373
- }
4374
- }
4375
- } else {
4376
- node.value = newValue;
4377
- }
4378
- return updateOptionBindings();
4379
- };
4380
- nodeChange = function() {
4381
- updateSelectBinding();
4382
- return updateOptionBindings();
4383
- };
4384
- Batman.data(node, 'updateBinding', updateSelectBinding);
4385
- return context.bind(node, key, dataChange, nodeChange, only);
4386
- });
4544
+ new Batman.DOM.Select(node, key, context, renderer, only);
4387
4545
  return true;
4388
4546
  },
4389
4547
  style: function(node, attr, key, context, renderer, only) {
@@ -4408,14 +4566,18 @@
4408
4566
  return true;
4409
4567
  },
4410
4568
  file: function(node, key, context, renderer, only) {
4411
- context.bind(node, key, function() {
4412
- return developer.warn("Can't write to file inputs! Tried to on key " + key + ".");
4413
- }, function(node, subContext) {
4414
- var actualObject, adapter, _i, _len, _ref;
4415
- if (subContext instanceof Batman.RenderContext.ContextProxy) {
4416
- actualObject = subContext.get('proxiedObject');
4569
+ context.bind(node, key, (function() {}), function(node, subContext) {
4570
+ var actualObject, adapter, keyContext, segments, _i, _len, _ref;
4571
+ segments = key.split('.');
4572
+ if (segments.length > 1) {
4573
+ keyContext = subContext.get(segments.slice(0, -1).join('.'));
4417
4574
  } else {
4418
- actualObject = subContext;
4575
+ keyContext = subContext;
4576
+ }
4577
+ if (keyContext instanceof Batman.RenderContext.ContextProxy) {
4578
+ actualObject = keyContext.get('proxiedObject');
4579
+ } else {
4580
+ actualObject = keyContext;
4419
4581
  }
4420
4582
  if (actualObject.hasStorage && actualObject.hasStorage()) {
4421
4583
  _ref = actualObject._batman.get('storage');
@@ -4519,13 +4681,15 @@
4519
4681
  Batman.DOM._yields[name] = node;
4520
4682
  if (contents = Batman.DOM._yieldContents[name]) {
4521
4683
  if (_replaceContent) {
4522
- $setInnerHTML(node, '');
4684
+ $setInnerHTML(node, '', true);
4523
4685
  }
4524
4686
  for (_i = 0, _len = contents.length; _i < _len; _i++) {
4525
4687
  content = contents[_i];
4526
4688
  if (!Batman.data(content, 'yielded')) {
4527
- content = $isChildOf(node, content) ? content.cloneNode(true) : content;
4528
- node.appendChild(content);
4689
+ if ($isChildOf(node, content)) {
4690
+ content = content.cloneNode(true);
4691
+ }
4692
+ $appendChild(node, content, true);
4529
4693
  Batman.data(content, 'yielded', true);
4530
4694
  }
4531
4695
  }
@@ -4548,8 +4712,21 @@
4548
4712
  replace: function(name, node) {
4549
4713
  return Batman.DOM.contentFor(name, node, true);
4550
4714
  },
4715
+ trackBinding: $trackBinding = function(binding, node) {
4716
+ var bindings;
4717
+ if (bindings = Batman.data(node, 'bindings')) {
4718
+ return bindings.add(binding);
4719
+ } else {
4720
+ return Batman.data(node, 'bindings', new Batman.SimpleSet(binding));
4721
+ }
4722
+ },
4551
4723
  unbindNode: $unbindNode = function(node) {
4552
- var eventListeners, eventName, listeners;
4724
+ var bindings, eventListeners, eventName, listeners;
4725
+ if (bindings = Batman.data(node, 'bindings')) {
4726
+ bindings.forEach(function(binding) {
4727
+ return binding.destroy();
4728
+ });
4729
+ }
4553
4730
  if (listeners = Batman.data(node, 'listeners')) {
4554
4731
  for (eventName in listeners) {
4555
4732
  eventListeners = listeners[eventName];
@@ -4579,7 +4756,16 @@
4579
4756
  }
4580
4757
  return _results;
4581
4758
  },
4582
- setInnerHTML: $setInnerHTML = function(node, html) {
4759
+ setInnerHTML: $setInnerHTML = function() {
4760
+ var args, child, hide, html, node, _i, _len, _ref;
4761
+ node = arguments[0], html = arguments[1], args = 3 <= arguments.length ? __slice.call(arguments, 2) : [];
4762
+ _ref = node.childNodes;
4763
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
4764
+ child = _ref[_i];
4765
+ if (hide = Batman.data(child, 'hide')) {
4766
+ hide.apply(child, args);
4767
+ }
4768
+ }
4583
4769
  $unbindTree(node, false);
4584
4770
  return node != null ? node.innerHTML = html : void 0;
4585
4771
  },
@@ -4588,6 +4774,14 @@
4588
4774
  $unbindTree(node);
4589
4775
  return node != null ? (_ref = node.parentNode) != null ? _ref.removeChild(node) : void 0 : void 0;
4590
4776
  },
4777
+ appendChild: $appendChild = function() {
4778
+ var args, child, parent, _ref;
4779
+ parent = arguments[0], child = arguments[1], args = 3 <= arguments.length ? __slice.call(arguments, 2) : [];
4780
+ if ((_ref = Batman.data(child, 'show')) != null) {
4781
+ _ref.apply(child, args);
4782
+ }
4783
+ return parent.appendChild(child);
4784
+ },
4591
4785
  valueForNode: function(node, value) {
4592
4786
  var isSetting;
4593
4787
  if (value == null) {
@@ -4653,26 +4847,131 @@
4653
4847
  },
4654
4848
  hasAddEventListener: $hasAddEventListener = !!(typeof window !== "undefined" && window !== null ? window.addEventListener : void 0)
4655
4849
  };
4850
+ Batman.DOM.AbstractBinding = (function() {
4851
+ function AbstractBinding(node) {
4852
+ Batman.DOM.trackBinding(this, node);
4853
+ }
4854
+ AbstractBinding.prototype.destroy = function() {};
4855
+ return AbstractBinding;
4856
+ })();
4857
+ Batman.DOM.Select = (function() {
4858
+ __extends(Select, Batman.DOM.AbstractBinding);
4859
+ function Select(node, key, context, renderer, only) {
4860
+ this.node = node;
4861
+ this.key = key;
4862
+ this.context = context;
4863
+ this.renderer = renderer;
4864
+ this.only = only;
4865
+ this.updateOptionBindings = __bind(this.updateOptionBindings, this);
4866
+ this.updateSelectBinding = __bind(this.updateSelectBinding, this);
4867
+ this.nodeChange = __bind(this.nodeChange, this);
4868
+ this.dataChange = __bind(this.dataChange, this);
4869
+ Select.__super__.constructor.apply(this, arguments);
4870
+ this.container = context.findKey(key)[1];
4871
+ renderer.on('rendered', __bind(function() {
4872
+ Batman.data(node, 'updateBinding', this.updateSelectBinding);
4873
+ return context.bind(node, key, this.dataChange, this.nodeChange, only);
4874
+ }, this));
4875
+ }
4876
+ Select.prototype.dataChange = function(newValue) {
4877
+ var child, match, matches, value, valueToChild, _i, _j, _k, _len, _len2, _len3, _ref, _ref2;
4878
+ if (newValue instanceof Array) {
4879
+ valueToChild = {};
4880
+ _ref = this.node.children;
4881
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
4882
+ child = _ref[_i];
4883
+ child.selected = false;
4884
+ matches = valueToChild[child.value];
4885
+ if (matches) {
4886
+ matches.push(child);
4887
+ } else {
4888
+ matches = [child];
4889
+ }
4890
+ valueToChild[child.value] = matches;
4891
+ }
4892
+ for (_j = 0, _len2 = newValue.length; _j < _len2; _j++) {
4893
+ value = newValue[_j];
4894
+ _ref2 = valueToChild[value];
4895
+ for (_k = 0, _len3 = _ref2.length; _k < _len3; _k++) {
4896
+ match = _ref2[_k];
4897
+ match.selected = true;
4898
+ }
4899
+ }
4900
+ } else {
4901
+ this.node.value = newValue;
4902
+ }
4903
+ return this.updateOptionBindings();
4904
+ };
4905
+ Select.prototype.nodeChange = function() {
4906
+ this.updateSelectBinding();
4907
+ return this.updateOptionBindings();
4908
+ };
4909
+ Select.prototype.updateSelectBinding = function() {
4910
+ var c, selections;
4911
+ selections = this.node.multiple ? (function() {
4912
+ var _i, _len, _ref, _results;
4913
+ _ref = this.node.children;
4914
+ _results = [];
4915
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
4916
+ c = _ref[_i];
4917
+ if (c.selected) {
4918
+ _results.push(c.value);
4919
+ }
4920
+ }
4921
+ return _results;
4922
+ }).call(this) : this.node.value;
4923
+ if (selections.length === 1) {
4924
+ selections = selections[0];
4925
+ }
4926
+ return this.container.set(this.key, selections);
4927
+ };
4928
+ Select.prototype.updateOptionBindings = function() {
4929
+ var child, data, subBoundValue, subContainer, subContext, subKey, _i, _len, _ref, _ref2, _results;
4930
+ _ref = this.node.children;
4931
+ _results = [];
4932
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
4933
+ child = _ref[_i];
4934
+ _results.push((data = Batman.data(child, 'selected')) ? (subContext = data.context) && (subKey = data.key) ? ((_ref2 = subContext.findKey(subKey), subBoundValue = _ref2[0], subContainer = _ref2[1], _ref2), child.selected !== subBoundValue ? subContainer.set(subKey, child.selected) : void 0) : void 0 : void 0);
4935
+ }
4936
+ return _results;
4937
+ };
4938
+ return Select;
4939
+ })();
4656
4940
  Batman.DOM.Style = (function() {
4941
+ __extends(Style, Batman.DOM.AbstractBinding);
4657
4942
  function Style(node, key, context) {
4658
4943
  this.node = node;
4659
4944
  this.key = key;
4660
4945
  this.context = context;
4946
+ this.unbindCurrentHash = __bind(this.unbindCurrentHash, this);
4661
4947
  this.reapplyOldStyles = __bind(this.reapplyOldStyles, this);
4662
4948
  this.setStyle = __bind(this.setStyle, this);
4663
4949
  this.bindSingleAttribute = __bind(this.bindSingleAttribute, this);
4664
4950
  this.onItemsRemoved = __bind(this.onItemsRemoved, this);
4665
4951
  this.onItemsAdded = __bind(this.onItemsAdded, this);
4666
4952
  this.dataChange = __bind(this.dataChange, this);
4953
+ this.destroy = __bind(this.destroy, this);
4954
+ Style.__super__.constructor.apply(this, arguments);
4667
4955
  this.oldStyles = {};
4668
4956
  context.bind(node, key, this.dataChange, function() {});
4669
4957
  }
4958
+ Style.prototype.destroy = function() {
4959
+ var k, _results;
4960
+ this.unbindCurrentHash();
4961
+ _results = [];
4962
+ for (k in this) {
4963
+ if (!__hasProp.call(this, k)) continue;
4964
+ _results.push(delete this[k]);
4965
+ }
4966
+ return _results;
4967
+ };
4670
4968
  Style.prototype.dataChange = function(value) {
4671
4969
  var cssName, cssValue, key, keyValue, keypathContext, keypathValue, style, _i, _len, _ref, _ref2, _ref3, _results;
4672
4970
  if (!value) {
4673
4971
  this.reapplyOldStyles();
4674
4972
  return;
4675
4973
  }
4974
+ this.unbindCurrentHash();
4676
4975
  if (typeof value === 'string' && (this.boundValueType = 'string')) {
4677
4976
  this.reapplyOldStyles();
4678
4977
  _ref = value.split(';');
@@ -4684,10 +4983,6 @@
4684
4983
  return;
4685
4984
  }
4686
4985
  if (value instanceof Batman.Hash && (this.boundValueType = 'batman.hash')) {
4687
- if (this.styleHash) {
4688
- this.styleHash.event('itemsWereRemoved').removeHandler(this.onItemsRemoved);
4689
- this.styleHash.event('itemsWereAdded').removeHandler(this.onItemsAdded);
4690
- }
4691
4986
  this.styleHash = value;
4692
4987
  value.on('itemsWereAdded', this.onItemsAdded);
4693
4988
  value.on('itemsWereRemoved', this.onItemsRemoved);
@@ -4738,11 +5033,19 @@
4738
5033
  }
4739
5034
  return _results;
4740
5035
  };
5036
+ Style.prototype.unbindCurrentHash = function() {
5037
+ if (this.styleHash) {
5038
+ this.styleHash.event('itemsWereRemoved').removeHandler(this.onItemsRemoved);
5039
+ return this.styleHash.event('itemsWereAdded').removeHandler(this.onItemsAdded);
5040
+ }
5041
+ };
4741
5042
  return Style;
4742
5043
  })();
4743
5044
  Batman.DOM.Iterator = (function() {
4744
- Iterator.prototype.currentAddNumber = 0;
4745
- Iterator.prototype.queuedAddNumber = 0;
5045
+ __extends(Iterator, Batman.DOM.AbstractBinding);
5046
+ Iterator.prototype.deferEvery = 50;
5047
+ Iterator.prototype.currentActionNumber = 0;
5048
+ Iterator.prototype.queuedActionNumber = 0;
4746
5049
  function Iterator(sourceNode, iteratorName, key, context, parentRenderer) {
4747
5050
  this.iteratorName = iteratorName;
4748
5051
  this.key = key;
@@ -4751,24 +5054,35 @@
4751
5054
  this.arrayChanged = __bind(this.arrayChanged, this);
4752
5055
  this.collectionChange = __bind(this.collectionChange, this);
4753
5056
  this.nodeMap = new Batman.SimpleHash;
5057
+ this.actionMap = new Batman.SimpleHash;
4754
5058
  this.rendererMap = new Batman.SimpleHash;
5059
+ this.actions = [];
4755
5060
  this.prototypeNode = sourceNode.cloneNode(true);
4756
5061
  this.prototypeNode.removeAttribute("data-foreach-" + iteratorName);
4757
5062
  this.parentNode = sourceNode.parentNode;
4758
5063
  this.siblingNode = sourceNode.nextSibling;
4759
- this.parentRenderer.on('parsed', function() {
5064
+ this.parentRenderer.on('parsed', __bind(function() {
5065
+ this.prototypeNode[Batman.expando] = sourceNode[Batman.expando];
5066
+ delete sourceNode[Batman.expando];
4760
5067
  return $removeNode(sourceNode);
4761
- });
4762
- this.addFunctions = [];
5068
+ }, this));
5069
+ this.parentRenderer.prevent('rendered');
5070
+ Iterator.__super__.constructor.call(this, this.parentNode);
4763
5071
  this.fragment = document.createDocumentFragment();
4764
5072
  context.bind(sourceNode, key, this.collectionChange, function() {});
4765
5073
  }
4766
- Iterator.prototype.collectionChange = function(newCollection) {
4767
- var key, value, _ref, _results;
5074
+ Iterator.prototype.destroy = function() {
5075
+ var k;
5076
+ $unbindNode(this.prototypeNode);
5077
+ this.unbindCollection();
5078
+ for (k in this) {
5079
+ if (!__hasProp.call(this, k)) continue;
5080
+ delete this[k];
5081
+ }
5082
+ return this.destroyed = true;
5083
+ };
5084
+ Iterator.prototype.unbindCollection = function() {
4768
5085
  if (this.collection) {
4769
- if (newCollection === this.collection) {
4770
- return;
4771
- }
4772
5086
  this.nodeMap.forEach(function(item, node) {
4773
5087
  return $removeNode(node);
4774
5088
  });
@@ -4777,13 +5091,19 @@
4777
5091
  return renderer.stop();
4778
5092
  });
4779
5093
  this.rendererMap.clear();
4780
- if (this.collection.isObservble && this.collection.toArray) {
4781
- this.collection.forget(this.arrayChanged);
5094
+ if (this.collection.isObservable && this.collection.toArray) {
5095
+ return this.collection.forget('toArray', this.arrayChanged);
4782
5096
  } else if (this.collection.isEventEmitter) {
4783
- this.collection.event('itemsWereAdded').removeHandler(this.currentAddNumber);
4784
- this.collection.event('itemsWereRemoved').removeHandler(this.currentRemovedHandler);
5097
+ this.collection.event('itemsWereAdded').removeHandler(this.currentAddedHandler);
5098
+ return this.collection.event('itemsWereRemoved').removeHandler(this.currentRemovedHandler);
4785
5099
  }
4786
5100
  }
5101
+ };
5102
+ Iterator.prototype.collectionChange = function(newCollection) {
5103
+ var key, value, _ref;
5104
+ if (newCollection !== this.collection) {
5105
+ this.unbindCollection();
5106
+ }
4787
5107
  this.collection = newCollection;
4788
5108
  if (this.collection) {
4789
5109
  if (this.collection.isObservable && this.collection.toArray) {
@@ -4796,8 +5116,7 @@
4796
5116
  for (i = 0, _len = items.length; i < _len; i++) {
4797
5117
  item = items[i];
4798
5118
  _results.push(this.addItem(item, {
4799
- fragment: true,
4800
- addNumber: this.currentAddFunction + i
5119
+ fragment: true
4801
5120
  }));
4802
5121
  }
4803
5122
  return _results;
@@ -4814,24 +5133,49 @@
4814
5133
  }, this));
4815
5134
  }
4816
5135
  if (this.collection.toArray) {
4817
- return this.arrayChanged();
5136
+ this.arrayChanged();
4818
5137
  } else if (this.collection.forEach) {
4819
- return this.collection.forEach(__bind(function(item) {
5138
+ this.collection.forEach(__bind(function(item) {
4820
5139
  return this.addItem(item);
4821
5140
  }, this));
4822
5141
  } else {
4823
5142
  _ref = this.collection;
4824
- _results = [];
4825
5143
  for (key in _ref) {
4826
5144
  if (!__hasProp.call(_ref, key)) continue;
4827
5145
  value = _ref[key];
4828
- _results.push(this.addItem(key));
5146
+ this.addItem(key);
4829
5147
  }
4830
- return _results;
4831
5148
  }
4832
5149
  } else {
4833
- return developer.warn("Warning! data-foreach-" + this.iteratorName + " called with an undefined binding. Key was: " + this.key + ".");
5150
+ developer.warn("Warning! data-foreach-" + this.iteratorName + " called with an undefined binding. Key was: " + this.key + ".");
5151
+ }
5152
+ return this.processActionQueue();
5153
+ };
5154
+ Iterator.prototype.arrayChanged = function() {
5155
+ var existingNode, item, newItemsInOrder, trackingNodeMap, _i, _len;
5156
+ newItemsInOrder = this.collection.toArray();
5157
+ trackingNodeMap = new Batman.SimpleHash;
5158
+ for (_i = 0, _len = newItemsInOrder.length; _i < _len; _i++) {
5159
+ item = newItemsInOrder[_i];
5160
+ existingNode = this.nodeMap.get(item);
5161
+ trackingNodeMap.set(item, true);
5162
+ if (existingNode) {
5163
+ this.insertItem(item, existingNode, {
5164
+ fragment: false,
5165
+ actionNumber: this.queuedActionNumber++,
5166
+ sync: true
5167
+ });
5168
+ } else {
5169
+ this.addItem(item, {
5170
+ fragment: false
5171
+ });
5172
+ }
4834
5173
  }
5174
+ return this.nodeMap.forEach(__bind(function(item, node) {
5175
+ if (!trackingNodeMap.hasKey(item)) {
5176
+ return this.removeItem(item);
5177
+ }
5178
+ }, this));
4835
5179
  };
4836
5180
  Iterator.prototype.addItem = function(item, options) {
4837
5181
  var childRenderer, finish, self;
@@ -4840,26 +5184,37 @@
4840
5184
  fragment: true
4841
5185
  };
4842
5186
  }
4843
- options.addNumber = this.queuedAddNumber++;
4844
5187
  this.parentRenderer.prevent('rendered');
4845
- finish = __bind(function() {
4846
- this.parentRenderer.allow('rendered');
4847
- return this.parentRenderer.fire('rendered');
4848
- }, this);
4849
5188
  self = this;
5189
+ options.actionNumber = this.queuedActionNumber++;
4850
5190
  childRenderer = new Batman.Renderer(this._nodeForItem(item), (function() {
4851
5191
  return self.insertItem(item, this.node, options);
4852
5192
  }), this.context.descend(item, this.iteratorName));
4853
5193
  this.rendererMap.set(item, childRenderer);
5194
+ finish = __bind(function() {
5195
+ if (this.destroyed) {
5196
+ return;
5197
+ }
5198
+ this.parentRenderer.allow('rendered');
5199
+ return this.parentRenderer.fire('rendered');
5200
+ }, this);
4854
5201
  childRenderer.on('rendered', finish);
4855
- return childRenderer.on('stopped', __bind(function() {
4856
- this.addFunctions[options.addNumber] = function() {};
4857
- this._processAddQueue();
4858
- return finish();
5202
+ childRenderer.on('stopped', __bind(function() {
5203
+ if (this.destroyed) {
5204
+ return;
5205
+ }
5206
+ this.actions[options.actionNumber] = function() {};
5207
+ finish();
5208
+ return this.processActionQueue();
4859
5209
  }, this));
5210
+ return item;
4860
5211
  };
4861
5212
  Iterator.prototype.removeItem = function(item) {
4862
5213
  var hideFunction, oldNode;
5214
+ if (this.destroyed) {
5215
+ return;
5216
+ }
5217
+ this._removeOldAction(item);
4863
5218
  oldNode = this.nodeMap.unset(item);
4864
5219
  if (oldNode) {
4865
5220
  if (hideFunction = Batman.data(oldNode, 'hide')) {
@@ -4869,57 +5224,43 @@
4869
5224
  }
4870
5225
  }
4871
5226
  };
4872
- Iterator.prototype.arrayChanged = function() {
4873
- var existingNode, item, newItemsInOrder, trackingNodeMap, _i, _len;
4874
- newItemsInOrder = this.collection.toArray();
4875
- trackingNodeMap = new Batman.SimpleHash;
4876
- for (_i = 0, _len = newItemsInOrder.length; _i < _len; _i++) {
4877
- item = newItemsInOrder[_i];
4878
- existingNode = this.nodeMap.get(item);
4879
- trackingNodeMap.set(item, true);
4880
- if (existingNode) {
4881
- this.insertItem(item, existingNode, {
4882
- fragment: false,
4883
- addNumber: this.queuedAddNumber++,
4884
- sync: true
4885
- });
4886
- } else {
4887
- this.addItem(item, {
4888
- fragment: false
4889
- });
4890
- }
4891
- }
4892
- return this.nodeMap.forEach(__bind(function(item, node) {
4893
- if (!trackingNodeMap.hasKey(item)) {
4894
- return this.removeItem(item);
4895
- }
4896
- }, this));
4897
- };
4898
5227
  Iterator.prototype.insertItem = function(item, node, options) {
5228
+ var futureActionNumber, _base, _base2, _name, _name2;
4899
5229
  if (options == null) {
4900
5230
  options = {};
4901
5231
  }
4902
- if (this.nodeMap.get(item) !== node) {
4903
- this.addFunctions[options.addNumber] = function() {};
5232
+ if (this.destroyed) {
5233
+ return;
5234
+ }
5235
+ futureActionNumber = this.actionMap.get(item);
5236
+ if ((futureActionNumber != null) && futureActionNumber > options.actionNumber) {
5237
+ this.actions[options.actionNumber] = function() {};
4904
5238
  } else {
4905
- this.rendererMap.unset(item);
4906
- this.addFunctions[options.addNumber] = function() {
4907
- var show;
4908
- show = Batman.data(node, 'show');
4909
- if (typeof show === 'function') {
4910
- return show.call(node, {
4911
- before: this.siblingNode
4912
- });
4913
- } else {
4914
- if (options.fragment) {
4915
- return this.fragment.appendChild(node);
5239
+ this._removeOldAction(item);
5240
+ this.actionMap.set(item, options.actionNumber);
5241
+ if (this.nodeMap.get(item) !== node) {
5242
+ (_base = this.actions)[_name = options.actionNumber] || (_base[_name] = function() {});
5243
+ } else {
5244
+ this.rendererMap.unset(item);
5245
+ (_base2 = this.actions)[_name2 = options.actionNumber] || (_base2[_name2] = function() {
5246
+ var show;
5247
+ show = Batman.data(node, 'show');
5248
+ if (typeof show === 'function') {
5249
+ return show.call(node, {
5250
+ before: this.siblingNode
5251
+ });
4916
5252
  } else {
4917
- return this.parentNode.insertBefore(node, this.siblingNode);
5253
+ if (options.fragment) {
5254
+ return this.fragment.appendChild(node);
5255
+ } else {
5256
+ return this.parentNode.insertBefore(node, this.siblingNode);
5257
+ }
4918
5258
  }
4919
- }
4920
- };
5259
+ });
5260
+ }
4921
5261
  }
4922
- return this._processAddQueue();
5262
+ this.actions[options.actionNumber].item = item;
5263
+ return this.processActionQueue();
4923
5264
  };
4924
5265
  Iterator.prototype._nodeForItem = function(item) {
4925
5266
  var newNode;
@@ -4927,16 +5268,43 @@
4927
5268
  this.nodeMap.set(item, newNode);
4928
5269
  return newNode;
4929
5270
  };
4930
- Iterator.prototype._processAddQueue = function() {
4931
- var f;
4932
- while (!!(f = this.addFunctions[this.currentAddNumber])) {
4933
- this.addFunctions[this.currentAddNumber] = void 0;
4934
- f.call(this);
4935
- this.currentAddNumber++;
5271
+ Iterator.prototype._removeOldAction = function(item) {
5272
+ var oldActionNumber;
5273
+ oldActionNumber = this.actionMap.get(item);
5274
+ if ((oldActionNumber != null) && oldActionNumber > this.currentActionNumber) {
5275
+ this.actionMap.unset(item);
5276
+ return this.actions[oldActionNumber] = function() {};
5277
+ }
5278
+ };
5279
+ Iterator.prototype.processActionQueue = function() {
5280
+ if (this.destroyed) {
5281
+ return;
4936
5282
  }
4937
- if (this.fragment && this.rendererMap.length === 0 && this.fragment.hasChildNodes()) {
4938
- this.parentNode.insertBefore(this.fragment, this.siblingNode);
4939
- this.fragment = document.createDocumentFragment();
5283
+ if (!this.actionQueueTimeout) {
5284
+ return this.actionQueueTimeout = $setImmediate(__bind(function() {
5285
+ var f, startTime;
5286
+ if (this.destroyed) {
5287
+ return;
5288
+ }
5289
+ delete this.actionQueueTimeout;
5290
+ startTime = new Date;
5291
+ while (!!(f = this.actions[this.currentActionNumber])) {
5292
+ this.actions[this.currentActionNumber] = true;
5293
+ f.call(this);
5294
+ this.currentActionNumber++;
5295
+ if (this.deferEvery && (new Date - startTime) > this.deferEvery) {
5296
+ return this.processActionQueue();
5297
+ }
5298
+ }
5299
+ if (this.fragment && this.rendererMap.length === 0 && this.fragment.hasChildNodes()) {
5300
+ this.parentNode.insertBefore(this.fragment, this.siblingNode);
5301
+ this.fragment = document.createDocumentFragment();
5302
+ }
5303
+ if (this.currentActionNumber === this.queuedActionNumber) {
5304
+ this.parentRenderer.allow('rendered');
5305
+ return this.parentRenderer.fire('rendered');
5306
+ }
5307
+ }, this));
4940
5308
  }
4941
5309
  };
4942
5310
  return Iterator;
@@ -5011,7 +5379,23 @@
5011
5379
  meta: buntUndefined(function(value, keypath) {
5012
5380
  developer.assert(value.meta, "Error, value doesn't have a meta to filter on!");
5013
5381
  return value.meta.get(keypath);
5014
- })
5382
+ }),
5383
+ interpolate: function(string, interpolationKeypaths) {
5384
+ var k, v, values;
5385
+ if (string == null) {
5386
+ return;
5387
+ }
5388
+ values = {};
5389
+ for (k in interpolationKeypaths) {
5390
+ v = interpolationKeypaths[k];
5391
+ values[k] = this.findKey(v)[0];
5392
+ if (!(values[k] != null)) {
5393
+ Batman.developer.warn("Warning! Undefined interpolation key " + k + " for interpolation", string);
5394
+ values[k] = '';
5395
+ }
5396
+ }
5397
+ return Batman.helpers.interpolate(string, values);
5398
+ }
5015
5399
  };
5016
5400
  _ref = ['capitalize', 'singularize', 'underscore', 'camelize'];
5017
5401
  for (_i = 0, _len = _ref.length; _i < _len; _i++) {
@@ -5176,11 +5560,137 @@
5176
5560
  }
5177
5561
  }
5178
5562
  };
5563
+ Batman.Paginator = (function() {
5564
+ __extends(Paginator, Batman.Object);
5565
+ function Paginator() {
5566
+ Paginator.__super__.constructor.apply(this, arguments);
5567
+ }
5568
+ Paginator.Cache = (function() {
5569
+ function Cache(offset, limit, items) {
5570
+ this.offset = offset;
5571
+ this.limit = limit;
5572
+ this.items = items;
5573
+ this.length = items.length;
5574
+ this.reach = offset + limit;
5575
+ }
5576
+ Cache.prototype.containsItemsForOffsetAndLimit = function(offset, limit) {
5577
+ return offset >= this.offset && (offset + limit) <= this.reach;
5578
+ };
5579
+ Cache.prototype.itemsForOffsetAndLimit = function(offset, limit) {
5580
+ var begin, end;
5581
+ if (!this.containsItemsForOffsetAndLimit(offset, limit)) {
5582
+ return;
5583
+ }
5584
+ begin = offset - this.offset;
5585
+ end = begin + limit;
5586
+ return this.items.slice(begin, end);
5587
+ };
5588
+ return Cache;
5589
+ })();
5590
+ Paginator.prototype.offset = 0;
5591
+ Paginator.prototype.limit = 10;
5592
+ Paginator.prototype.totalCount = 0;
5593
+ Paginator.prototype.offsetFromPageAndLimit = function(page, limit) {
5594
+ return Math.round((+page - 1) * limit);
5595
+ };
5596
+ Paginator.prototype.pageFromOffsetAndLimit = function(offset, limit) {
5597
+ return offset / limit + 1;
5598
+ };
5599
+ Paginator.prototype.toArray = function() {
5600
+ var cache, items, limit, offset;
5601
+ cache = this.get('cache');
5602
+ offset = this.get('offset');
5603
+ limit = this.get('limit');
5604
+ items = cache != null ? cache.itemsForOffsetAndLimit(offset, limit) : void 0;
5605
+ if (!items) {
5606
+ this.loadItemsForOffsetAndLimit(offset, limit);
5607
+ }
5608
+ return items || [];
5609
+ };
5610
+ Paginator.prototype.page = function() {
5611
+ return this.pageFromOffsetAndLimit(this.get('offset'), this.get('limit'));
5612
+ };
5613
+ Paginator.prototype.pageCount = function() {
5614
+ return Math.ceil(this.get('totalCount') / this.get('limit'));
5615
+ };
5616
+ Paginator.prototype.previousPage = function() {
5617
+ return this.set('page', this.get('page') - 1);
5618
+ };
5619
+ Paginator.prototype.nextPage = function() {
5620
+ return this.set('page', this.get('page') + 1);
5621
+ };
5622
+ Paginator.prototype.loadItemsForOffsetAndLimit = function(offset, limit) {};
5623
+ Paginator.prototype.updateCache = function(offset, limit, items) {
5624
+ return this.set('cache', new Batman.Paginator.Cache(offset, limit, items));
5625
+ };
5626
+ Paginator.accessor('toArray', Paginator.prototype.toArray);
5627
+ Paginator.accessor('offset', 'limit', 'totalCount', {
5628
+ get: Batman.Property.defaultAccessor.get,
5629
+ set: function(key, value) {
5630
+ return Batman.Property.defaultAccessor.set.call(this, key, +value);
5631
+ }
5632
+ });
5633
+ Paginator.accessor('page', {
5634
+ get: Paginator.prototype.page,
5635
+ set: function(_, value) {
5636
+ value = +value;
5637
+ this.set('offset', this.offsetFromPageAndLimit(value, this.get('limit')));
5638
+ return value;
5639
+ }
5640
+ });
5641
+ Paginator.accessor('pageCount', Paginator.prototype.pageCount);
5642
+ return Paginator;
5643
+ })();
5644
+ Batman.ModelPaginator = (function() {
5645
+ __extends(ModelPaginator, Batman.Paginator);
5646
+ function ModelPaginator() {
5647
+ ModelPaginator.__super__.constructor.apply(this, arguments);
5648
+ }
5649
+ ModelPaginator.prototype.cachePadding = 0;
5650
+ ModelPaginator.prototype.paddedOffset = function(offset) {
5651
+ offset -= this.cachePadding;
5652
+ if (offset < 0) {
5653
+ return 0;
5654
+ } else {
5655
+ return offset;
5656
+ }
5657
+ };
5658
+ ModelPaginator.prototype.paddedLimit = function(limit) {
5659
+ return limit + this.cachePadding * 2;
5660
+ };
5661
+ ModelPaginator.prototype.loadItemsForOffsetAndLimit = function(offset, limit) {
5662
+ var k, params, v, _ref2;
5663
+ params = this.paramsForOffsetAndLimit(offset, limit);
5664
+ _ref2 = this.params;
5665
+ for (k in _ref2) {
5666
+ v = _ref2[k];
5667
+ params[k] = v;
5668
+ }
5669
+ return this.model.load(params, __bind(function(err, records) {
5670
+ if (err == null) {
5671
+ return this.updateCache(this.offsetFromParams(params), this.limitFromParams(params), records);
5672
+ }
5673
+ }, this));
5674
+ };
5675
+ ModelPaginator.prototype.paramsForOffsetAndLimit = function(offset, limit) {
5676
+ return {
5677
+ offset: this.paddedOffset(offset),
5678
+ limit: this.paddedLimit(limit)
5679
+ };
5680
+ };
5681
+ ModelPaginator.prototype.offsetFromParams = function(params) {
5682
+ return params.offset;
5683
+ };
5684
+ ModelPaginator.prototype.limitFromParams = function(params) {
5685
+ return params.limit;
5686
+ };
5687
+ return ModelPaginator;
5688
+ })();
5179
5689
  container = typeof exports !== "undefined" && exports !== null ? (module.exports = Batman, global) : (window.Batman = Batman, window);
5180
5690
  $mixin(container, Batman.Observable);
5181
5691
  Batman.exportHelpers = function(onto) {
5182
5692
  var k, _j, _len2, _ref2;
5183
- _ref2 = ['mixin', 'unmixin', 'route', 'redirect', 'typeOf', 'redirect'];
5693
+ _ref2 = ['mixin', 'unmixin', 'route', 'redirect', 'typeOf', 'redirect', 'setImmediate'];
5184
5694
  for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) {
5185
5695
  k = _ref2[_j];
5186
5696
  onto["$" + k] = Batman[k];