batman-rails 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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];