batman-rails 0.15.4 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 827c6fe292b697c321c92b6ee0f3034720f0081c
4
- data.tar.gz: ca9f83be4e3331e7b3dab5eb4cdc80ee0296ff7d
3
+ metadata.gz: 9c8deae7a6dad1d59f1cb67e92a798e50414598d
4
+ data.tar.gz: a7b266ed03e94f18b5db5b585e4de18138e9510a
5
5
  SHA512:
6
- metadata.gz: a00bce8ce04bcdbac33a8ff75f6b32159613a1840555f4430341733c0933d32aed1789c26d9df0871a91e1979b37d0af5776b9f559e5a91b5cb2d2582e797956
7
- data.tar.gz: 92998e811e228de5b19e0731b77c47b36af61328156109f8f4ce28aa137c81b6d45f423803932c637ebc92f4ea3ee7dfc6ec0caf21919f643f6b8cd605facb3a
6
+ metadata.gz: a69ac93e5f28fc771eae9177305d3cc7c821d8a8f15a01220785b1562930f16b0577187f62c54185739cbf82af8ce27b435bc381eee4d795a18c136f276e896e
7
+ data.tar.gz: 2bd249704f4a95b3dc1bf3fc4acf8e94a7d09788cf362ff4b01b833a4924a4b0cec9883d16707fa134c972226732413609077ae29d682da5ea2bee6e282f0991
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Batman::Rails
2
2
 
3
- Easily setup and use batman.js (0.15) with Rails 4
3
+ Easily setup and use batman.js (0.16) with Rails 4
4
4
 
5
5
  ## Installation
6
6
 
@@ -15,25 +15,32 @@ And then execute:
15
15
 
16
16
  ### Layout and namespacing
17
17
 
18
- Running `rails generate batman:app` will create the following directory structure under `app/assets/app_name/`:
18
+ Running `rails generate batman:app` will:
19
19
 
20
- models/
21
- views/
22
- controllers/
23
- html/
24
- lib/
20
+ - create the following directory structure under `app/assets/batman/`:
21
+
22
+ ```
23
+ - models/
24
+ - views/
25
+ - controllers/
26
+ - html/
27
+ - lib/
28
+ - app_name.coffee # initial setup & requires
29
+ ```
25
30
 
26
- It will also create a toplevel `app_name.coffee` file to setup namespacing and setup initial requires.
31
+ - create a controller & route to server your batman.js app from `/` and serve HTML
32
+ - configure the asset pipeline to precompile `app_name.coffee`
27
33
 
28
34
  ## Generators
29
- Batman-Rails provides 3 simple generators to help get you started using Batman.js with Rails.
30
- The generators will only create client side code (CoffeeScript).
35
+
36
+ Batman-Rails provides 3 simple generators to help get you started using batman.js with Rails.
37
+ The generators will only create client-side code (CoffeeScript).
31
38
 
32
39
  ### Model Generator
33
40
 
34
41
  rails generate batman:model
35
42
 
36
- This generator creates a batman model inside `app/assets/app_name/models` to be used to talk to the Rails backend.
43
+ This generator creates a batman model inside `app/assets/batman/models` to be used to talk to the Rails backend.
37
44
 
38
45
  ### Controllers
39
46
 
@@ -67,6 +74,22 @@ Install the gem and generate scaffolding.
67
74
 
68
75
  You now have installed the `batman-rails` gem, setup a default directory structure for your frontend batman code. Then you generated the usual Rails server side scaffolding and finally generated Batman.js code to provide a simple single page app.
69
76
 
77
+ ## Precompiling Views
78
+
79
+ In production, you may want to send all your HTML templates with the first request rather than sending them as-needed. `batman-rails` includes a view helper to do this. Add it to your application layout:
80
+
81
+ ```erb
82
+ <%= batman_define_views %>
83
+ ```
84
+
85
+ It will gather HTML from `app/assets/batman/html` and interpolate them into JS code to preload `Batman.View.store`. If your HTML is in another directory, pass that directory as an option:
86
+
87
+ ```erb
88
+ <%= batman_define_views(path_to_html: "app/assets/templates/">
89
+ ```
90
+
91
+ Now it will gather HTML from `app/assets/templates`!
92
+
70
93
  ## Contributing
71
94
 
72
95
  1. Fork it
@@ -1,8 +1,14 @@
1
1
  require "batman-rails/version"
2
+ require "batman-rails/helpers/define_view_helper"
2
3
 
3
4
  module Batman
4
5
  module Rails
5
6
  class Engine < ::Rails::Engine
7
+ initializer 'batman_rails.action_controller' do |app|
8
+ ActiveSupport.on_load :action_controller do
9
+ helper Batman::DefineViewHelper
10
+ end
11
+ end
6
12
  end
7
13
  end
8
14
  end
@@ -0,0 +1,45 @@
1
+ module Batman
2
+ module DefineViewHelper
3
+ def batman_define_views(options={})
4
+ content_tag :script do
5
+ template_js_string = BatmanView.all(self, options).map(&:inline_preload_javascript).join
6
+ raw %Q[(function(){#{template_js_string}})();]
7
+ end
8
+ end
9
+
10
+ class BatmanView
11
+ PATH_TO_HTML = "app/assets/batman/html/"
12
+
13
+ attr_accessor :pathname, :render_context, :path_to_html
14
+
15
+ def self.all(render_context, options={})
16
+ path_to_html = options[:path_to_html] || PATH_TO_HTML
17
+ glob_path = File.join(path_to_html, "**/*")
18
+ valid_files = Dir.glob(glob_path).select { |pathname| File.file?(pathname) }
19
+ valid_files.collect do |pathname|
20
+ new(pathname, render_context, path_to_html: path_to_html)
21
+ end
22
+ end
23
+
24
+ def initialize(pathname, render_context, options={})
25
+ @pathname = pathname
26
+ @render_context = render_context
27
+ @path_to_html = options[:path_to_html] || PATH_TO_HTML
28
+ end
29
+
30
+ def inline_preload_javascript
31
+ %Q{Batman.View.store.set(#{name.to_json}, #{content.to_json});}
32
+ end
33
+
34
+ private
35
+
36
+ def name
37
+ pathname.split(path_to_html).last.gsub(/\.html\.[a-z]+$/, '').to_s
38
+ end
39
+
40
+ def content
41
+ render_context.render(file: pathname)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -1,6 +1,6 @@
1
1
  module Batman
2
2
  module Rails
3
- VERSION = "0.15.4"
4
- BATMAN_VERSION = "0.15.0"
3
+ VERSION = "0.16.0"
4
+ BATMAN_VERSION = "0.16.0"
5
5
  end
6
6
  end
@@ -26,6 +26,10 @@ module Batman
26
26
  inject_into_file "config/routes.rb", :after => "#{js_application_name}::Application.routes.draw do\n" do
27
27
  route_catchall
28
28
  end
29
+
30
+ inject_into_file "config/application.rb", :after => "class Application < Rails::Application\n" do
31
+ precompile_app
32
+ end
29
33
  end
30
34
 
31
35
  end
@@ -72,6 +76,12 @@ module Batman
72
76
  CODE
73
77
  end
74
78
 
79
+ def precompile_app
80
+ <<-CODE
81
+ \n config.assets.precompile += ['#{application_name}.js']\n
82
+ CODE
83
+ end
84
+
75
85
  def es5_requires
76
86
  <<-CODE
77
87
  #= require batman/es5-shim\n
@@ -51,7 +51,7 @@ module Batman
51
51
  module ClassMethods
52
52
  def requires_app_name
53
53
  class_option :app_name, :type => :string, :optional => true,
54
- :desc => "Name of the Batman app (defaults to the Rails app name"
54
+ :desc => "Name of the Batman app (defaults to the Rails app name)"
55
55
  end
56
56
  end
57
57
  end
@@ -1,7 +1,13 @@
1
1
  class <%= js_app_name %>Controller < ApplicationController
2
2
 
3
3
  def index
4
- render nothing: true, layout: '<%= app_name %>'
4
+ if request.xhr?
5
+ prefix_length = Rails.application.config.assets.prefix.length + 1
6
+ path = request.path[prefix_length..-1]
7
+ render :text => Rails.application.assets[path]
8
+ else
9
+ render nothing: true, layout: '<%= app_name %>'
10
+ end
5
11
  end
6
12
 
7
13
  end
@@ -0,0 +1,18 @@
1
+ require 'test_helper'
2
+ require 'generators/batman/view_generator'
3
+
4
+ class DefineViewHelperTest < ActionView::TestCase
5
+ include Batman::DefineViewHelper
6
+ tests Batman::DefineViewHelper
7
+
8
+ test "it defaults to looking in app/assets/batman/html" do
9
+ File.expects(:join).with("app/assets/batman/html/", "**/*").returns("app/assets/batman/html/**/*")
10
+ batman_define_views
11
+ end
12
+
13
+ test "it can be given override path" do
14
+ File.expects(:join).with("app/assets/batman/templates/", "**/*").returns("app/assets/batman/templates/**/*")
15
+ batman_define_views(path_to_html: "app/assets/batman/templates")
16
+ end
17
+
18
+ end
@@ -0,0 +1,140 @@
1
+ (function() {
2
+ var __hasProp = {}.hasOwnProperty,
3
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
4
+
5
+ Batman.I18N = (function(_super) {
6
+ __extends(I18N, _super);
7
+
8
+ I18N.defaultLocale = "en";
9
+
10
+ I18N.useFallback = false;
11
+
12
+ I18N.classAccessor('locale', {
13
+ get: function() {
14
+ return this.locale || this.get('defaultLocale');
15
+ },
16
+ set: function(k, v) {
17
+ return this.locale = v;
18
+ },
19
+ unset: function() {
20
+ var x;
21
+ x = this.locale;
22
+ delete this.locale;
23
+ return x;
24
+ }
25
+ });
26
+
27
+ I18N.classAccessor('translations', function() {
28
+ return this.get("locales." + (this.get('locale')));
29
+ });
30
+
31
+ I18N.classAccessor('defaultTranslations', function() {
32
+ return this.get("locales." + this.defaultLocale);
33
+ });
34
+
35
+ I18N.translate = function(key, values) {
36
+ var translation;
37
+ translation = this.get("translations." + key);
38
+ if (this.useFallback) {
39
+ translation || (translation = this.get("defaultTranslations." + key));
40
+ }
41
+ if (translation == null) {
42
+ Batman.developer.warn("Warning, undefined translation " + key + " when in locale " + (this.get('locale')));
43
+ return "";
44
+ }
45
+ if (!values) {
46
+ return translation;
47
+ }
48
+ return Batman.helpers.interpolate(translation, values);
49
+ };
50
+
51
+ I18N.enable = function() {
52
+ var _this = this;
53
+ this._oldTranslation = Batman.translate;
54
+ this.locales.set('en', Batman.translate.messages);
55
+ return Batman.translate = function() {
56
+ return _this.translate.apply(_this, arguments);
57
+ };
58
+ };
59
+
60
+ I18N.disable = function() {
61
+ return Batman.translate = this._oldTranslation;
62
+ };
63
+
64
+ function I18N() {
65
+ Batman.developer.error("Can't instantiate i18n!");
66
+ }
67
+
68
+ return I18N;
69
+
70
+ })(Batman.Object);
71
+
72
+ Batman.I18N.LocalesStorage = (function(_super) {
73
+ __extends(LocalesStorage, _super);
74
+
75
+ function LocalesStorage() {
76
+ this.isStorage = true;
77
+ this._storage = {};
78
+ LocalesStorage.__super__.constructor.apply(this, arguments);
79
+ }
80
+
81
+ LocalesStorage.accessor({
82
+ get: function(k) {
83
+ var _this = this;
84
+ if (!this._storage[k]) {
85
+ this._storage[k] = {};
86
+ new Batman.Request({
87
+ url: "/locales/" + k + ".json",
88
+ success: function(data) {
89
+ return _this.set(k, data[k]);
90
+ },
91
+ error: function(xhr) {
92
+ throw new Error("Couldn't load locale file " + k + "!");
93
+ }
94
+ });
95
+ }
96
+ return this._storage[k];
97
+ },
98
+ set: function(k, v) {
99
+ return this._storage[k] = v;
100
+ },
101
+ unset: function(k) {
102
+ var x;
103
+ x = this._storage[k];
104
+ delete this._storage[k];
105
+ return x;
106
+ }
107
+ });
108
+
109
+ return LocalesStorage;
110
+
111
+ })(Batman.Object);
112
+
113
+ Batman.I18N.set('locales', new Batman.I18N.LocalesStorage);
114
+
115
+ Batman.Filters.t = Batman.Filters.translate = function(string, interpolationKeypaths, binding) {
116
+ var translated;
117
+ if (!binding) {
118
+ binding = interpolationKeypaths;
119
+ interpolationKeypaths = void 0;
120
+ }
121
+ if (string == null) {
122
+ return "";
123
+ }
124
+ if (!(binding.key && binding.key.substr(0, 2) === "t.")) {
125
+ translated = Batman.I18N.translate(string);
126
+ if (translated) {
127
+ string = translated;
128
+ }
129
+ }
130
+ return Batman.Filters.interpolate.call(this, string, interpolationKeypaths, binding);
131
+ };
132
+
133
+ Batman.config.translations = true;
134
+
135
+ }).call(this);
136
+
137
+ (function() {
138
+
139
+
140
+ }).call(this);
@@ -12,7 +12,7 @@
12
12
  })(Batman.Object, mixins, function(){});
13
13
  };
14
14
 
15
- Batman.version = '0.14.1';
15
+ Batman.version = '0.16.0';
16
16
 
17
17
  Batman.config = {
18
18
  pathToApp: '/',
@@ -433,6 +433,7 @@
433
433
  "<": "&lt;",
434
434
  ">": "&gt;",
435
435
  "\"": "&#34;",
436
+ "/": "&#47;",
436
437
  "'": "&#39;"
437
438
  };
438
439
 
@@ -624,11 +625,11 @@
624
625
  }).call(this);
625
626
 
626
627
  (function() {
627
- var Inflector, camelize_rx, capitalize_rx, humanize_rx1, humanize_rx2, humanize_rx3, underscore_rx1, underscore_rx2;
628
+ var Inflector, camelize_rx, humanize_rx1, humanize_rx2, humanize_rx3, titleize_rx, underscore_rx1, underscore_rx2;
628
629
 
629
630
  camelize_rx = /(?:^|_|\-)(.)/g;
630
631
 
631
- capitalize_rx = /(^|\s)([a-z])/g;
632
+ titleize_rx = /(^|\s)([a-z])/g;
632
633
 
633
634
  underscore_rx1 = /([A-Z]+)([A-Z][a-z])/g;
634
635
 
@@ -636,7 +637,7 @@
636
637
 
637
638
  humanize_rx1 = /_id$/;
638
639
 
639
- humanize_rx2 = /_|-/g;
640
+ humanize_rx2 = /_|-|\./g;
640
641
 
641
642
  humanize_rx3 = /^\w/g;
642
643
 
@@ -675,11 +676,15 @@
675
676
  underscore: function(string) {
676
677
  return string.replace(underscore_rx1, '$1_$2').replace(underscore_rx2, '$1_$2').replace('-', '_').toLowerCase();
677
678
  },
678
- capitalize: function(string) {
679
- return string.replace(capitalize_rx, function(m, p1, p2) {
679
+ titleize: function(string) {
680
+ return string.replace(titleize_rx, function(m, p1, p2) {
680
681
  return p1 + p2.toUpperCase();
681
682
  });
682
683
  },
684
+ capitalize: function(string) {
685
+ Batman.developer.deprecated('capitalize', 'Renamed to titleize.');
686
+ return Batman.helpers.titleize(string);
687
+ },
683
688
  trim: function(string) {
684
689
  if (string) {
685
690
  return string.trim();
@@ -709,6 +714,17 @@
709
714
  return string.replace(humanize_rx1, '').replace(humanize_rx2, ' ').replace(humanize_rx3, function(match) {
710
715
  return match.toUpperCase();
711
716
  });
717
+ },
718
+ toSentence: function(array) {
719
+ var itemString, last;
720
+ if (array.length < 3) {
721
+ return array.join(' and ');
722
+ } else {
723
+ last = array.pop();
724
+ itemString = array.join(', ');
725
+ itemString += ", and " + last;
726
+ return itemString;
727
+ }
712
728
  }
713
729
  };
714
730
 
@@ -1050,7 +1066,7 @@
1050
1066
  };
1051
1067
 
1052
1068
  Event.prototype.allowAndFire = function() {
1053
- return this.allowAndFireWithContext(this.handlerContext, arguments);
1069
+ return this.allowAndFireWithContext(this.handlerContext(), arguments);
1054
1070
  };
1055
1071
 
1056
1072
  Event.prototype.allowAndFireWithContext = function(context, args) {
@@ -1298,7 +1314,7 @@
1298
1314
  }
1299
1315
  result = true;
1300
1316
  this.forEach(function() {
1301
- return result = result && f.apply(ctx, arguments);
1317
+ return result = result && !!f.apply(ctx, arguments);
1302
1318
  });
1303
1319
  return result;
1304
1320
  },
@@ -1309,7 +1325,7 @@
1309
1325
  }
1310
1326
  result = false;
1311
1327
  this.forEach(function() {
1312
- return result = result || f.apply(ctx, arguments);
1328
+ return result = result || !!f.apply(ctx, arguments);
1313
1329
  });
1314
1330
  return result;
1315
1331
  },
@@ -1690,7 +1706,24 @@
1690
1706
  return obj;
1691
1707
  };
1692
1708
 
1693
- SimpleHash.prototype.toJSON = SimpleHash.prototype.toObject;
1709
+ SimpleHash.prototype.toJSON = function() {
1710
+ var key, obj, objectKey, value, values, _ref, _ref1, _ref2;
1711
+ obj = {};
1712
+ _ref = this._storage;
1713
+ for (key in _ref) {
1714
+ value = _ref[key];
1715
+ obj[this.unprefixedKey(key)] = (value != null ? typeof value.toJSON === "function" ? value.toJSON() : void 0 : void 0) || value;
1716
+ }
1717
+ if (this._objectStorage) {
1718
+ _ref1 = this._objectStorage;
1719
+ for (key in _ref1) {
1720
+ values = _ref1[key];
1721
+ _ref2 = values[0], objectKey = _ref2[0], value = _ref2[1];
1722
+ obj[key] = (value != null ? typeof value.toJSON === "function" ? value.toJSON() : void 0 : void 0) || value;
1723
+ }
1724
+ }
1725
+ return obj;
1726
+ };
1694
1727
 
1695
1728
  return SimpleHash;
1696
1729
 
@@ -1732,6 +1765,14 @@
1732
1765
  return this.get(label);
1733
1766
  };
1734
1767
 
1768
+ AssociationCurator.prototype.getAll = function() {
1769
+ var allAssociations, typeSets, _ref;
1770
+ typeSets = this._byTypeStorage.map(function(label, typeSet) {
1771
+ return typeSet;
1772
+ });
1773
+ return allAssociations = (_ref = new Batman.SimpleSet).merge.apply(_ref, typeSets);
1774
+ };
1775
+
1735
1776
  AssociationCurator.prototype.reset = function() {
1736
1777
  this.forEach(function(label, association) {
1737
1778
  return association.reset();
@@ -1921,6 +1962,14 @@
1921
1962
  return merged;
1922
1963
  };
1923
1964
 
1965
+ SimpleSet.prototype.mappedTo = function(key) {
1966
+ var _this = this;
1967
+ this._mappings || (this._mappings = new Batman.SimpleHash);
1968
+ return this._mappings.getOrSet(key, function() {
1969
+ return new Batman.SetMapping(_this, key);
1970
+ });
1971
+ };
1972
+
1924
1973
  SimpleSet.prototype.indexedBy = function(key) {
1925
1974
  this._indexes || (this._indexes = new Batman.SimpleHash);
1926
1975
  return this._indexes.get(key) || this._indexes.set(key, new Batman.SetIndex(this, key));
@@ -2100,6 +2149,8 @@
2100
2149
 
2101
2150
  Property.prototype.isDead = false;
2102
2151
 
2152
+ Property.prototype.isBatchingChanges = false;
2153
+
2103
2154
  Property.prototype.registerAsMutableSource = function() {
2104
2155
  return Batman.Property.registerSource(this);
2105
2156
  };
@@ -2241,7 +2292,9 @@
2241
2292
 
2242
2293
  Property.prototype.sourceChangeHandler = function() {
2243
2294
  var _this = this;
2244
- this._sourceChangeHandler || (this._sourceChangeHandler = this._handleSourceChange.bind(this));
2295
+ this._sourceChangeHandler || (this._sourceChangeHandler = function() {
2296
+ return _this._handleSourceChange();
2297
+ });
2245
2298
  Batman.developer["do"](function() {
2246
2299
  return _this._sourceChangeHandler.property = _this;
2247
2300
  });
@@ -2256,7 +2309,7 @@
2256
2309
  } else if (!this.isFinal() && !this.hasObservers()) {
2257
2310
  this.cached = false;
2258
2311
  return this._removeHandlers();
2259
- } else {
2312
+ } else if (!this.isBatchingChanges) {
2260
2313
  return this.refresh();
2261
2314
  }
2262
2315
  };
@@ -2750,6 +2803,10 @@
2750
2803
  definition.invert = true;
2751
2804
  return new Batman.DOM.InsertionBinding(definition);
2752
2805
  },
2806
+ deferif: function(definition) {
2807
+ definition.invert = true;
2808
+ return new Batman.DOM.DeferredRenderBinding(definition);
2809
+ },
2753
2810
  renderif: function(definition) {
2754
2811
  return new Batman.DOM.DeferredRenderBinding(definition);
2755
2812
  },
@@ -2827,9 +2884,9 @@
2827
2884
  __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
2828
2885
 
2829
2886
  Batman.DOM.events = {
2830
- click: function(node, callback, view, eventName, preventDefault) {
2887
+ primaryInteractionEvent: function(node, callback, view, eventName, preventDefault) {
2831
2888
  if (eventName == null) {
2832
- eventName = 'click';
2889
+ eventName = Batman.DOM.primaryInteractionEventName;
2833
2890
  }
2834
2891
  if (preventDefault == null) {
2835
2892
  preventDefault = true;
@@ -2853,6 +2910,15 @@
2853
2910
  }
2854
2911
  return node;
2855
2912
  },
2913
+ click: function(node, callback, view, eventName, preventDefault) {
2914
+ if (eventName == null) {
2915
+ eventName = 'click';
2916
+ }
2917
+ if (preventDefault == null) {
2918
+ preventDefault = true;
2919
+ }
2920
+ return Batman.DOM.events.primaryInteractionEvent(node, callback, view, eventName, preventDefault);
2921
+ },
2856
2922
  doubleclick: function(node, callback, view) {
2857
2923
  return Batman.DOM.events.click(node, callback, view, 'dblclick');
2858
2924
  },
@@ -2943,6 +3009,8 @@
2943
3009
  return true;
2944
3010
  };
2945
3011
 
3012
+ Batman.DOM.primaryInteractionEventName = 'click';
3013
+
2946
3014
  }).call(this);
2947
3015
 
2948
3016
  (function() {
@@ -3001,6 +3069,13 @@
3001
3069
  event: function(definition) {
3002
3070
  return new Batman.DOM.EventBinding(definition);
3003
3071
  },
3072
+ track: function(definition) {
3073
+ if (definition.attr === 'view') {
3074
+ return new Batman.DOM.ViewTrackingBinding(definition);
3075
+ } else if (definition.attr === 'click') {
3076
+ return new Batman.DOM.ClickTrackingBinding(definition);
3077
+ }
3078
+ },
3004
3079
  addclass: function(definition) {
3005
3080
  return new Batman.DOM.AddClassBinding(definition);
3006
3081
  },
@@ -3211,6 +3286,62 @@
3211
3286
  return this._batmanID();
3212
3287
  });
3213
3288
 
3289
+ BatmanObject.delegate = function() {
3290
+ var options, properties, _i,
3291
+ _this = this;
3292
+ properties = 2 <= arguments.length ? __slice.call(arguments, 0, _i = arguments.length - 1) : (_i = 0, []), options = arguments[_i++];
3293
+ if (options == null) {
3294
+ options = {};
3295
+ }
3296
+ if (!options.to) {
3297
+ Batman.developer.warn('delegate must include to option', this, properties);
3298
+ }
3299
+ return properties.forEach(function(property) {
3300
+ return _this.accessor(property, {
3301
+ get: function() {
3302
+ var _ref;
3303
+ return (_ref = this.get(options.to)) != null ? _ref.get(property) : void 0;
3304
+ },
3305
+ set: function(key, value) {
3306
+ var _ref;
3307
+ return (_ref = this.get(options.to)) != null ? _ref.set(property, value) : void 0;
3308
+ },
3309
+ unset: function() {
3310
+ var _ref;
3311
+ return (_ref = this.get(options.to)) != null ? _ref.unset(property) : void 0;
3312
+ }
3313
+ });
3314
+ });
3315
+ };
3316
+
3317
+ BatmanObject.classDelegate = function() {
3318
+ var options, properties, _i,
3319
+ _this = this;
3320
+ properties = 2 <= arguments.length ? __slice.call(arguments, 0, _i = arguments.length - 1) : (_i = 0, []), options = arguments[_i++];
3321
+ if (options == null) {
3322
+ options = {};
3323
+ }
3324
+ if (!options.to) {
3325
+ Batman.developer.warn('delegate must include to option', this, properties);
3326
+ }
3327
+ return properties.forEach(function(property) {
3328
+ return _this.classAccessor(property, {
3329
+ get: function() {
3330
+ var _ref;
3331
+ return (_ref = this.get(options.to)) != null ? _ref.get(property) : void 0;
3332
+ },
3333
+ set: function(key, value) {
3334
+ var _ref;
3335
+ return (_ref = this.get(options.to)) != null ? _ref.set(property, value) : void 0;
3336
+ },
3337
+ unset: function() {
3338
+ var _ref;
3339
+ return (_ref = this.get(options.to)) != null ? _ref.unset(property) : void 0;
3340
+ }
3341
+ });
3342
+ });
3343
+ };
3344
+
3214
3345
  function BatmanObject() {
3215
3346
  var mixins;
3216
3347
  mixins = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
@@ -3250,6 +3381,23 @@
3250
3381
  return obj;
3251
3382
  };
3252
3383
 
3384
+ BatmanObject.prototype.batchAccessorChanges = function() {
3385
+ var i, key, properties, property, result, wrappedFunction, _i, _j, _k, _len, _len1;
3386
+ properties = 2 <= arguments.length ? __slice.call(arguments, 0, _i = arguments.length - 1) : (_i = 0, []), wrappedFunction = arguments[_i++];
3387
+ for (i = _j = 0, _len = properties.length; _j < _len; i = ++_j) {
3388
+ key = properties[i];
3389
+ property = properties[i] = this.property(key);
3390
+ property.isBatchingChanges = true;
3391
+ }
3392
+ result = wrappedFunction.call(this);
3393
+ for (_k = 0, _len1 = properties.length; _k < _len1; _k++) {
3394
+ property = properties[_k];
3395
+ property.isBatchingChanges = false;
3396
+ property.refresh();
3397
+ }
3398
+ return result;
3399
+ };
3400
+
3253
3401
  return BatmanObject;
3254
3402
 
3255
3403
  })(Object);
@@ -3274,7 +3422,7 @@
3274
3422
  this.parseTree(this.node);
3275
3423
  }
3276
3424
 
3277
- bindingSortOrder = ["defineview", "foreach", "renderif", "view", "formfor", "context", "bind", "source", "target"];
3425
+ bindingSortOrder = ["defineview", "foreach", "renderif", "view", "formfor", "context", "bind", "source", "target", "track", "event"];
3278
3426
 
3279
3427
  viewBackedBindings = ["foreach", "renderif", "formfor", "context"];
3280
3428
 
@@ -3407,7 +3555,7 @@
3407
3555
  });
3408
3556
  } else {
3409
3557
  return Batman.t('errors.format', {
3410
- attribute: Batman.helpers.humanize(this.attribute),
3558
+ attribute: Batman.helpers.humanize(Batman.ValidationError.singularizeAssociated(this.attribute)),
3411
3559
  message: this.message
3412
3560
  });
3413
3561
  }
@@ -3420,6 +3568,15 @@
3420
3568
  });
3421
3569
  }
3422
3570
 
3571
+ ValidationError.singularizeAssociated = function(attribute) {
3572
+ var i, parts, _i, _ref;
3573
+ parts = attribute.split(".");
3574
+ for (i = _i = 0, _ref = parts.length - 1; _i < _ref; i = _i += 1) {
3575
+ parts[i] = Batman.helpers.singularize(parts[i]);
3576
+ }
3577
+ return parts.join(" ");
3578
+ };
3579
+
3423
3580
  return ValidationError;
3424
3581
 
3425
3582
  })(Batman.Object);
@@ -3474,6 +3631,19 @@
3474
3631
 
3475
3632
  })(StorageAdapter.StorageError);
3476
3633
 
3634
+ StorageAdapter.UnauthorizedError = (function(_super1) {
3635
+ __extends(UnauthorizedError, _super1);
3636
+
3637
+ UnauthorizedError.prototype.name = 'UnauthorizedError';
3638
+
3639
+ function UnauthorizedError(message) {
3640
+ UnauthorizedError.__super__.constructor.call(this, message || "Storage operation denied due to invalid credentials!");
3641
+ }
3642
+
3643
+ return UnauthorizedError;
3644
+
3645
+ })(StorageAdapter.StorageError);
3646
+
3477
3647
  StorageAdapter.NotAllowedError = (function(_super1) {
3478
3648
  __extends(NotAllowedError, _super1);
3479
3649
 
@@ -3500,6 +3670,19 @@
3500
3670
 
3501
3671
  })(StorageAdapter.StorageError);
3502
3672
 
3673
+ StorageAdapter.EntityTooLargeError = (function(_super1) {
3674
+ __extends(EntityTooLargeError, _super1);
3675
+
3676
+ EntityTooLargeError.prototype.name = "EntityTooLargeError";
3677
+
3678
+ function EntityTooLargeError(message) {
3679
+ EntityTooLargeError.__super__.constructor.call(this, message || "Storage operation denied due to size constraints!");
3680
+ }
3681
+
3682
+ return EntityTooLargeError;
3683
+
3684
+ })(StorageAdapter.StorageError);
3685
+
3503
3686
  StorageAdapter.UnprocessableRecordError = (function(_super1) {
3504
3687
  __extends(UnprocessableRecordError, _super1);
3505
3688
 
@@ -3539,6 +3722,19 @@
3539
3722
 
3540
3723
  })(StorageAdapter.StorageError);
3541
3724
 
3725
+ StorageAdapter.BadGatewayError = (function(_super1) {
3726
+ __extends(BadGatewayError, _super1);
3727
+
3728
+ BadGatewayError.prototype.name = "BadGatewayError";
3729
+
3730
+ function BadGatewayError(message) {
3731
+ BadGatewayError.__super__.constructor.call(this, message || "Storage operation failed due to unavailability of the backend!");
3732
+ }
3733
+
3734
+ return BadGatewayError;
3735
+
3736
+ })(StorageAdapter.StorageError);
3737
+
3542
3738
  function StorageAdapter(model) {
3543
3739
  var constructor;
3544
3740
  StorageAdapter.__super__.constructor.call(this, {
@@ -3555,6 +3751,25 @@
3555
3751
 
3556
3752
  StorageAdapter.prototype.isStorageAdapter = true;
3557
3753
 
3754
+ StorageAdapter.prototype.onlyCertainAttributes = function(json, only) {
3755
+ var key;
3756
+ for (key in json) {
3757
+ if (only.indexOf(key) < 0) {
3758
+ delete json[key];
3759
+ }
3760
+ }
3761
+ return json;
3762
+ };
3763
+
3764
+ StorageAdapter.prototype.exceptCertainAttributes = function(json, except) {
3765
+ var key, _i, _len;
3766
+ for (_i = 0, _len = except.length; _i < _len; _i++) {
3767
+ key = except[_i];
3768
+ delete json[key];
3769
+ }
3770
+ return json;
3771
+ };
3772
+
3558
3773
  StorageAdapter.prototype.storageKey = function(record) {
3559
3774
  var model;
3560
3775
  model = (record != null ? record.constructor : void 0) || this.model;
@@ -3565,14 +3780,14 @@
3565
3780
  if (constructor == null) {
3566
3781
  constructor = this.model;
3567
3782
  }
3568
- return constructor._makeOrFindRecordFromData(attributes);
3783
+ return constructor.createFromJSON(attributes);
3569
3784
  };
3570
3785
 
3571
3786
  StorageAdapter.prototype.getRecordsFromData = function(attributeSet, constructor) {
3572
3787
  if (constructor == null) {
3573
3788
  constructor = this.model;
3574
3789
  }
3575
- return constructor._makeOrFindRecordsFromData(attributeSet);
3790
+ return constructor.createMultipleFromJSON(attributeSet);
3576
3791
  };
3577
3792
 
3578
3793
  StorageAdapter.skipIfError = function(f) {
@@ -3744,7 +3959,7 @@
3744
3959
  }
3745
3960
  this.url = function(options) {
3746
3961
  var childSegment, parentID, plural;
3747
- childSegment = Batman.helpers.pluralize(this.get('resourceName').toLowerCase());
3962
+ childSegment = this.storageKey || Batman.helpers.pluralize(this.get('resourceName').toLowerCase());
3748
3963
  for (key in parents) {
3749
3964
  plural = parents[key];
3750
3965
  parentID = options.data[key];
@@ -3757,7 +3972,7 @@
3757
3972
  };
3758
3973
  return this.prototype.url = function() {
3759
3974
  var childSegment, id, parentID, plural, url;
3760
- childSegment = Batman.helpers.pluralize(this.constructor.get('resourceName').toLowerCase());
3975
+ childSegment = this.constructor.storageKey || Batman.helpers.pluralize(this.constructor.get('resourceName').toLowerCase());
3761
3976
  for (key in parents) {
3762
3977
  plural = parents[key];
3763
3978
  parentID = this.get('dirtyKeys').get(key);
@@ -3916,6 +4131,12 @@
3916
4131
  RestStorage.prototype.before('create', 'update', RestStorage.skipIfError(function(env, next) {
3917
4132
  var data, json, namespace;
3918
4133
  json = env.subject.toJSON();
4134
+ if (env.options.only) {
4135
+ json = this.onlyCertainAttributes(json, env.options.only);
4136
+ }
4137
+ if (env.options.except) {
4138
+ json = this.exceptCertainAttributes(json, env.options.except);
4139
+ }
3919
4140
  if (namespace = this.recordJsonNamespace(env.subject)) {
3920
4141
  data = {};
3921
4142
  data[namespace] = json;
@@ -4033,13 +4254,16 @@
4033
4254
 
4034
4255
  RestStorage._statusCodeErrors = {
4035
4256
  '0': RestStorage.CommunicationError,
4257
+ '401': RestStorage.UnauthorizedError,
4036
4258
  '403': RestStorage.NotAllowedError,
4037
4259
  '404': RestStorage.NotFoundError,
4038
4260
  '406': RestStorage.NotAcceptableError,
4039
4261
  '409': RestStorage.RecordExistsError,
4262
+ '413': RestStorage.EntityTooLargeError,
4040
4263
  '422': RestStorage.UnprocessableRecordError,
4041
4264
  '500': RestStorage.InternalStorageError,
4042
- '501': RestStorage.NotImplementedError
4265
+ '501': RestStorage.NotImplementedError,
4266
+ '502': RestStorage.BadGatewayError
4043
4267
  };
4044
4268
 
4045
4269
  RestStorage.prototype._errorFor = function(error, env) {
@@ -4151,7 +4375,15 @@
4151
4375
  }));
4152
4376
 
4153
4377
  LocalStorage.prototype.before('create', 'update', LocalStorage.skipIfError(function(env, next) {
4154
- env.recordAttributes = JSON.stringify(env.subject);
4378
+ var json;
4379
+ json = env.subject.toJSON();
4380
+ if (env.options.only) {
4381
+ json = this.onlyCertainAttributes(json, env.options.only);
4382
+ }
4383
+ if (env.options.except) {
4384
+ json = this.exceptCertainAttributes(json, env.options.except);
4385
+ }
4386
+ env.recordAttributes = JSON.stringify(json);
4155
4387
  return next();
4156
4388
  }));
4157
4389
 
@@ -4516,7 +4748,7 @@
4516
4748
  return {
4517
4749
  controller: resourceNameFromModel(argument.constructor),
4518
4750
  action: 'show',
4519
- id: argument.get('id')
4751
+ id: (typeof argument.toParam === "function" ? argument.toParam() : void 0) || argument.get('id')
4520
4752
  };
4521
4753
  } else {
4522
4754
  return {};
@@ -4542,7 +4774,7 @@
4542
4774
  ControllerDirectory.accessor('__app', Batman.Property.defaultAccessor);
4543
4775
 
4544
4776
  ControllerDirectory.accessor(function(key) {
4545
- return this.get("__app." + (Batman.helpers.capitalize(key)) + "Controller.sharedController");
4777
+ return this.get("__app." + (Batman.helpers.titleize(key)) + "Controller.sharedController");
4546
4778
  });
4547
4779
 
4548
4780
  return ControllerDirectory;
@@ -4689,7 +4921,9 @@
4689
4921
  for (index = _i = 0, _len = matches.length; _i < _len; index = ++_i) {
4690
4922
  match = matches[index];
4691
4923
  name = namedArguments[index];
4692
- params[name] = match;
4924
+ if (match != null) {
4925
+ params[name] = decodeURIComponent(match);
4926
+ }
4693
4927
  }
4694
4928
  return Batman.extend(params, uri.queryParams);
4695
4929
  };
@@ -4791,9 +5025,13 @@
4791
5025
 
4792
5026
  function ControllerActionRoute(templatePath, options) {
4793
5027
  this.callback = __bind(this.callback, this);
4794
- var action, controller, _ref;
5028
+ var action, controller, _ref, _ref1;
4795
5029
  if (options.signature) {
4796
- _ref = options.signature.split('#'), controller = _ref[0], action = _ref[1];
5030
+ if (Batman.typeOf(options.signature) === 'String') {
5031
+ _ref = options.signature.split('#'), controller = _ref[0], action = _ref[1];
5032
+ } else {
5033
+ _ref1 = options.signature, controller = _ref1.controller, action = _ref1.action;
5034
+ }
4797
5035
  action || (action = 'index');
4798
5036
  options.controller = controller;
4799
5037
  options.action = action;
@@ -5279,6 +5517,9 @@
5279
5517
  _results = [];
5280
5518
  for (_i = 0, _len = value.length; _i < _len; _i++) {
5281
5519
  handler = value[_i];
5520
+ if (typeof handler === 'string') {
5521
+ handler = _this[handler];
5522
+ }
5282
5523
  _results.push(handler.call(_this, error));
5283
5524
  }
5284
5525
  return _results;
@@ -5379,16 +5620,14 @@
5379
5620
  }
5380
5621
  } else {
5381
5622
  if (Batman.typeOf(url) === 'Object') {
5382
- if (!url.controller) {
5383
- url.controller = this;
5384
- }
5623
+ url.controller || (url.controller = this.get('routingKey'));
5385
5624
  }
5386
5625
  return Batman.redirect(url);
5387
5626
  }
5388
5627
  };
5389
5628
 
5390
5629
  Controller.prototype.render = function(options) {
5391
- var action, frame, view, yieldContentView, yieldName, _ref, _ref1, _ref2, _ref3;
5630
+ var action, frame, view, yieldContentView, yieldName, _ref, _ref1, _ref2, _ref3, _ref4;
5392
5631
  if (options == null) {
5393
5632
  options = {};
5394
5633
  }
@@ -5404,7 +5643,7 @@
5404
5643
  options.view = null;
5405
5644
  } else {
5406
5645
  options.viewClass || (options.viewClass = this._viewClassForAction(action));
5407
- options.source || (options.source = Batman.helpers.underscore(this.get('routingKey') + '/' + action));
5646
+ options.source || (options.source = ((_ref1 = options.viewClass) != null ? _ref1.prototype.source : void 0) || Batman.helpers.underscore(this.get('routingKey') + '/' + action));
5408
5647
  view = this.renderCache.viewForOptions(options);
5409
5648
  }
5410
5649
  if (view) {
@@ -5421,10 +5660,10 @@
5421
5660
  view.set('contentFor', yieldName);
5422
5661
  }
5423
5662
  view.set('controller', this);
5424
- if ((_ref1 = Batman.currentApp) != null) {
5425
- if ((_ref2 = _ref1.layout) != null) {
5426
- if ((_ref3 = _ref2.subviews) != null) {
5427
- _ref3.add(view);
5663
+ if ((_ref2 = Batman.currentApp) != null) {
5664
+ if ((_ref3 = _ref2.layout) != null) {
5665
+ if ((_ref4 = _ref3.subviews) != null) {
5666
+ _ref4.add(view);
5428
5667
  }
5429
5668
  }
5430
5669
  }
@@ -5526,7 +5765,7 @@
5526
5765
 
5527
5766
  Set._applySetAccessors(Set);
5528
5767
 
5529
- _ref = ['indexedBy', 'indexedByUnique', 'sortedBy', 'equality', '_indexOfItem'];
5768
+ _ref = ['indexedBy', 'indexedByUnique', 'sortedBy', 'equality', '_indexOfItem', 'mappedTo'];
5530
5769
  for (_i = 0, _len = _ref.length; _i < _len; _i++) {
5531
5770
  k = _ref[_i];
5532
5771
  Set.prototype[k] = Batman.SimpleSet.prototype[k];
@@ -5544,7 +5783,11 @@
5544
5783
  _fn(k);
5545
5784
  }
5546
5785
 
5547
- Set.prototype.toJSON = Set.prototype.toArray;
5786
+ Set.prototype.toJSON = function() {
5787
+ return this.map(function(value) {
5788
+ return (typeof value.toJSON === "function" ? value.toJSON() : void 0) || value;
5789
+ });
5790
+ };
5548
5791
 
5549
5792
  Set.prototype.add = Set.mutation(function() {
5550
5793
  var addedItems;
@@ -6005,7 +6248,7 @@
6005
6248
  predicateKeys = [];
6006
6249
  definePredicate = function(state) {
6007
6250
  var key;
6008
- key = "is" + (Batman.helpers.capitalize(state));
6251
+ key = "is" + (Batman.helpers.titleize(state));
6009
6252
  if (_this.prototype[key] != null) {
6010
6253
  return;
6011
6254
  }
@@ -6259,7 +6502,9 @@
6259
6502
  if ((matches = validatorClass.matches(optionsOrFunction))) {
6260
6503
  validators.push({
6261
6504
  keys: keys,
6262
- validator: new validatorClass(matches)
6505
+ validator: new validatorClass(matches),
6506
+ "if": optionsOrFunction["if"],
6507
+ unless: optionsOrFunction.unless
6263
6508
  });
6264
6509
  }
6265
6510
  }
@@ -6333,16 +6578,25 @@
6333
6578
  };
6334
6579
 
6335
6580
  Model.findWithOptions = function(id, options, callback) {
6336
- var record;
6581
+ var record,
6582
+ _this = this;
6337
6583
  if (options == null) {
6338
6584
  options = {};
6339
6585
  }
6340
6586
  Batman.developer.assert(callback, "Must call find with a callback!");
6341
- record = new this;
6342
- record._withoutDirtyTracking(function() {
6343
- return this.set('id', id);
6587
+ this._pending || (this._pending = {});
6588
+ record = this._loadIdentity(id) || this._pending[id];
6589
+ if (record == null) {
6590
+ record = new this;
6591
+ record._withoutDirtyTracking(function() {
6592
+ return this.set('id', id);
6593
+ });
6594
+ this._pending[id] = record;
6595
+ }
6596
+ record.loadWithOptions(options, function() {
6597
+ delete _this._pending[id];
6598
+ return callback.apply(_this, arguments);
6344
6599
  });
6345
- record.loadWithOptions(options, callback);
6346
6600
  return record;
6347
6601
  };
6348
6602
 
@@ -6400,6 +6654,10 @@
6400
6654
  return this._makeOrFindRecordFromData(json);
6401
6655
  };
6402
6656
 
6657
+ Model.createMultipleFromJSON = function(array) {
6658
+ return this._makeOrFindRecordsFromData(array);
6659
+ };
6660
+
6403
6661
  Model._loadIdentity = function(id) {
6404
6662
  return this.get('loaded.indexedByUnique.id').get(id);
6405
6663
  };
@@ -6625,10 +6883,7 @@
6625
6883
  }
6626
6884
  },
6627
6885
  set: function(key, value) {
6628
- var parsedValue, primaryKey;
6629
- if ((typeof value === "string") && (value.match(/[^0-9]/) === null) && (("" + (parsedValue = parseInt(value, 10))) === value)) {
6630
- value = parsedValue;
6631
- }
6886
+ var primaryKey;
6632
6887
  primaryKey = this.constructor.primaryKey;
6633
6888
  if (primaryKey === 'id') {
6634
6889
  this._willSet(key);
@@ -6664,22 +6919,20 @@
6664
6919
  Model.prototype.toJSON = function() {
6665
6920
  var encoders, obj,
6666
6921
  _this = this;
6667
- obj = {};
6668
6922
  encoders = this._batman.get('encoders');
6669
- if (!(!encoders || encoders.isEmpty())) {
6670
- encoders.forEach(function(key, encoder) {
6671
- var encodedVal, val;
6672
- if (encoder.encode) {
6673
- val = _this.get(key);
6674
- if (typeof val !== 'undefined') {
6675
- encodedVal = encoder.encode(val, key, obj, _this);
6676
- if (typeof encodedVal !== 'undefined') {
6677
- return obj[encoder.as] = encodedVal;
6678
- }
6679
- }
6680
- }
6681
- });
6923
+ if (!encoders || encoders.isEmpty()) {
6924
+ return {};
6682
6925
  }
6926
+ obj = {};
6927
+ encoders.forEach(function(key, encoder) {
6928
+ var encodedVal, val, _ref2;
6929
+ if (!encoder.encode || (val = _this.get(key)) === void 0) {
6930
+ return;
6931
+ }
6932
+ if ((encodedVal = encoder.encode(val, key, obj, _this)) !== void 0) {
6933
+ return obj[(_ref2 = typeof encoder.as === "function" ? encoder.as(key, val, obj, _this) : void 0) != null ? _ref2 : encoder.as] = encodedVal;
6934
+ }
6935
+ });
6683
6936
  return obj;
6684
6937
  };
6685
6938
 
@@ -6697,9 +6950,16 @@
6697
6950
  }
6698
6951
  } else {
6699
6952
  encoders.forEach(function(key, encoder) {
6700
- if (encoder.decode && typeof data[encoder.as] !== 'undefined') {
6701
- return obj[key] = encoder.decode(data[encoder.as], encoder.as, data, obj, _this);
6953
+ var as, _ref2;
6954
+ if (!encoder.decode) {
6955
+ return;
6956
+ }
6957
+ as = (_ref2 = typeof encoder.as === "function" ? encoder.as(key, data[key], obj, _this) : void 0) != null ? _ref2 : encoder.as;
6958
+ value = data[as];
6959
+ if (value === void 0 || (value === null && (_this._associationForAttribute(as) != null))) {
6960
+ return;
6702
6961
  }
6962
+ return obj[key] = encoder.decode(value, as, data, obj, _this);
6703
6963
  });
6704
6964
  }
6705
6965
  if (this.constructor.primaryKey !== 'id') {
@@ -6785,11 +7045,12 @@
6785
7045
  _ref3 = isNew ? ['create', 'create', 'created'] : ['save', 'update', 'saved'], startState = _ref3[0], storageOperation = _ref3[1], endState = _ref3[2];
6786
7046
  if (this.get('lifecycle').startTransition(startState)) {
6787
7047
  return this.validate(function(error, errors) {
6788
- var associations;
7048
+ var associations, payload;
6789
7049
  if (error || errors.length) {
6790
7050
  _this.get('lifecycle').failedValidation();
6791
7051
  return typeof callback === "function" ? callback(error || errors, _this) : void 0;
6792
7052
  }
7053
+ _this.fire('validated');
6793
7054
  associations = _this.constructor._batman.get('associations');
6794
7055
  _this._withoutDirtyTracking(function() {
6795
7056
  var _ref4,
@@ -6798,9 +7059,10 @@
6798
7059
  return association.apply(_this);
6799
7060
  }) : void 0 : void 0;
6800
7061
  });
6801
- return _this._doStorageOperation(storageOperation, {
7062
+ payload = Batman.extend({}, options, {
6802
7063
  data: options
6803
- }, function(err, record, env) {
7064
+ });
7065
+ return _this._doStorageOperation(storageOperation, payload, function(err, record, env) {
6804
7066
  if (!err) {
6805
7067
  _this.get('dirtyKeys').clear();
6806
7068
  _this.get('_dirtiedKeys').clear();
@@ -6858,7 +7120,7 @@
6858
7120
  };
6859
7121
 
6860
7122
  Model.prototype.validate = function(callback) {
6861
- var args, count, e, errors, finishedValidation, key, validator, validators, _j, _k, _len1, _len2, _ref2;
7123
+ var args, condition, count, e, errors, finishedValidation, key, validator, validators, _j, _k, _len1, _len2, _ref2;
6862
7124
  errors = this.get('errors');
6863
7125
  errors.clear();
6864
7126
  validators = this._batman.get('validators') || [];
@@ -6878,6 +7140,20 @@
6878
7140
  };
6879
7141
  for (_j = 0, _len1 = validators.length; _j < _len1; _j++) {
6880
7142
  validator = validators[_j];
7143
+ if (validator["if"]) {
7144
+ condition = typeof validator["if"] === 'string' ? this.get(validator["if"]) : validator["if"].call(this, errors, this, key);
7145
+ if (!condition) {
7146
+ finishedValidation();
7147
+ continue;
7148
+ }
7149
+ }
7150
+ if (validator.unless) {
7151
+ condition = typeof validator.unless === 'string' ? this.get(validator.unless) : validator.unless.call(this, errors, this, key);
7152
+ if (condition) {
7153
+ finishedValidation();
7154
+ continue;
7155
+ }
7156
+ }
6881
7157
  _ref2 = validator.keys;
6882
7158
  for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
6883
7159
  key = _ref2[_k];
@@ -6921,6 +7197,11 @@
6921
7197
  }
6922
7198
  };
6923
7199
 
7200
+ Model.prototype._associationForAttribute = function(attribute) {
7201
+ var _ref2;
7202
+ return (_ref2 = this.constructor._batman.get('associations')) != null ? _ref2.get(attribute) : void 0;
7203
+ };
7204
+
6924
7205
  Model.prototype._doStorageOperation = function(operation, options, callback) {
6925
7206
  var adapter,
6926
7207
  _this = this;
@@ -6948,6 +7229,74 @@
6948
7229
  Model.prototype[functionName] = Batman.Property.wrapTrackingPrevention(Model.prototype[functionName]);
6949
7230
  }
6950
7231
 
7232
+ Model.prototype.reflectOnAllAssociations = function(associationType) {
7233
+ var associations;
7234
+ associations = this.constructor._batman.get('associations');
7235
+ if (associationType != null) {
7236
+ return associations != null ? associations.getByType(associationType) : void 0;
7237
+ } else {
7238
+ return associations != null ? associations.getAll() : void 0;
7239
+ }
7240
+ };
7241
+
7242
+ Model.prototype.reflectOnAssociation = function(associationLabel) {
7243
+ var _ref3;
7244
+ return (_ref3 = this.constructor._batman.get('associations')) != null ? _ref3.getByLabel(associationLabel) : void 0;
7245
+ };
7246
+
7247
+ Model.prototype.transaction = function() {
7248
+ return this._transaction([], []);
7249
+ };
7250
+
7251
+ Model.prototype._transaction = function(visited, stack) {
7252
+ var attributes, hasManys, index, key, label, newValues, transaction, value, _k, _len2, _ref3, _ref4;
7253
+ index = visited.indexOf(this);
7254
+ if (index !== -1) {
7255
+ return stack[index];
7256
+ }
7257
+ visited.push(this);
7258
+ stack.push(transaction = new this.constructor);
7259
+ if (hasManys = this.reflectOnAllAssociations('hasMany')) {
7260
+ hasManys = hasManys.filter(function(association) {
7261
+ return association.options.includeInTransaction;
7262
+ });
7263
+ _ref3 = hasManys.mapToProperty('label');
7264
+ for (_k = 0, _len2 = _ref3.length; _k < _len2; _k++) {
7265
+ label = _ref3[_k];
7266
+ this.get(label);
7267
+ }
7268
+ }
7269
+ attributes = this.get('attributes').toObject();
7270
+ for (key in attributes) {
7271
+ if (!__hasProp.call(attributes, key)) continue;
7272
+ value = attributes[key];
7273
+ if (value instanceof Batman.Model && !value.isTransaction) {
7274
+ attributes[key] = value._transaction(visited, stack);
7275
+ } else if (value instanceof Batman.AssociationSet && !value.isTransaction) {
7276
+ newValues = new Batman.TransactionAssociationSet(value, visited, stack);
7277
+ attributes[key] = newValues;
7278
+ } else if (Batman.typeOf(value) === 'Object') {
7279
+ Batman.developer.warn("You're passing a mutable object (" + key + ", " + value.constructor.name + ") in a " + this.constructor.name + " transaction:", value);
7280
+ }
7281
+ }
7282
+ transaction._withoutDirtyTracking(function() {
7283
+ return transaction.updateAttributes(attributes);
7284
+ });
7285
+ transaction._batman.base = this;
7286
+ _ref4 = Batman.Transaction;
7287
+ for (key in _ref4) {
7288
+ value = _ref4[key];
7289
+ transaction[key] = value;
7290
+ }
7291
+ transaction.accessor('isTransaction', function() {
7292
+ return this.isTransaction;
7293
+ });
7294
+ transaction.accessor('base', function() {
7295
+ return this.base();
7296
+ });
7297
+ return transaction;
7298
+ };
7299
+
6951
7300
  return Model;
6952
7301
 
6953
7302
  }).call(this, Batman.Object);
@@ -6964,7 +7313,7 @@
6964
7313
  var collection, _base;
6965
7314
  Batman.initializeObject(this);
6966
7315
  collection = (_base = this._batman).associations || (_base.associations = new Batman.AssociationCurator(this));
6967
- return collection.add(new Batman["" + (Batman.helpers.capitalize(k)) + "Association"](this, label, scope));
7316
+ return collection.add(new Batman["" + (Batman.helpers.titleize(k)) + "Association"](this, label, scope));
6968
7317
  };
6969
7318
  };
6970
7319
  for (_i = 0, _len = _ref.length; _i < _len; _i++) {
@@ -7131,11 +7480,8 @@
7131
7480
  loadOptions.urlContext = this.model;
7132
7481
  }
7133
7482
  return this.association.getRelatedModel().loadWithOptions(loadOptions, function(error, loadedRecords) {
7134
- if (error) {
7135
- throw error;
7136
- }
7137
7483
  if (!loadedRecords || loadedRecords.length <= 0) {
7138
- return callback(new Error("Couldn't find related record!"), void 0);
7484
+ return callback(error || new Error("Couldn't find related record!"), void 0);
7139
7485
  } else {
7140
7486
  return callback(void 0, loadedRecords[0]);
7141
7487
  }
@@ -7170,18 +7516,12 @@
7170
7516
  };
7171
7517
 
7172
7518
  BelongsToProxy.prototype.fetchFromRemote = function(callback) {
7173
- var loadOptions,
7174
- _this = this;
7519
+ var loadOptions;
7175
7520
  loadOptions = {};
7176
7521
  if (this.association.options.url) {
7177
7522
  loadOptions.recordUrl = this.association.options.url;
7178
7523
  }
7179
- return this.association.getRelatedModel().findWithOptions(this.get('foreignValue'), loadOptions, function(error, loadedRecord) {
7180
- if (error) {
7181
- throw error;
7182
- }
7183
- return callback(void 0, loadedRecord);
7184
- });
7524
+ return this.association.getRelatedModel().findWithOptions(this.get('foreignValue'), loadOptions, callback);
7185
7525
  };
7186
7526
 
7187
7527
  return BelongsToProxy;
@@ -7212,18 +7552,12 @@
7212
7552
  };
7213
7553
 
7214
7554
  PolymorphicBelongsToProxy.prototype.fetchFromRemote = function(callback) {
7215
- var loadOptions,
7216
- _this = this;
7555
+ var loadOptions;
7217
7556
  loadOptions = {};
7218
7557
  if (this.association.options.url) {
7219
7558
  loadOptions.recordUrl = this.association.options.url;
7220
7559
  }
7221
- return this.association.getRelatedModelForType(this.get('foreignTypeValue')).findWithOptions(this.get('foreignValue'), loadOptions, function(error, loadedRecord) {
7222
- if (error) {
7223
- throw error;
7224
- }
7225
- return callback(void 0, loadedRecord);
7226
- });
7560
+ return this.association.getRelatedModelForType(this.get('foreignTypeValue')).findWithOptions(this.get('foreignValue'), loadOptions, callback);
7227
7561
  };
7228
7562
 
7229
7563
  return PolymorphicBelongsToProxy;
@@ -7599,10 +7933,24 @@
7599
7933
  }
7600
7934
  }
7601
7935
 
7602
- Request.prototype.send = function() {
7603
- return Batman.developer.error("Please source a dependency file for a request implementation");
7936
+ Request.prototype.send = function(data) {
7937
+ return Batman.developer.error("You must add a platform library to implement Batman.Request (for example, batman.jquery)");
7604
7938
  };
7605
7939
 
7940
+ Request.set('pendingRequestCount', 0);
7941
+
7942
+ Request.classAccessor('requestIsPending', function() {
7943
+ return this.get('pendingRequestCount') > 0;
7944
+ });
7945
+
7946
+ Request.prototype.on('loading', function() {
7947
+ return Batman.Request.set('pendingRequestCount', Batman.Request.get('pendingRequestCount') + 1);
7948
+ });
7949
+
7950
+ Request.prototype.on('loaded', function() {
7951
+ return Batman.Request.set('pendingRequestCount', Batman.Request.get('pendingRequestCount') - 1);
7952
+ });
7953
+
7606
7954
  return Request;
7607
7955
 
7608
7956
  })(Batman.Object);
@@ -7675,7 +8023,7 @@
7675
8023
 
7676
8024
  SetObserver.prototype._manageObserversForItem = function(item, method) {
7677
8025
  var key, _i, _len, _ref;
7678
- if (item.isObservable) {
8026
+ if (item != null ? item.isObservable : void 0) {
7679
8027
  _ref = this.observedItemKeys;
7680
8028
  for (_i = 0, _len = _ref.length; _i < _len; _i++) {
7681
8029
  key = _ref[_i];
@@ -8003,16 +8351,23 @@
8003
8351
 
8004
8352
  AssociationSet.accessor('loaded', Batman.Property.defaultAccessor);
8005
8353
 
8006
- AssociationSet.prototype.load = function(callback) {
8007
- var _this = this;
8354
+ AssociationSet.prototype.load = function(options, callback) {
8355
+ var loadOptions,
8356
+ _this = this;
8357
+ loadOptions = this._getLoadOptions();
8358
+ if (!callback) {
8359
+ callback = options;
8360
+ } else {
8361
+ loadOptions.data = Batman.extend(loadOptions.data, options);
8362
+ }
8008
8363
  if (this.foreignKeyValue == null) {
8009
8364
  return callback(void 0, this);
8010
8365
  }
8011
- return this.association.getRelatedModel().loadWithOptions(this._getLoadOptions(), function(err, records) {
8366
+ return this.association.getRelatedModel().loadWithOptions(loadOptions, function(err, records, env) {
8012
8367
  if (!err) {
8013
8368
  _this.markAsLoaded();
8014
8369
  }
8015
- return callback(err, _this);
8370
+ return callback(err, _this, env);
8016
8371
  });
8017
8372
  };
8018
8373
 
@@ -8034,6 +8389,35 @@
8034
8389
  return this.fire('loaded');
8035
8390
  };
8036
8391
 
8392
+ AssociationSet.accessor('parentRecord', function() {
8393
+ var parentClass, parentPrimaryKey, parentPrimaryKeyValue, query;
8394
+ parentClass = this.get('association.model');
8395
+ parentPrimaryKey = parentClass.get('primaryKey');
8396
+ parentPrimaryKeyValue = this.get('foreignKeyValue');
8397
+ query = {};
8398
+ query[parentPrimaryKey] = parentPrimaryKeyValue;
8399
+ return parentClass.createFromJSON(query);
8400
+ });
8401
+
8402
+ AssociationSet.prototype.build = function(attrs) {
8403
+ var associatedClass, initParams, mixedAttrs, newObj, options, scope;
8404
+ if (attrs == null) {
8405
+ attrs = {};
8406
+ }
8407
+ initParams = {};
8408
+ initParams[this.get('association.foreignKey')] = this.get('foreignKeyValue');
8409
+ options = this.get('association.options');
8410
+ if (options.inverseOf != null) {
8411
+ initParams[options.inverseOf] = this.get('parentRecord');
8412
+ }
8413
+ scope = options.namespace || Batman.currentApp;
8414
+ associatedClass = scope[options.name];
8415
+ mixedAttrs = Batman.extend(attrs, initParams);
8416
+ newObj = new associatedClass(mixedAttrs);
8417
+ this.add(newObj);
8418
+ return newObj;
8419
+ };
8420
+
8037
8421
  return AssociationSet;
8038
8422
 
8039
8423
  })(Batman.SetSort);
@@ -8074,6 +8458,107 @@
8074
8458
 
8075
8459
  }).call(this);
8076
8460
 
8461
+ (function() {
8462
+ var __hasProp = {}.hasOwnProperty,
8463
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
8464
+
8465
+ Batman.SetMapping = (function(_super) {
8466
+ __extends(SetMapping, _super);
8467
+
8468
+ function SetMapping(base, key) {
8469
+ var initialValues,
8470
+ _this = this;
8471
+ this.base = base;
8472
+ this.key = key;
8473
+ initialValues = this.base.mapToProperty(this.key);
8474
+ this._counter = new this.constructor.PresenceCounter(initialValues);
8475
+ SetMapping.__super__.constructor.apply(this, initialValues);
8476
+ this._setObserver = new Batman.SetObserver(this.base);
8477
+ this._setObserver.observedItemKeys = [this.key];
8478
+ this._setObserver.observerForItemAndKey = function(item) {
8479
+ return function(newValue, oldValue) {
8480
+ return _this._handleItemModified(item, newValue, oldValue);
8481
+ };
8482
+ };
8483
+ this._setObserver.on('itemsWereAdded', this._handleItemsAdded.bind(this));
8484
+ this._setObserver.on('itemsWereRemoved', this._handleItemsRemoved.bind(this));
8485
+ this._setObserver.startObserving();
8486
+ }
8487
+
8488
+ SetMapping.prototype._handleItemsAdded = function(items, indexes) {
8489
+ var item, _i, _len, _results;
8490
+ _results = [];
8491
+ for (_i = 0, _len = items.length; _i < _len; _i++) {
8492
+ item = items[_i];
8493
+ _results.push(this._addValueInstance(item.get(this.key)));
8494
+ }
8495
+ return _results;
8496
+ };
8497
+
8498
+ SetMapping.prototype._handleItemsRemoved = function(items, indexes) {
8499
+ var item, _i, _len, _results;
8500
+ _results = [];
8501
+ for (_i = 0, _len = items.length; _i < _len; _i++) {
8502
+ item = items[_i];
8503
+ _results.push(this._removeValueInstance(item.get(this.key)));
8504
+ }
8505
+ return _results;
8506
+ };
8507
+
8508
+ SetMapping.prototype._handleItemModified = function(item, newValue, oldValue) {
8509
+ this._removeValueInstance(oldValue);
8510
+ return this._addValueInstance(newValue);
8511
+ };
8512
+
8513
+ SetMapping.prototype._addValueInstance = function(mappedValue) {
8514
+ this._counter.increment(mappedValue);
8515
+ return this.add(mappedValue);
8516
+ };
8517
+
8518
+ SetMapping.prototype._removeValueInstance = function(mappedValue) {
8519
+ var remaining;
8520
+ remaining = this._counter.decrement(mappedValue);
8521
+ if (remaining === 0) {
8522
+ return this.remove(mappedValue);
8523
+ }
8524
+ };
8525
+
8526
+ return SetMapping;
8527
+
8528
+ })(Batman.Set);
8529
+
8530
+ Batman.SetMapping.PresenceCounter = (function() {
8531
+ function PresenceCounter(initialValues) {
8532
+ var value, _i, _len;
8533
+ this._storage = new Batman.SimpleHash;
8534
+ for (_i = 0, _len = initialValues.length; _i < _len; _i++) {
8535
+ value = initialValues[_i];
8536
+ this.increment(value);
8537
+ }
8538
+ }
8539
+
8540
+ PresenceCounter.prototype.increment = function(value) {
8541
+ var count;
8542
+ count = this._storage.get(value);
8543
+ if (!count) {
8544
+ return this._storage.set(value, 1);
8545
+ } else {
8546
+ return this._storage.set(value, count + 1);
8547
+ }
8548
+ };
8549
+
8550
+ PresenceCounter.prototype.decrement = function(value) {
8551
+ var count;
8552
+ count = this._storage.get(value);
8553
+ return this._storage.set(value, count - 1);
8554
+ };
8555
+
8556
+ return PresenceCounter;
8557
+
8558
+ })();
8559
+
8560
+ }).call(this);
8561
+
8077
8562
  (function() {
8078
8563
  var __hasProp = {}.hasOwnProperty,
8079
8564
  __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
@@ -8944,6 +9429,9 @@
8944
9429
  };
8945
9430
 
8946
9431
  RouteMapBuilder.prototype.root = function(signature, options) {
9432
+ if (options == null) {
9433
+ options = {};
9434
+ }
8947
9435
  return this.route('/', signature, options);
8948
9436
  };
8949
9437
 
@@ -9018,7 +9506,10 @@
9018
9506
  klass = options.callback ? Batman.CallbackActionRoute : Batman.ControllerActionRoute;
9019
9507
  options.app = this.app;
9020
9508
  route = new klass(path, options);
9021
- return this.routeMap.addRoute(name, route);
9509
+ this.routeMap.addRoute(name, route);
9510
+ if (name === '') {
9511
+ return this.routeMap.addRoute('root', route);
9512
+ }
9022
9513
  };
9023
9514
 
9024
9515
  RouteMapBuilder.prototype._nameFromPath = function(path) {
@@ -9248,9 +9739,10 @@
9248
9739
  Association.prototype.isPolymorphic = false;
9249
9740
 
9250
9741
  Association.prototype.defaultOptions = {
9251
- saveInline: true,
9742
+ saveInline: false,
9252
9743
  autoload: true,
9253
- nestUrl: false
9744
+ nestUrl: false,
9745
+ includeInTransaction: true
9254
9746
  };
9255
9747
 
9256
9748
  function Association(model, label, options) {
@@ -9626,7 +10118,7 @@
9626
10118
  }
9627
10119
  Batman.developer["do"](function() {
9628
10120
  if ((Batman.currentApp != null) && !relatedModel) {
9629
- return Batman.developer.warn("Related model " + type + " for polymorphic association not found.");
10121
+ return Batman.developer.warn("Related model " + type + " for hasMany polymorphic association " + this.label + " not found.");
9630
10122
  }
9631
10123
  });
9632
10124
  return relatedModel;
@@ -9729,21 +10221,13 @@
9729
10221
  SingularAssociation.prototype.isSingular = true;
9730
10222
 
9731
10223
  SingularAssociation.prototype.getAccessor = function(association, model, label) {
9732
- var proxy, record, recordInAttributes,
9733
- _this = this;
10224
+ var proxy, record, recordInAttributes;
9734
10225
  if (recordInAttributes = association.getFromAttributes(this)) {
9735
10226
  return recordInAttributes;
9736
10227
  }
9737
10228
  if (association.getRelatedModel()) {
9738
10229
  proxy = this.associationProxy(association);
9739
10230
  record = false;
9740
- if (proxy._loadSetter == null) {
9741
- proxy._loadSetter = proxy.once('loaded', function(child) {
9742
- return _this._withoutDirtyTracking(function() {
9743
- return this.set(association.label, child);
9744
- });
9745
- });
9746
- }
9747
10231
  if (!Batman.Property.withoutTracking(function() {
9748
10232
  return proxy.get('loaded');
9749
10233
  })) {
@@ -9935,7 +10419,7 @@
9935
10419
  if (this.options.encodeForeignTypeKey) {
9936
10420
  this.model.encode(this.foreignTypeKey);
9937
10421
  }
9938
- this.typeIndicies = {};
10422
+ this.typeIndices = {};
9939
10423
  }
9940
10424
 
9941
10425
  PolymorphicBelongsToAssociation.prototype.getRelatedModel = false;
@@ -9990,7 +10474,7 @@
9990
10474
  }
9991
10475
  Batman.developer["do"](function() {
9992
10476
  if ((Batman.currentApp != null) && !relatedModel) {
9993
- return Batman.developer.warn("Related model " + type + " for polymorphic association not found.");
10477
+ return Batman.developer.warn("Related model " + type + " for belongsTo polymorphic association " + this.label + " not found.");
9994
10478
  }
9995
10479
  });
9996
10480
  return relatedModel;
@@ -9998,8 +10482,8 @@
9998
10482
 
9999
10483
  PolymorphicBelongsToAssociation.prototype.setIndexForType = function(type) {
10000
10484
  var _base;
10001
- (_base = this.typeIndicies)[type] || (_base[type] = new Batman.PolymorphicUniqueAssociationSetIndex(this, type, this.primaryKey));
10002
- return this.typeIndicies[type];
10485
+ (_base = this.typeIndices)[type] || (_base[type] = new Batman.PolymorphicUniqueAssociationSetIndex(this, type, this.primaryKey));
10486
+ return this.typeIndices[type];
10003
10487
  };
10004
10488
 
10005
10489
  PolymorphicBelongsToAssociation.prototype.inverseForType = function(type) {
@@ -10049,9 +10533,193 @@
10049
10533
  }).call(this);
10050
10534
 
10051
10535
  (function() {
10052
- var __hasProp = {}.hasOwnProperty,
10053
- __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
10054
- __slice = [].slice;
10536
+ var __hasProp = {}.hasOwnProperty;
10537
+
10538
+ Batman.Transaction = {
10539
+ isTransaction: true,
10540
+ base: function() {
10541
+ return this._batman.base;
10542
+ },
10543
+ applyChanges: function(visited) {
10544
+ var attributes, base, key, updatedAssociationSet, value, _ref;
10545
+ if (visited == null) {
10546
+ visited = [];
10547
+ }
10548
+ if (visited.indexOf(this) !== -1) {
10549
+ return this.base();
10550
+ }
10551
+ visited.push(this);
10552
+ attributes = this.get('attributes').toObject();
10553
+ for (key in attributes) {
10554
+ if (!__hasProp.call(attributes, key)) continue;
10555
+ value = attributes[key];
10556
+ if (value instanceof Batman.Model && value.isTransaction) {
10557
+ value.applyChanges(visited);
10558
+ delete attributes[key];
10559
+ } else if (value instanceof Batman.TransactionAssociationSet) {
10560
+ updatedAssociationSet = value.applyChanges(visited);
10561
+ attributes[key] = updatedAssociationSet;
10562
+ }
10563
+ }
10564
+ base = this.base();
10565
+ base.mixin(attributes);
10566
+ return (_ref = typeof base.applyChanges === "function" ? base.applyChanges() : void 0) != null ? _ref : base;
10567
+ },
10568
+ save: function(options, callback) {
10569
+ var finish, validated, _ref,
10570
+ _this = this;
10571
+ if (!callback) {
10572
+ _ref = [{}, options], options = _ref[0], callback = _ref[1];
10573
+ }
10574
+ this.once('validated', validated = function() {
10575
+ return _this.applyChanges();
10576
+ });
10577
+ finish = function() {
10578
+ _this.off('validated', validated);
10579
+ return typeof callback === "function" ? callback.apply(null, arguments) : void 0;
10580
+ };
10581
+ return this.constructor.prototype.save.call(this, options, function(err, result) {
10582
+ if (!err) {
10583
+ result = _this.base();
10584
+ result.get('dirtyKeys').clear();
10585
+ result.get('_dirtiedKeys').clear();
10586
+ result.get('lifecycle').startTransition('save');
10587
+ result.get('lifecycle').startTransition('saved');
10588
+ }
10589
+ return finish(err, result);
10590
+ });
10591
+ }
10592
+ };
10593
+
10594
+ }).call(this);
10595
+
10596
+ (function() {
10597
+ var __hasProp = {}.hasOwnProperty,
10598
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
10599
+ __slice = [].slice;
10600
+
10601
+ Batman.TransactionAssociationSet = (function(_super) {
10602
+ __extends(TransactionAssociationSet, _super);
10603
+
10604
+ TransactionAssociationSet.prototype.isTransaction = true;
10605
+
10606
+ function TransactionAssociationSet(associationSet, visited, stack) {
10607
+ this.set('associationSet', associationSet);
10608
+ this._loader = this._addFromAssociationSet.bind(this);
10609
+ associationSet.on('itemsWereAdded', this._loader);
10610
+ this._visited = visited;
10611
+ this._stack = stack;
10612
+ this._storage = [];
10613
+ this._originalStorage = [];
10614
+ this._removedStorage = [];
10615
+ this.add.apply(this, associationSet.toArray());
10616
+ }
10617
+
10618
+ TransactionAssociationSet.delegate('association', 'foreignKeyValue', {
10619
+ to: 'associationSet'
10620
+ });
10621
+
10622
+ TransactionAssociationSet.prototype._addFromAssociationSet = function(items, indexes) {
10623
+ return this.add.apply(this, items);
10624
+ };
10625
+
10626
+ TransactionAssociationSet.prototype.add = TransactionAssociationSet.mutation(function() {
10627
+ var addedTransactions, item, items, originalIndex, removedIndex, transactionItem, _i, _len;
10628
+ items = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
10629
+ addedTransactions = [];
10630
+ for (_i = 0, _len = items.length; _i < _len; _i++) {
10631
+ item = items[_i];
10632
+ if (!(item instanceof Batman.Model && !item.isTransaction)) {
10633
+ Batman.developer.warn("You tried to add a " + item.constructor.name + " to a TransactionAssociationSet (" + (this.get('association.label')) + ")", item);
10634
+ continue;
10635
+ }
10636
+ transactionItem = item._transaction(this._visited, this._stack);
10637
+ this._storage.push(transactionItem);
10638
+ addedTransactions.push(transactionItem);
10639
+ originalIndex = this._originalStorage.indexOf(item);
10640
+ if (originalIndex === -1) {
10641
+ this._originalStorage.push(item);
10642
+ }
10643
+ removedIndex = this._removedStorage.indexOf(item);
10644
+ if (removedIndex > -1) {
10645
+ this._removedStorage.splice(removedIndex, 1);
10646
+ }
10647
+ }
10648
+ this.length = this._storage.length;
10649
+ if (addedTransactions.length) {
10650
+ this.fire('itemsWereAdded', addedTransactions);
10651
+ }
10652
+ return addedTransactions;
10653
+ });
10654
+
10655
+ TransactionAssociationSet.prototype.remove = TransactionAssociationSet.mutation(function() {
10656
+ var item, originalIndex, removedTransactions, transactionIndex, transactionItem, transactions, _i, _len;
10657
+ transactions = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
10658
+ removedTransactions = [];
10659
+ for (_i = 0, _len = transactions.length; _i < _len; _i++) {
10660
+ transactionItem = transactions[_i];
10661
+ if (!transactionItem.isTransaction) {
10662
+ throw "Tried to remove real item from transaction set: " + (t.toJSON());
10663
+ }
10664
+ transactionIndex = this._storage.indexOf(transactionItem);
10665
+ if (transactionIndex > -1) {
10666
+ this._storage.splice(transactionIndex, 1);
10667
+ removedTransactions.push(transactionItem);
10668
+ }
10669
+ item = transactionItem.base();
10670
+ originalIndex = this._originalStorage.indexOf(item);
10671
+ if (originalIndex > -1) {
10672
+ this._removedStorage.push(item);
10673
+ this._originalStorage.splice(originalIndex, 1);
10674
+ }
10675
+ }
10676
+ this.length = this._storage.length;
10677
+ if (removedTransactions.length) {
10678
+ this.fire('itemsWereRemoved', removedTransactions);
10679
+ }
10680
+ return removedTransactions;
10681
+ });
10682
+
10683
+ TransactionAssociationSet.prototype.applyChanges = function(visited) {
10684
+ var originals, target, transactionItem, _i, _len, _ref;
10685
+ _ref = this._storage;
10686
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
10687
+ transactionItem = _ref[_i];
10688
+ transactionItem.applyChanges(visited);
10689
+ }
10690
+ originals = (function(func, args, ctor) {
10691
+ ctor.prototype = func.prototype;
10692
+ var child = new ctor, result = func.apply(child, args);
10693
+ return Object(result) === result ? result : child;
10694
+ })(Batman.Set, this._originalStorage, function(){});
10695
+ target = this.get('associationSet');
10696
+ target.off('itemsWereAdded', this._loader);
10697
+ target.replace(originals);
10698
+ target.set('removedItems', (function(func, args, ctor) {
10699
+ ctor.prototype = func.prototype;
10700
+ var child = new ctor, result = func.apply(child, args);
10701
+ return Object(result) === result ? result : child;
10702
+ })(Batman.Set, this._removedStorage, function(){}));
10703
+ return target;
10704
+ };
10705
+
10706
+ TransactionAssociationSet.accessor('length', function() {
10707
+ this.registerAsMutableSource();
10708
+ return this.length;
10709
+ });
10710
+
10711
+ TransactionAssociationSet.prototype.build = Batman.AssociationSet.prototype.build;
10712
+
10713
+ return TransactionAssociationSet;
10714
+
10715
+ })(Batman.Set);
10716
+
10717
+ }).call(this);
10718
+
10719
+ (function() {
10720
+ var __hasProp = {}.hasOwnProperty,
10721
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
10722
+ __slice = [].slice;
10055
10723
 
10056
10724
  Batman.Validator = (function(_super) {
10057
10725
  __extends(Validator, _super);
@@ -10102,12 +10770,20 @@
10102
10770
  Validator.__super__.constructor.apply(this, mixins);
10103
10771
  }
10104
10772
 
10105
- Validator.prototype.validate = function(record) {
10106
- return Batman.developer.error("You must override validate in Batman.Validator subclasses.");
10773
+ Validator.prototype.validateEach = function(record) {
10774
+ return Batman.developer.error("You must override validateEach in Batman.Validator subclasses.");
10107
10775
  };
10108
10776
 
10109
- Validator.prototype.format = function(key, messageKey, interpolations) {
10110
- return Batman.t("errors.messages." + messageKey, interpolations);
10777
+ Validator.prototype.format = function(attr, messageKey, interpolations, record) {
10778
+ if (this.options.message) {
10779
+ if (typeof this.options.message === 'function') {
10780
+ return this.options.message.call(record, attr, messageKey);
10781
+ } else {
10782
+ return this.options.message;
10783
+ }
10784
+ } else {
10785
+ return Batman.t("errors.messages." + messageKey, interpolations);
10786
+ }
10111
10787
  };
10112
10788
 
10113
10789
  Validator.prototype.handleBlank = function(value) {
@@ -10142,10 +10818,13 @@
10142
10818
  equal_to: "must be equal to %{count}",
10143
10819
  less_than: "must be less than %{count}",
10144
10820
  less_than_or_equal_to: "must be less than or equal to %{count}",
10821
+ not_an_integer: "must be an integer",
10145
10822
  not_matching: "is not valid",
10146
10823
  invalid_association: "is not valid",
10147
10824
  not_included_in_list: "is not included in the list",
10148
- included_in_list: "is included in the list"
10825
+ included_in_list: "is included in the list",
10826
+ not_an_email: "is not a valid email address",
10827
+ confirmation_does_not_match: 'and confirmation do not match'
10149
10828
  }
10150
10829
  }
10151
10830
  });
@@ -10176,7 +10855,7 @@
10176
10855
  return callback();
10177
10856
  }
10178
10857
  if ((value == null) || value === '' || !this.regexp.test(value)) {
10179
- errors.add(key, this.format(key, 'not_matching'));
10858
+ errors.add(key, this.format(key, 'not_matching', {}, record));
10180
10859
  }
10181
10860
  return callback();
10182
10861
  };
@@ -10208,7 +10887,7 @@
10208
10887
  var value;
10209
10888
  value = record.get(key);
10210
10889
  if (!this.isPresent(value)) {
10211
- errors.add(key, this.format(key, 'blank'));
10890
+ errors.add(key, this.format(key, 'blank', {}, record));
10212
10891
  }
10213
10892
  return callback();
10214
10893
  };
@@ -10238,7 +10917,7 @@
10238
10917
  return _ref;
10239
10918
  }
10240
10919
 
10241
- NumericValidator.triggers('numeric', 'greaterThan', 'greaterThanOrEqualTo', 'equalTo', 'lessThan', 'lessThanOrEqualTo');
10920
+ NumericValidator.triggers('numeric', 'greaterThan', 'greaterThanOrEqualTo', 'equalTo', 'lessThan', 'lessThanOrEqualTo', 'onlyInteger');
10242
10921
 
10243
10922
  NumericValidator.options('allowBlank');
10244
10923
 
@@ -10250,32 +10929,34 @@
10250
10929
  return callback();
10251
10930
  }
10252
10931
  if ((value == null) || !(this.isNumeric(value) || this.canCoerceToNumeric(value))) {
10253
- errors.add(key, this.format(key, 'not_numeric'));
10932
+ errors.add(key, this.format(key, 'not_numeric', {}, record));
10933
+ } else if (options.onlyInteger && !this.isInteger(value)) {
10934
+ errors.add(key, this.format(key, 'not_an_integer', {}, record));
10254
10935
  } else {
10255
10936
  if ((options.greaterThan != null) && value <= options.greaterThan) {
10256
10937
  errors.add(key, this.format(key, 'greater_than', {
10257
10938
  count: options.greaterThan
10258
- }));
10939
+ }, record));
10259
10940
  }
10260
10941
  if ((options.greaterThanOrEqualTo != null) && value < options.greaterThanOrEqualTo) {
10261
10942
  errors.add(key, this.format(key, 'greater_than_or_equal_to', {
10262
10943
  count: options.greaterThanOrEqualTo
10263
- }));
10944
+ }, record));
10264
10945
  }
10265
10946
  if ((options.equalTo != null) && value !== options.equalTo) {
10266
10947
  errors.add(key, this.format(key, 'equal_to', {
10267
10948
  count: options.equalTo
10268
- }));
10949
+ }, record));
10269
10950
  }
10270
10951
  if ((options.lessThan != null) && value >= options.lessThan) {
10271
10952
  errors.add(key, this.format(key, 'less_than', {
10272
10953
  count: options.lessThan
10273
- }));
10954
+ }, record));
10274
10955
  }
10275
10956
  if ((options.lessThanOrEqualTo != null) && value > options.lessThanOrEqualTo) {
10276
10957
  errors.add(key, this.format(key, 'less_than_or_equal_to', {
10277
10958
  count: options.lessThanOrEqualTo
10278
- }));
10959
+ }, record));
10279
10960
  }
10280
10961
  }
10281
10962
  return callback();
@@ -10285,6 +10966,10 @@
10285
10966
  return !isNaN(parseFloat(value)) && isFinite(value);
10286
10967
  };
10287
10968
 
10969
+ NumericValidator.prototype.isInteger = function(value) {
10970
+ return parseFloat(value) === (value | 0);
10971
+ };
10972
+
10288
10973
  NumericValidator.prototype.canCoerceToNumeric = function(value) {
10289
10974
  return (value - 0) == value && value.length > 0;
10290
10975
  };
@@ -10323,7 +11008,7 @@
10323
11008
  var options, value;
10324
11009
  options = this.options;
10325
11010
  value = record.get(key);
10326
- if (value !== '' && this.handleBlank(value)) {
11011
+ if (this.handleBlank(value)) {
10327
11012
  return callback();
10328
11013
  }
10329
11014
  if (value == null) {
@@ -10332,17 +11017,17 @@
10332
11017
  if (options.minLength && value.length < options.minLength) {
10333
11018
  errors.add(key, this.format(key, 'too_short', {
10334
11019
  count: options.minLength
10335
- }));
11020
+ }, record));
10336
11021
  }
10337
11022
  if (options.maxLength && value.length > options.maxLength) {
10338
11023
  errors.add(key, this.format(key, 'too_long', {
10339
11024
  count: options.maxLength
10340
- }));
11025
+ }, record));
10341
11026
  }
10342
11027
  if (options.length && value.length !== options.length) {
10343
11028
  errors.add(key, this.format(key, 'wrong_length', {
10344
11029
  count: options.length
10345
- }));
11030
+ }, record));
10346
11031
  }
10347
11032
  return callback();
10348
11033
  };
@@ -10364,14 +11049,18 @@
10364
11049
 
10365
11050
  InclusionValidator.triggers('inclusion');
10366
11051
 
11052
+ InclusionValidator.options('allowBlank');
11053
+
10367
11054
  function InclusionValidator(options) {
10368
11055
  this.acceptableValues = options.inclusion["in"];
10369
11056
  InclusionValidator.__super__.constructor.apply(this, arguments);
10370
11057
  }
10371
11058
 
10372
11059
  InclusionValidator.prototype.validateEach = function(errors, record, key, callback) {
10373
- if (this.acceptableValues.indexOf(record.get(key)) === -1) {
10374
- errors.add(key, this.format(key, 'not_included_in_list'));
11060
+ var value;
11061
+ value = record.get(key);
11062
+ if (!this.handleBlank(value) && this.acceptableValues.indexOf(value) === -1) {
11063
+ errors.add(key, this.format(key, 'not_included_in_list', {}, record));
10375
11064
  }
10376
11065
  return callback();
10377
11066
  };
@@ -10400,7 +11089,7 @@
10400
11089
 
10401
11090
  ExclusionValidator.prototype.validateEach = function(errors, record, key, callback) {
10402
11091
  if (this.unacceptableValues.indexOf(record.get(key)) >= 0) {
10403
- errors.add(key, this.format(key, 'included_in_list'));
11092
+ errors.add(key, this.format(key, 'included_in_list', {}, record));
10404
11093
  }
10405
11094
  return callback();
10406
11095
  };
@@ -10413,6 +11102,82 @@
10413
11102
 
10414
11103
  }).call(this);
10415
11104
 
11105
+ (function() {
11106
+ var _ref,
11107
+ __hasProp = {}.hasOwnProperty,
11108
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
11109
+
11110
+ Batman.EmailValidator = (function(_super) {
11111
+ __extends(EmailValidator, _super);
11112
+
11113
+ function EmailValidator() {
11114
+ _ref = EmailValidator.__super__.constructor.apply(this, arguments);
11115
+ return _ref;
11116
+ }
11117
+
11118
+ EmailValidator.triggers('email');
11119
+
11120
+ EmailValidator.prototype.emailRegexp = /[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9-]+(\.[a-z0-9-]+)*/;
11121
+
11122
+ EmailValidator.prototype.validateEach = function(errors, record, key, callback) {
11123
+ var value;
11124
+ value = record.get(key);
11125
+ if ((value == null) || value === '' || !this.emailRegexp.test(value)) {
11126
+ errors.add(key, this.format(key, 'not_an_email', {}, record));
11127
+ }
11128
+ return callback();
11129
+ };
11130
+
11131
+ return EmailValidator;
11132
+
11133
+ })(Batman.Validator);
11134
+
11135
+ Batman.Validators.push(Batman.EmailValidator);
11136
+
11137
+ }).call(this);
11138
+
11139
+ (function() {
11140
+ var _ref,
11141
+ __hasProp = {}.hasOwnProperty,
11142
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
11143
+
11144
+ Batman.ConfirmationValidator = (function(_super) {
11145
+ __extends(ConfirmationValidator, _super);
11146
+
11147
+ function ConfirmationValidator() {
11148
+ _ref = ConfirmationValidator.__super__.constructor.apply(this, arguments);
11149
+ return _ref;
11150
+ }
11151
+
11152
+ ConfirmationValidator.triggers('confirmation');
11153
+
11154
+ ConfirmationValidator.prototype.validateEach = function(errors, record, key, callback) {
11155
+ var confirmation_key, confirmation_value, options, value;
11156
+ options = this.options;
11157
+ if (!options.confirmation) {
11158
+ return;
11159
+ }
11160
+ if (Batman.typeOf(this.options.confirmation) === "String") {
11161
+ confirmation_key = this.options.confirmation;
11162
+ } else {
11163
+ confirmation_key = key + "_confirmation";
11164
+ }
11165
+ value = record.get(key);
11166
+ confirmation_value = record.get(confirmation_key);
11167
+ if (value !== confirmation_value) {
11168
+ errors.add(key, this.format(key, 'confirmation_does_not_match', {}, record));
11169
+ }
11170
+ return callback();
11171
+ };
11172
+
11173
+ return ConfirmationValidator;
11174
+
11175
+ })(Batman.Validator);
11176
+
11177
+ Batman.Validators.push(Batman.ConfirmationValidator);
11178
+
11179
+ }).call(this);
11180
+
10416
11181
  (function() {
10417
11182
  var _ref,
10418
11183
  __hasProp = {}.hasOwnProperty,
@@ -10439,7 +11204,7 @@
10439
11204
  count = 1;
10440
11205
  childFinished = function(err, childErrors) {
10441
11206
  if (childErrors.length > 0) {
10442
- errors.add(key, _this.format(key, 'invalid_association'));
11207
+ errors.add(key, _this.format(key, 'invalid_association', {}, record));
10443
11208
  }
10444
11209
  if (--count === 0) {
10445
11210
  return callback();
@@ -10468,6 +11233,63 @@
10468
11233
 
10469
11234
  }).call(this);
10470
11235
 
11236
+ (function() {
11237
+ var _ref,
11238
+ __hasProp = {}.hasOwnProperty,
11239
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
11240
+
11241
+ Batman.AssociatedFieldValidator = (function(_super) {
11242
+ __extends(AssociatedFieldValidator, _super);
11243
+
11244
+ function AssociatedFieldValidator() {
11245
+ _ref = AssociatedFieldValidator.__super__.constructor.apply(this, arguments);
11246
+ return _ref;
11247
+ }
11248
+
11249
+ AssociatedFieldValidator.triggers('associatedFields');
11250
+
11251
+ AssociatedFieldValidator.prototype.validateEach = function(errors, record, key, callback) {
11252
+ var childFinished, count, value,
11253
+ _this = this;
11254
+ value = record.get(key);
11255
+ if (value != null) {
11256
+ if (value instanceof Batman.AssociationProxy) {
11257
+ value = typeof value.get === "function" ? value.get('target') : void 0;
11258
+ }
11259
+ count = 1;
11260
+ childFinished = function(err, childErrors) {
11261
+ if (childErrors != null) {
11262
+ childErrors.forEach(function(validationError) {
11263
+ return errors.add(validationError.get('attribute'), validationError.get('message'));
11264
+ });
11265
+ }
11266
+ if (--count === 0) {
11267
+ return callback();
11268
+ }
11269
+ };
11270
+ if ((value != null ? value.forEach : void 0) != null) {
11271
+ value.forEach(function(record) {
11272
+ count += 1;
11273
+ return record.validate(childFinished);
11274
+ });
11275
+ } else if ((value != null ? value.validate : void 0) != null) {
11276
+ count += 1;
11277
+ value.validate(childFinished);
11278
+ }
11279
+ return childFinished(null, []);
11280
+ } else {
11281
+ return callback();
11282
+ }
11283
+ };
11284
+
11285
+ return AssociatedFieldValidator;
11286
+
11287
+ })(Batman.Validator);
11288
+
11289
+ Batman.Validators.push(Batman.AssociatedFieldValidator);
11290
+
11291
+ }).call(this);
11292
+
10471
11293
  (function() {
10472
11294
  var __hasProp = {}.hasOwnProperty,
10473
11295
  __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
@@ -10629,6 +11451,14 @@
10629
11451
  return this._batman.set('options', keys);
10630
11452
  };
10631
11453
 
11454
+ View.filter = function(key, filter) {
11455
+ var filters;
11456
+ Batman.initializeObject(this.prototype);
11457
+ filters = this.prototype._batman.filters || {};
11458
+ filters[key] = filter;
11459
+ return this.prototype._batman.set('filters', filters);
11460
+ };
11461
+
10632
11462
  View.viewForNode = function(node, climbTree) {
10633
11463
  var view;
10634
11464
  if (climbTree == null) {
@@ -10702,16 +11532,19 @@
10702
11532
  };
10703
11533
 
10704
11534
  View.prototype._addSubview = function(subview) {
10705
- var subviewController, yieldName, yieldObject;
11535
+ var filters, subviewController, yieldName, yieldObject;
10706
11536
  subviewController = subview.controller;
10707
11537
  subview.removeFromSuperview();
10708
11538
  subview.set('controller', subviewController || this.controller);
10709
11539
  subview.set('superview', this);
10710
- subview.fire('viewDidMoveToSuperview');
11540
+ subview.fireAndCall('viewDidMoveToSuperview');
10711
11541
  if ((yieldName = subview.contentFor) && !subview.parentNode) {
10712
11542
  yieldObject = Batman.DOM.Yield.withName(yieldName);
10713
11543
  yieldObject.set('contentView', subview);
10714
11544
  }
11545
+ if (filters = this._batman.get('filters')) {
11546
+ subview._batman.set('filters', Batman.mixin({}, filters, subview._batman.get('filters')));
11547
+ }
10715
11548
  this.get('node');
10716
11549
  subview.get('node');
10717
11550
  this.observe('node', subview._nodesChanged);
@@ -10725,7 +11558,7 @@
10725
11558
  if (!this.superview) {
10726
11559
  return;
10727
11560
  }
10728
- this.fire('viewWillRemoveFromSuperview');
11561
+ this.fireAndCall('viewWillRemoveFromSuperview');
10729
11562
  this.forget('node', this._nodesChanged);
10730
11563
  this.forget('parentNode', this._nodesChanged);
10731
11564
  this.superview.forget('node', this._nodesChanged);
@@ -10806,10 +11639,7 @@
10806
11639
  if (value != null) {
10807
11640
  this.set(eventName, value);
10808
11641
  } else {
10809
- this.fire(eventName);
10810
- if (typeof this[eventName] === "function") {
10811
- this[eventName]();
10812
- }
11642
+ this.fireAndCall(eventName);
10813
11643
  }
10814
11644
  _ref = this.subviews._storage;
10815
11645
  _results = [];
@@ -10874,7 +11704,7 @@
10874
11704
  if (node) {
10875
11705
  this.set('node', node);
10876
11706
  }
10877
- this.fire('viewDidLoad');
11707
+ this.fireAndCall('viewDidLoad');
10878
11708
  }
10879
11709
  return this.node;
10880
11710
  },
@@ -10910,8 +11740,7 @@
10910
11740
  }
10911
11741
  new Batman.BindingParser(this);
10912
11742
  this.set('isBound', true);
10913
- this.fire('ready');
10914
- return typeof this.ready === "function" ? this.ready() : void 0;
11743
+ return this.fireAndCall('ready');
10915
11744
  };
10916
11745
 
10917
11746
  View.prototype.destroyBindings = function() {
@@ -10945,11 +11774,14 @@
10945
11774
  Batman.developer.warn("Tried to die() a view more than once.");
10946
11775
  return;
10947
11776
  }
10948
- this.fire('destroy');
11777
+ this.fireAndCall('destroy');
11778
+ this.destroyBindings();
11779
+ this.destroySubviews();
10949
11780
  if (this.node) {
10950
11781
  this.wasInDOM = Batman.DOM.containsNode(this.node);
10951
11782
  Batman.DOM.destroyNode(this.node);
10952
11783
  }
11784
+ this.removeFromSuperview();
10953
11785
  this.forget();
10954
11786
  if ((_ref = this._batman.properties) != null) {
10955
11787
  _ref.forEach(function(key, property) {
@@ -10963,9 +11795,6 @@
10963
11795
  event.clearHandlers();
10964
11796
  }
10965
11797
  }
10966
- this.destroyBindings();
10967
- this.destroySubviews();
10968
- this.removeFromSuperview();
10969
11798
  this.node = null;
10970
11799
  this.parentNode = null;
10971
11800
  this.subviews = null;
@@ -10987,22 +11816,19 @@
10987
11816
  };
10988
11817
 
10989
11818
  View.prototype.targetForKeypath = function(keypath, forceTarget) {
10990
- var controller, lookupNode, nearestNonBackingView, proxiedObject;
10991
- proxiedObject = this.get('proxiedObject');
10992
- lookupNode = proxiedObject || this;
11819
+ var controller, lookupNode, nearestNonBackingView, target;
11820
+ lookupNode = this;
10993
11821
  while (lookupNode) {
10994
- if (typeof Batman.get(lookupNode, keypath) !== 'undefined') {
10995
- return lookupNode;
11822
+ if (target = this._testTargetForKeypath(lookupNode, keypath)) {
11823
+ return target;
10996
11824
  }
10997
11825
  if (forceTarget && !nearestNonBackingView && !lookupNode.isBackingView) {
10998
- nearestNonBackingView = lookupNode;
11826
+ nearestNonBackingView = Batman.get(lookupNode, 'proxiedObject') || lookupNode;
10999
11827
  }
11000
11828
  if (!controller && lookupNode.isView && lookupNode.controller) {
11001
11829
  controller = lookupNode.controller;
11002
11830
  }
11003
- if (proxiedObject && lookupNode === proxiedObject) {
11004
- lookupNode = this;
11005
- } else if (lookupNode.isView && lookupNode.superview) {
11831
+ if (lookupNode.isView && lookupNode.superview) {
11006
11832
  lookupNode = lookupNode.superview;
11007
11833
  } else if (controller) {
11008
11834
  lookupNode = controller;
@@ -11022,6 +11848,18 @@
11022
11848
  return nearestNonBackingView;
11023
11849
  };
11024
11850
 
11851
+ View.prototype._testTargetForKeypath = function(object, keypath) {
11852
+ var proxiedObject;
11853
+ if (proxiedObject = Batman.get(object, 'proxiedObject')) {
11854
+ if (typeof Batman.get(proxiedObject, keypath) !== 'undefined') {
11855
+ return proxiedObject;
11856
+ }
11857
+ }
11858
+ if (typeof Batman.get(object, keypath) !== 'undefined') {
11859
+ return object;
11860
+ }
11861
+ };
11862
+
11025
11863
  View.prototype.lookupKeypath = function(keypath) {
11026
11864
  var base, target;
11027
11865
  base = this.baseForKeypath(keypath);
@@ -11041,6 +11879,11 @@
11041
11879
  return (_ref = Batman.Property.forBaseAndKey(target, keypath)) != null ? _ref.setValue(value) : void 0;
11042
11880
  };
11043
11881
 
11882
+ View.prototype.fireAndCall = function(key) {
11883
+ this.fire(key);
11884
+ return typeof this[key] === "function" ? this[key]() : void 0;
11885
+ };
11886
+
11044
11887
  return View;
11045
11888
 
11046
11889
  })(Batman.Object);
@@ -11247,7 +12090,7 @@
11247
12090
  };
11248
12091
 
11249
12092
  AbstractBinding.prototype.parseFilter = function() {
11250
- var args, e, filter, filterName, filterString, filters, key, keyPath, orig, split;
12093
+ var args, e, filter, filterName, filterString, filters, key, keyPath, orig, split, _ref, _ref1;
11251
12094
  this.filterFunctions = [];
11252
12095
  this.filterArguments = [];
11253
12096
  keyPath = this.keyPath;
@@ -11275,7 +12118,7 @@
11275
12118
  }
11276
12119
  filterName = filterString.substr(0, split);
11277
12120
  args = filterString.substr(split);
11278
- if (!(filter = Batman.Filters[filterName])) {
12121
+ if (!(filter = ((_ref = this.view) != null ? (_ref1 = _ref._batman.get('filters')) != null ? _ref1[filterName] : void 0 : void 0) || Batman.Filters[filterName])) {
11279
12122
  return Batman.developer.error("Unrecognized filter '" + filterName + "' in key \"" + this.keyPath + "\"!");
11280
12123
  }
11281
12124
  this.filterFunctions.push(filter);
@@ -11433,7 +12276,7 @@
11433
12276
  if (_this.isDataChanging) {
11434
12277
  return;
11435
12278
  }
11436
- return _this.view.set(_this.keyPath, value);
12279
+ return _this.view.setKeypath(_this.keyPath, value);
11437
12280
  });
11438
12281
  }
11439
12282
 
@@ -11509,16 +12352,16 @@
11509
12352
  view = Batman.View.viewForNode(this.node, false);
11510
12353
  if (!!value === !this.invert) {
11511
12354
  if (view != null) {
11512
- view.fire('viewWillShow');
12355
+ view.fireAndCall('viewWillShow');
11513
12356
  }
11514
12357
  this.node.style.display = this.originalDisplay;
11515
- return view != null ? view.fire('viewDidShow') : void 0;
12358
+ return view != null ? view.fireAndCall('viewDidShow') : void 0;
11516
12359
  } else {
11517
12360
  if (view != null) {
11518
- view.fire('viewWillHide');
12361
+ view.fireAndCall('viewWillHide');
11519
12362
  }
11520
12363
  Batman.DOM.setStyleProperty(this.node, 'display', 'none', 'important');
11521
- return view != null ? view.fire('viewDidHide') : void 0;
12364
+ return view != null ? view.fireAndCall('viewDidHide') : void 0;
11522
12365
  }
11523
12366
  };
11524
12367
 
@@ -11692,20 +12535,13 @@
11692
12535
  }).call(this);
11693
12536
 
11694
12537
  (function() {
11695
- var _ref,
11696
- __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
12538
+ var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
11697
12539
  __hasProp = {}.hasOwnProperty,
11698
12540
  __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
11699
12541
 
11700
12542
  Batman.DOM.RouteBinding = (function(_super) {
11701
12543
  __extends(RouteBinding, _super);
11702
12544
 
11703
- function RouteBinding() {
11704
- this.routeClick = __bind(this.routeClick, this);
11705
- _ref = RouteBinding.__super__.constructor.apply(this, arguments);
11706
- return _ref;
11707
- }
11708
-
11709
12545
  RouteBinding.prototype.onAnchorTag = false;
11710
12546
 
11711
12547
  RouteBinding.prototype.onlyObserve = Batman.BindingDefinitionOnlyObserve.Data;
@@ -11714,34 +12550,44 @@
11714
12550
  return this.view.lookupKeypath('dispatcher') || Batman.App.get('current.dispatcher');
11715
12551
  });
11716
12552
 
12553
+ function RouteBinding() {
12554
+ this.routeClick = __bind(this.routeClick, this);
12555
+ var definition, paramKeypath;
12556
+ RouteBinding.__super__.constructor.apply(this, arguments);
12557
+ if (paramKeypath = this.node.getAttribute('data-route-params')) {
12558
+ definition = new Batman.DOM.ReaderBindingDefinition(this.node, paramKeypath, this.view);
12559
+ this.set('queryParams', new Batman.DOM.RouteParamsBinding(definition, this));
12560
+ }
12561
+ }
12562
+
11717
12563
  RouteBinding.prototype.bind = function() {
11718
- var _ref1;
11719
- if ((_ref1 = this.node.nodeName) === 'a' || _ref1 === 'A') {
12564
+ var _ref;
12565
+ if ((_ref = this.node.nodeName) === 'a' || _ref === 'A') {
11720
12566
  this.onAnchorTag = true;
11721
12567
  }
11722
12568
  RouteBinding.__super__.bind.apply(this, arguments);
11723
12569
  if (this.onAnchorTag && this.node.getAttribute('target')) {
11724
12570
  return;
11725
12571
  }
11726
- return Batman.DOM.events.click(this.node, this.routeClick);
12572
+ return Batman.DOM.events.primaryInteractionEvent(this.node, this.routeClick);
11727
12573
  };
11728
12574
 
11729
12575
  RouteBinding.prototype.routeClick = function(node, event) {
11730
- var params;
12576
+ var path;
11731
12577
  if (event.__batmanActionTaken) {
11732
12578
  return;
11733
12579
  }
11734
12580
  event.__batmanActionTaken = true;
11735
- params = this.pathFromValue(this.get('filteredValue'));
11736
- if (params != null) {
11737
- return Batman.redirect(params);
12581
+ path = this.generatePath(this.get('filteredValue'), this.get('queryParams.filteredValue'));
12582
+ if (path != null) {
12583
+ return Batman.redirect(path);
11738
12584
  }
11739
12585
  };
11740
12586
 
11741
- RouteBinding.prototype.dataChange = function(value) {
12587
+ RouteBinding.prototype.dataChange = function(value, node, queryParams) {
11742
12588
  var path;
11743
12589
  if (value) {
11744
- path = this.pathFromValue(value);
12590
+ path = this.generatePath(value, queryParams || this.get('queryParams.filteredValue'));
11745
12591
  }
11746
12592
  if (this.onAnchorTag) {
11747
12593
  if (path && Batman.navigator) {
@@ -11753,21 +12599,46 @@
11753
12599
  }
11754
12600
  };
11755
12601
 
11756
- RouteBinding.prototype.pathFromValue = function(value) {
11757
- var _ref1;
11758
- if (value) {
11759
- if (value.isNamedRouteQuery) {
11760
- return value.get('path');
11761
- } else {
11762
- return (_ref1 = this.get('dispatcher')) != null ? _ref1.pathFromParams(value) : void 0;
12602
+ RouteBinding.prototype.generatePath = function(value, params) {
12603
+ var path, uri, _ref;
12604
+ path = (value != null ? value.isNamedRouteQuery : void 0) ? value.get('path') : (_ref = this.get('dispatcher')) != null ? _ref.pathFromParams(value) : void 0;
12605
+ if (!params || !path) {
12606
+ return path;
12607
+ }
12608
+ if (Batman.typeOf(params) === 'Object') {
12609
+ if (params.toObject != null) {
12610
+ params = params.toObject();
11763
12611
  }
12612
+ } else {
12613
+ params = Batman.URI.paramsFromQuery(params);
11764
12614
  }
12615
+ uri = new Batman.URI(path);
12616
+ uri.queryParams = params;
12617
+ return uri.toString();
11765
12618
  };
11766
12619
 
11767
12620
  return RouteBinding;
11768
12621
 
11769
12622
  })(Batman.DOM.AbstractBinding);
11770
12623
 
12624
+ Batman.DOM.RouteParamsBinding = (function(_super) {
12625
+ __extends(RouteParamsBinding, _super);
12626
+
12627
+ RouteParamsBinding.prototype.onlyObserve = Batman.BindingDefinitionOnlyObserve.Data;
12628
+
12629
+ function RouteParamsBinding(definition, routeBinding) {
12630
+ this.routeBinding = routeBinding;
12631
+ RouteParamsBinding.__super__.constructor.call(this, definition);
12632
+ }
12633
+
12634
+ RouteParamsBinding.prototype.dataChange = function(value) {
12635
+ return this.routeBinding.dataChange(this.routeBinding.get('filteredValue'), this.node, value);
12636
+ };
12637
+
12638
+ return RouteParamsBinding;
12639
+
12640
+ })(Batman.DOM.AbstractBinding);
12641
+
11771
12642
  }).call(this);
11772
12643
 
11773
12644
  (function() {
@@ -11846,7 +12717,7 @@
11846
12717
  }).call(this);
11847
12718
 
11848
12719
  (function() {
11849
- var _ref, _ref1,
12720
+ var _ref,
11850
12721
  __hasProp = {}.hasOwnProperty,
11851
12722
  __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
11852
12723
 
@@ -11867,20 +12738,21 @@
11867
12738
  Batman.DOM.DeferredRenderBinding = (function(_super) {
11868
12739
  __extends(DeferredRenderBinding, _super);
11869
12740
 
11870
- function DeferredRenderBinding() {
11871
- _ref1 = DeferredRenderBinding.__super__.constructor.apply(this, arguments);
11872
- return _ref1;
11873
- }
11874
-
11875
12741
  DeferredRenderBinding.prototype.onlyObserve = Batman.BindingDefinitionOnlyObserve.Data;
11876
12742
 
11877
12743
  DeferredRenderBinding.prototype.backWithView = Batman.DeferredRenderView;
11878
12744
 
11879
12745
  DeferredRenderBinding.prototype.skipChildren = true;
11880
12746
 
12747
+ function DeferredRenderBinding(definition) {
12748
+ this.invert = definition.invert;
12749
+ this.attributeName = this.invert ? 'data-deferif' : 'data-renderif';
12750
+ DeferredRenderBinding.__super__.constructor.apply(this, arguments);
12751
+ }
12752
+
11881
12753
  DeferredRenderBinding.prototype.dataChange = function(value) {
11882
- if (value && !this.backingView.isBound) {
11883
- this.node.removeAttribute('data-renderif');
12754
+ if (!!value === !this.invert && !this.backingView.isBound) {
12755
+ this.node.removeAttribute(this.attributeName);
11884
12756
  return this.backingView.initializeBindings();
11885
12757
  }
11886
12758
  };
@@ -11937,22 +12809,79 @@
11937
12809
  var __hasProp = {}.hasOwnProperty,
11938
12810
  __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
11939
12811
 
11940
- Batman.DOM.EventBinding = (function(_super) {
11941
- __extends(EventBinding, _super);
12812
+ Batman.Tracking = {
12813
+ loadTracker: function() {
12814
+ if (Batman.Tracking.tracker) {
12815
+ return Batman.Tracking.tracker;
12816
+ }
12817
+ Batman.Tracking.tracker = Batman.currentApp.EventTracker ? new Batman.currentApp.EventTracker : (Batman.developer.warn("Define " + Batman.currentApp.name + ".EventTracker to use data-track"), {
12818
+ track: function() {}
12819
+ });
12820
+ return Batman.Tracking.tracker;
12821
+ },
12822
+ trackEvent: function(type, data, node) {
12823
+ return Batman.Tracking.loadTracker().track(type, data, node);
12824
+ }
12825
+ };
11942
12826
 
11943
- EventBinding.prototype.onlyObserve = Batman.BindingDefinitionOnlyObserve.Data;
12827
+ Batman.DOM.ClickTrackingBinding = (function(_super) {
12828
+ __extends(ClickTrackingBinding, _super);
11944
12829
 
11945
- EventBinding.prototype.bindImmediately = false;
12830
+ ClickTrackingBinding.prototype.onlyObserve = Batman.BindingDefinitionOnlyObserve.None;
11946
12831
 
11947
- function EventBinding() {
11948
- var attacher, callback,
12832
+ ClickTrackingBinding.prototype.bindImmediately = false;
12833
+
12834
+ function ClickTrackingBinding() {
12835
+ var callback,
11949
12836
  _this = this;
11950
- EventBinding.__super__.constructor.apply(this, arguments);
12837
+ ClickTrackingBinding.__super__.constructor.apply(this, arguments);
11951
12838
  callback = function() {
11952
- var func, target;
11953
- func = _this.get('filteredValue');
11954
- target = _this.view.targetForKeypath(_this.functionPath || _this.unfilteredKey);
11955
- if (target && _this.functionPath) {
12839
+ return Batman.Tracking.trackEvent('click', _this.get('filteredValue'), _this.node);
12840
+ };
12841
+ Batman.DOM.events.click(this.node, callback, this.view, 'click', false);
12842
+ this.bind();
12843
+ }
12844
+
12845
+ return ClickTrackingBinding;
12846
+
12847
+ })(Batman.DOM.AbstractAttributeBinding);
12848
+
12849
+ Batman.DOM.ViewTrackingBinding = (function(_super) {
12850
+ __extends(ViewTrackingBinding, _super);
12851
+
12852
+ ViewTrackingBinding.prototype.onlyObserve = Batman.BindingDefinitionOnlyObserve.None;
12853
+
12854
+ function ViewTrackingBinding() {
12855
+ ViewTrackingBinding.__super__.constructor.apply(this, arguments);
12856
+ Batman.Tracking.trackEvent('view', this.get('filteredValue'), this.node);
12857
+ }
12858
+
12859
+ return ViewTrackingBinding;
12860
+
12861
+ })(Batman.DOM.AbstractAttributeBinding);
12862
+
12863
+ }).call(this);
12864
+
12865
+ (function() {
12866
+ var __hasProp = {}.hasOwnProperty,
12867
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
12868
+
12869
+ Batman.DOM.EventBinding = (function(_super) {
12870
+ __extends(EventBinding, _super);
12871
+
12872
+ EventBinding.prototype.onlyObserve = Batman.BindingDefinitionOnlyObserve.None;
12873
+
12874
+ EventBinding.prototype.bindImmediately = false;
12875
+
12876
+ function EventBinding() {
12877
+ var attacher, callback,
12878
+ _this = this;
12879
+ EventBinding.__super__.constructor.apply(this, arguments);
12880
+ callback = function() {
12881
+ var func, target;
12882
+ func = _this.get('filteredValue');
12883
+ target = _this.view.targetForKeypath(_this.functionPath || _this.unfilteredKey);
12884
+ if (target && _this.functionPath) {
11956
12885
  target = Batman.get(target, _this.functionPath);
11957
12886
  }
11958
12887
  return func != null ? func.apply(target, arguments) : void 0;
@@ -11962,7 +12891,7 @@
11962
12891
  } else {
11963
12892
  Batman.DOM.events.other(this.node, this.attributeName, callback, this.view);
11964
12893
  }
11965
- this.view.bindings.push(this);
12894
+ this.bind();
11966
12895
  }
11967
12896
 
11968
12897
  EventBinding.prototype._unfilteredValue = function(key) {
@@ -11999,20 +12928,31 @@
11999
12928
 
12000
12929
  ContextBinding.prototype.bindingName = 'context';
12001
12930
 
12002
- function ContextBinding() {
12003
- var contextAttribute;
12931
+ function ContextBinding(definition) {
12932
+ var contextAttribute,
12933
+ _this = this;
12934
+ this.contextKeypath = definition.attr || 'proxiedObject';
12004
12935
  ContextBinding.__super__.constructor.apply(this, arguments);
12005
12936
  contextAttribute = this.attributeName ? "data-" + this.bindingName + "-" + this.attributeName : "data-" + this.bindingName;
12006
12937
  this.node.removeAttribute(contextAttribute);
12007
12938
  this.node.insertBefore(document.createComment("batman-" + contextAttribute + "=\"" + this.keyPath + "\""), this.node.firstChild);
12939
+ this.backingView.observe(this.contextKeypath, this._updateValue = function(value) {
12940
+ if (_this.isDataChanging) {
12941
+ return;
12942
+ }
12943
+ return _this.view.setKeypath(_this.keyPath, value);
12944
+ });
12008
12945
  }
12009
12946
 
12010
12947
  ContextBinding.prototype.dataChange = function(proxiedObject) {
12011
- return this.backingView.set(this.attributeName || 'proxiedObject', proxiedObject);
12948
+ this.isDataChanging = true;
12949
+ this.backingView.set(this.contextKeypath, proxiedObject);
12950
+ return this.isDataChanging = false;
12012
12951
  };
12013
12952
 
12014
12953
  ContextBinding.prototype.die = function() {
12015
- this.backingView.unset(this.attributeName || 'proxiedObject');
12954
+ this.backingView.forget(this.contextKeypath, this._updateValue);
12955
+ this.backingView.unset(this.contextKeypath);
12016
12956
  return ContextBinding.__super__.die.apply(this, arguments);
12017
12957
  };
12018
12958
 
@@ -12250,6 +13190,22 @@
12250
13190
  return _ref;
12251
13191
  }
12252
13192
 
13193
+ AbstractCollectionBinding.prototype.dataChange = function(collection) {
13194
+ var items, _items;
13195
+ if (collection != null) {
13196
+ if (!this.bindCollection(collection)) {
13197
+ items = (collection != null ? collection.forEach : void 0) ? (_items = [], collection.forEach(function(item) {
13198
+ return _items.push(item);
13199
+ }), _items) : Object.keys(collection);
13200
+ this.handleArrayChanged(items);
13201
+ }
13202
+ } else {
13203
+ this.unbindCollection();
13204
+ this.collection = [];
13205
+ this.handleArrayChanged([]);
13206
+ }
13207
+ };
13208
+
12253
13209
  AbstractCollectionBinding.prototype.bindCollection = function(newCollection) {
12254
13210
  var _ref1;
12255
13211
  if (newCollection instanceof Batman.Hash) {
@@ -12368,6 +13324,9 @@
12368
13324
 
12369
13325
  StyleBinding.prototype.setStyle = function(key, value) {
12370
13326
  key = Batman.helpers.camelize(key.trim(), true);
13327
+ if (key === "") {
13328
+ return false;
13329
+ }
12371
13330
  if (this.oldStyles[key] == null) {
12372
13331
  this.oldStyles[key] = this.node.style[key] || "";
12373
13332
  }
@@ -12435,27 +13394,28 @@
12435
13394
  }).call(this);
12436
13395
 
12437
13396
  (function() {
12438
- var _ref,
12439
- __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
13397
+ var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
12440
13398
  __hasProp = {}.hasOwnProperty,
12441
13399
  __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
12442
13400
 
12443
13401
  Batman.DOM.ClassBinding = (function(_super) {
12444
13402
  __extends(ClassBinding, _super);
12445
13403
 
13404
+ ClassBinding.prototype.onlyObserve = Batman.BindingDefinitionOnlyObserve.Data;
13405
+
12446
13406
  function ClassBinding() {
12447
13407
  this.handleArrayChanged = __bind(this.handleArrayChanged, this);
12448
- _ref = ClassBinding.__super__.constructor.apply(this, arguments);
12449
- return _ref;
13408
+ this.existingClasses = arguments[0].node.className.split(' ');
13409
+ ClassBinding.__super__.constructor.apply(this, arguments);
12450
13410
  }
12451
13411
 
12452
- ClassBinding.prototype.onlyObserve = Batman.BindingDefinitionOnlyObserve.Data;
12453
-
12454
13412
  ClassBinding.prototype.dataChange = function(value) {
13413
+ var newClasses;
12455
13414
  if (value != null) {
12456
13415
  this.unbindCollection();
12457
13416
  if (typeof value === 'string') {
12458
- return this.node.className = value;
13417
+ newClasses = [value].concat(this.existingClasses);
13418
+ return this.node.className = newClasses.join(' ').trim();
12459
13419
  } else {
12460
13420
  this.bindCollection(value);
12461
13421
  return this.updateFromCollection();
@@ -12464,17 +13424,17 @@
12464
13424
  };
12465
13425
 
12466
13426
  ClassBinding.prototype.updateFromCollection = function() {
12467
- var array, k, v;
13427
+ var array, existingClasses, k, newClasses, v;
12468
13428
  if (this.collection) {
12469
13429
  array = this.collection.map ? this.collection.map(function(x) {
12470
13430
  return x;
12471
13431
  }) : (function() {
12472
- var _ref1, _results;
12473
- _ref1 = this.collection;
13432
+ var _ref, _results;
13433
+ _ref = this.collection;
12474
13434
  _results = [];
12475
- for (k in _ref1) {
12476
- if (!__hasProp.call(_ref1, k)) continue;
12477
- v = _ref1[k];
13435
+ for (k in _ref) {
13436
+ if (!__hasProp.call(_ref, k)) continue;
13437
+ v = _ref[k];
12478
13438
  _results.push(k);
12479
13439
  }
12480
13440
  return _results;
@@ -12482,7 +13442,11 @@
12482
13442
  if (array.toArray != null) {
12483
13443
  array = array.toArray();
12484
13444
  }
12485
- return this.node.className = array.join(' ');
13445
+ existingClasses = this.existingClasses.slice(0);
13446
+ newClasses = array.filter(function(val) {
13447
+ return existingClasses.indexOf(val) === -1;
13448
+ });
13449
+ return this.node.className = existingClasses.concat(newClasses).join(' ').trim();
12486
13450
  }
12487
13451
  };
12488
13452
 
@@ -12523,22 +13487,22 @@
12523
13487
  parentNode = this.placeholderNode.parentNode || this.node.parentNode;
12524
13488
  if (!!value === !this.invert) {
12525
13489
  if (view != null) {
12526
- view.fire('viewWillShow');
13490
+ view.fireAndCall('viewWillShow');
12527
13491
  }
12528
13492
  if (this.node.parentNode == null) {
12529
13493
  parentNode.insertBefore(this.node, this.placeholderNode);
12530
13494
  parentNode.removeChild(this.placeholderNode);
12531
13495
  }
12532
- return view != null ? view.fire('viewDidShow') : void 0;
13496
+ return view != null ? view.fireAndCall('viewDidShow') : void 0;
12533
13497
  } else {
12534
13498
  if (view != null) {
12535
- view.fire('viewWillHide');
13499
+ view.fireAndCall('viewWillHide');
12536
13500
  }
12537
13501
  if (this.node.parentNode != null) {
12538
13502
  parentNode.insertBefore(this.placeholderNode, this.node);
12539
13503
  parentNode.removeChild(this.node);
12540
13504
  }
12541
- return view != null ? view.fire('viewDidHide') : void 0;
13505
+ return view != null ? view.fireAndCall('viewDidHide') : void 0;
12542
13506
  }
12543
13507
  };
12544
13508
 
@@ -12747,8 +13711,12 @@
12747
13711
  IteratorBinding.__super__.constructor.apply(this, arguments);
12748
13712
  this.backingView.set('attributeName', this.attributeName);
12749
13713
  this.view.prevent('ready');
12750
- Batman.setImmediate(function() {
13714
+ this._handle = Batman.setImmediate(function() {
12751
13715
  var parentNode;
13716
+ if (_this.backingView.isDead) {
13717
+ Batman.developer.warn("IteratorBinding (data-foreach-" + _this.iteratorName + "='" + _this.keyPath + "') trying to insert dead backing view into DOM (" + _this.view.constructor.name + ")");
13718
+ return;
13719
+ }
12752
13720
  parentNode = _this.prototypeNode.parentNode;
12753
13721
  parentNode.insertBefore(_this.backingView.get('node'), _this.prototypeNode);
12754
13722
  parentNode.removeChild(_this.prototypeNode);
@@ -12757,22 +13725,6 @@
12757
13725
  });
12758
13726
  }
12759
13727
 
12760
- IteratorBinding.prototype.dataChange = function(collection) {
12761
- var items, _items;
12762
- if (collection != null) {
12763
- if (!this.bindCollection(collection)) {
12764
- items = (collection != null ? collection.forEach : void 0) ? (_items = [], collection.forEach(function(item) {
12765
- return _items.push(item);
12766
- }), _items) : Object.keys(collection);
12767
- this.handleArrayChanged(items);
12768
- }
12769
- } else {
12770
- this.unbindCollection();
12771
- this.collection = [];
12772
- this.handleArrayChanged([]);
12773
- }
12774
- };
12775
-
12776
13728
  IteratorBinding.prototype.handleArrayChanged = function(newItems) {
12777
13729
  if (!this.backingView.isDead) {
12778
13730
  this.backingView.destroySubviews();
@@ -12806,6 +13758,9 @@
12806
13758
  };
12807
13759
 
12808
13760
  IteratorBinding.prototype.die = function() {
13761
+ if (this._handle) {
13762
+ Batman.clearImmediate(this._handle);
13763
+ }
12809
13764
  this.prototypeNode = null;
12810
13765
  return IteratorBinding.__super__.die.apply(this, arguments);
12811
13766
  };
@@ -13074,6 +14029,24 @@
13074
14029
  equals: buntUndefined(function(lhs, rhs, binding) {
13075
14030
  return lhs === rhs;
13076
14031
  }),
14032
+ eq: function(lhs, rhs) {
14033
+ return lhs === rhs;
14034
+ },
14035
+ neq: function(lhs, rhs) {
14036
+ return lhs !== rhs;
14037
+ },
14038
+ lt: buntUndefined(function(lhs, rhs) {
14039
+ return lhs < rhs;
14040
+ }),
14041
+ gt: buntUndefined(function(lhs, rhs) {
14042
+ return lhs > rhs;
14043
+ }),
14044
+ lteq: buntUndefined(function(lhs, rhs) {
14045
+ return lhs <= rhs;
14046
+ }),
14047
+ gteq: buntUndefined(function(lhs, rhs) {
14048
+ return lhs >= rhs;
14049
+ }),
13077
14050
  and: function(lhs, rhs) {
13078
14051
  return lhs && rhs;
13079
14052
  },
@@ -13083,6 +14056,27 @@
13083
14056
  not: function(value, binding) {
13084
14057
  return !value;
13085
14058
  },
14059
+ ceil: function(value) {
14060
+ return Math.ceil(value);
14061
+ },
14062
+ floor: function(value) {
14063
+ return Math.floor(value);
14064
+ },
14065
+ round: function(value) {
14066
+ return Math.round(value);
14067
+ },
14068
+ precision: function(value, p) {
14069
+ return parseFloat(value).toPrecision(p);
14070
+ },
14071
+ fixed: function(value, f) {
14072
+ return parseFloat(value).toFixed(f);
14073
+ },
14074
+ delimitNumber: function(value) {
14075
+ value = value.toString();
14076
+ return value.replace(/(^|[^\w.])(\d{4,})/g, function($0, $1, $2) {
14077
+ return $1 + $2.replace(/\d(?=(?:\d\d\d)+(?!\d))/g, "$&,");
14078
+ });
14079
+ },
13086
14080
  trim: buntUndefined(function(value, binding) {
13087
14081
  return value.trim();
13088
14082
  }),
@@ -13193,7 +14187,7 @@
13193
14187
  values = {};
13194
14188
  for (k in interpolationKeypaths) {
13195
14189
  v = interpolationKeypaths[k];
13196
- values[k] = this.get(v);
14190
+ values[k] = this.lookupKeypath(v);
13197
14191
  if (values[k] == null) {
13198
14192
  Batman.developer.warn("Warning! Undefined interpolation key " + k + " for interpolation", string);
13199
14193
  values[k] = '';
@@ -13207,18 +14201,14 @@
13207
14201
  if (!block) {
13208
14202
  return;
13209
14203
  }
13210
- return function() {
13211
- var regularArgs;
13212
- regularArgs = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
13213
- return block.call.apply(block, [this].concat(__slice.call(curryArgs), __slice.call(regularArgs)));
13214
- };
14204
+ if (typeof block === "function") {
14205
+ return function() {
14206
+ return block.call.apply(block, [this].concat(__slice.call(curryArgs), __slice.call(arguments)));
14207
+ };
14208
+ } else if (typeof block.get === "function") {
14209
+ return block.get.apply(block, curryArgs);
14210
+ }
13215
14211
  },
13216
- routeToAction: buntUndefined(function(model, action) {
13217
- var params;
13218
- params = Batman.Dispatcher.paramsFromArgument(model);
13219
- params.action = action;
13220
- return params;
13221
- }),
13222
14212
  escape: buntUndefined(Batman.escapeHTML)
13223
14213
  };
13224
14214
 
@@ -13240,4 +14230,1455 @@
13240
14230
  (function() {
13241
14231
 
13242
14232
 
14233
+ }).call(this);
14234
+
14235
+ /**
14236
+ * Zest (https://github.com/chjj/zest)
14237
+ * A css selector engine.
14238
+ * Copyright (c) 2011-2012, Christopher Jeffrey. (MIT Licensed)
14239
+ */
14240
+
14241
+ // TODO
14242
+ // - Recognize the TR subject selector when parsing.
14243
+ // - Pass context to scope.
14244
+ // - Add :column pseudo-classes.
14245
+
14246
+ ;(function() {
14247
+
14248
+ /**
14249
+ * Shared
14250
+ */
14251
+
14252
+ var window = this
14253
+ , document = this.document
14254
+ , old = this.zest;
14255
+
14256
+ /**
14257
+ * Helpers
14258
+ */
14259
+
14260
+ var compareDocumentPosition = (function() {
14261
+ if (document.compareDocumentPosition) {
14262
+ return function(a, b) {
14263
+ return a.compareDocumentPosition(b);
14264
+ };
14265
+ }
14266
+ return function(a, b) {
14267
+ var el = a.ownerDocument.getElementsByTagName('*')
14268
+ , i = el.length;
14269
+
14270
+ while (i--) {
14271
+ if (el[i] === a) return 2;
14272
+ if (el[i] === b) return 4;
14273
+ }
14274
+
14275
+ return 1;
14276
+ };
14277
+ })();
14278
+
14279
+ var order = function(a, b) {
14280
+ return compareDocumentPosition(a, b) & 2 ? 1 : -1;
14281
+ };
14282
+
14283
+ var next = function(el) {
14284
+ while ((el = el.nextSibling)
14285
+ && el.nodeType !== 1);
14286
+ return el;
14287
+ };
14288
+
14289
+ var prev = function(el) {
14290
+ while ((el = el.previousSibling)
14291
+ && el.nodeType !== 1);
14292
+ return el;
14293
+ };
14294
+
14295
+ var child = function(el) {
14296
+ if (el = el.firstChild) {
14297
+ while (el.nodeType !== 1
14298
+ && (el = el.nextSibling));
14299
+ }
14300
+ return el;
14301
+ };
14302
+
14303
+ var lastChild = function(el) {
14304
+ if (el = el.lastChild) {
14305
+ while (el.nodeType !== 1
14306
+ && (el = el.previousSibling));
14307
+ }
14308
+ return el;
14309
+ };
14310
+
14311
+ var unquote = function(str) {
14312
+ if (!str) return str;
14313
+ var ch = str[0];
14314
+ return ch === '"' || ch === '\''
14315
+ ? str.slice(1, -1)
14316
+ : str;
14317
+ };
14318
+
14319
+ var indexOf = (function() {
14320
+ if (Array.prototype.indexOf) {
14321
+ return Array.prototype.indexOf;
14322
+ }
14323
+ return function(obj, item) {
14324
+ var i = this.length;
14325
+ while (i--) {
14326
+ if (this[i] === item) return i;
14327
+ }
14328
+ return -1;
14329
+ };
14330
+ })();
14331
+
14332
+ var makeInside = function(start, end) {
14333
+ var regex = rules.inside.source
14334
+ .replace(/</g, start)
14335
+ .replace(/>/g, end);
14336
+
14337
+ return new RegExp(regex);
14338
+ };
14339
+
14340
+ var replace = function(regex, name, val) {
14341
+ regex = regex.source;
14342
+ regex = regex.replace(name, val.source || val);
14343
+ return new RegExp(regex);
14344
+ };
14345
+
14346
+ var truncateUrl = function(url, num) {
14347
+ return url
14348
+ .replace(/^(?:\w+:\/\/|\/+)/, '')
14349
+ .replace(/(?:\/+|\/*#.*?)$/, '')
14350
+ .split('/', num)
14351
+ .join('/');
14352
+ };
14353
+
14354
+ /**
14355
+ * Handle `nth` Selectors
14356
+ */
14357
+
14358
+ var parseNth = function(param, test) {
14359
+ var param = param.replace(/\s+/g, '')
14360
+ , cap;
14361
+
14362
+ if (param === 'even') {
14363
+ param = '2n+0';
14364
+ } else if (param === 'odd') {
14365
+ param = '2n+1';
14366
+ } else if (!~param.indexOf('n')) {
14367
+ param = '0n' + param;
14368
+ }
14369
+
14370
+ cap = /^([+-])?(\d+)?n([+-])?(\d+)?$/.exec(param);
14371
+
14372
+ return {
14373
+ group: cap[1] === '-'
14374
+ ? -(cap[2] || 1)
14375
+ : +(cap[2] || 1),
14376
+ offset: cap[4]
14377
+ ? (cap[3] === '-' ? -cap[4] : +cap[4])
14378
+ : 0
14379
+ };
14380
+ };
14381
+
14382
+ var nth = function(param, test, last) {
14383
+ var param = parseNth(param)
14384
+ , group = param.group
14385
+ , offset = param.offset
14386
+ , find = !last ? child : lastChild
14387
+ , advance = !last ? next : prev;
14388
+
14389
+ return function(el) {
14390
+ if (el.parentNode.nodeType !== 1) return;
14391
+
14392
+ var rel = find(el.parentNode)
14393
+ , pos = 0;
14394
+
14395
+ while (rel) {
14396
+ if (test(rel, el)) pos++;
14397
+ if (rel === el) {
14398
+ pos -= offset;
14399
+ return group && pos
14400
+ ? !(pos % group) && (pos < 0 === group < 0)
14401
+ : !pos;
14402
+ }
14403
+ rel = advance(rel);
14404
+ }
14405
+ };
14406
+ };
14407
+
14408
+ /**
14409
+ * Simple Selectors
14410
+ */
14411
+
14412
+ var selectors = {
14413
+ '*': (function() {
14414
+ if (function() {
14415
+ var el = document.createElement('div');
14416
+ el.appendChild(document.createComment(''));
14417
+ return !!el.getElementsByTagName('*')[0];
14418
+ }()) {
14419
+ return function(el) {
14420
+ if (el.nodeType === 1) return true;
14421
+ };
14422
+ }
14423
+ return function() {
14424
+ return true;
14425
+ };
14426
+ })(),
14427
+ 'type': function(type) {
14428
+ type = type.toLowerCase();
14429
+ return function(el) {
14430
+ return el.nodeName.toLowerCase() === type;
14431
+ };
14432
+ },
14433
+ 'attr': function(key, op, val, i) {
14434
+ op = operators[op];
14435
+ return function(el) {
14436
+ var attr;
14437
+ switch (key) {
14438
+ case 'for':
14439
+ attr = el.htmlFor;
14440
+ break;
14441
+ case 'class':
14442
+ // className is '' when non-existent
14443
+ // getAttribute('class') is null
14444
+ attr = el.className;
14445
+ if (attr === '' && el.getAttribute('class') == null) {
14446
+ attr = null;
14447
+ }
14448
+ break;
14449
+ case 'href':
14450
+ attr = el.getAttribute('href', 2);
14451
+ break;
14452
+ case 'title':
14453
+ // getAttribute('title') can be '' when non-existent sometimes?
14454
+ attr = el.getAttribute('title') || null;
14455
+ break;
14456
+ case 'id':
14457
+ if (el.getAttribute) {
14458
+ attr = el.getAttribute('id');
14459
+ break;
14460
+ }
14461
+ default:
14462
+ attr = el[key] != null
14463
+ ? el[key]
14464
+ : el.getAttribute && el.getAttribute(key);
14465
+ break;
14466
+ }
14467
+ if (attr == null) return;
14468
+ attr = attr + '';
14469
+ if (i) {
14470
+ attr = attr.toLowerCase();
14471
+ val = val.toLowerCase();
14472
+ }
14473
+ return op(attr, val);
14474
+ };
14475
+ },
14476
+ ':first-child': function(el) {
14477
+ return !prev(el) && el.parentNode.nodeType === 1;
14478
+ },
14479
+ ':last-child': function(el) {
14480
+ return !next(el) && el.parentNode.nodeType === 1;
14481
+ },
14482
+ ':only-child': function(el) {
14483
+ return !prev(el) && !next(el)
14484
+ && el.parentNode.nodeType === 1;
14485
+ },
14486
+ ':nth-child': function(param, last) {
14487
+ return nth(param, function() {
14488
+ return true;
14489
+ }, last);
14490
+ },
14491
+ ':nth-last-child': function(param) {
14492
+ return selectors[':nth-child'](param, true);
14493
+ },
14494
+ ':root': function(el) {
14495
+ return el.ownerDocument.documentElement === el;
14496
+ },
14497
+ ':empty': function(el) {
14498
+ return !el.firstChild;
14499
+ },
14500
+ ':not': function(sel) {
14501
+ var test = compileGroup(sel);
14502
+ return function(el) {
14503
+ return !test(el);
14504
+ };
14505
+ },
14506
+ ':first-of-type': function(el) {
14507
+ if (el.parentNode.nodeType !== 1) return;
14508
+ var type = el.nodeName;
14509
+ while (el = prev(el)) {
14510
+ if (el.nodeName === type) return;
14511
+ }
14512
+ return true;
14513
+ },
14514
+ ':last-of-type': function(el) {
14515
+ if (el.parentNode.nodeType !== 1) return;
14516
+ var type = el.nodeName;
14517
+ while (el = next(el)) {
14518
+ if (el.nodeName === type) return;
14519
+ }
14520
+ return true;
14521
+ },
14522
+ ':only-of-type': function(el) {
14523
+ return selectors[':first-of-type'](el)
14524
+ && selectors[':last-of-type'](el);
14525
+ },
14526
+ ':nth-of-type': function(param, last) {
14527
+ return nth(param, function(rel, el) {
14528
+ return rel.nodeName === el.nodeName;
14529
+ }, last);
14530
+ },
14531
+ ':nth-last-of-type': function(param) {
14532
+ return selectors[':nth-of-type'](param, true);
14533
+ },
14534
+ ':checked': function(el) {
14535
+ return !!(el.checked || el.selected);
14536
+ },
14537
+ ':indeterminate': function(el) {
14538
+ return !selectors[':checked'](el);
14539
+ },
14540
+ ':enabled': function(el) {
14541
+ return !el.disabled && el.type !== 'hidden';
14542
+ },
14543
+ ':disabled': function(el) {
14544
+ return !!el.disabled;
14545
+ },
14546
+ ':target': function(el) {
14547
+ return el.id === window.location.hash.substring(1);
14548
+ },
14549
+ ':focus': function(el) {
14550
+ return el === el.ownerDocument.activeElement;
14551
+ },
14552
+ ':matches': function(sel) {
14553
+ return compileGroup(sel);
14554
+ },
14555
+ ':nth-match': function(param, last) {
14556
+ var args = param.split(/\s*,\s*/)
14557
+ , arg = args.shift()
14558
+ , test = compileGroup(args.join(','));
14559
+
14560
+ return nth(arg, test, last);
14561
+ },
14562
+ ':nth-last-match': function(param) {
14563
+ return selectors[':nth-match'](param, true);
14564
+ },
14565
+ ':links-here': function(el) {
14566
+ return el + '' === window.location + '';
14567
+ },
14568
+ ':lang': function(param) {
14569
+ return function(el) {
14570
+ while (el) {
14571
+ if (el.lang) return el.lang.indexOf(param) === 0;
14572
+ el = el.parentNode;
14573
+ }
14574
+ };
14575
+ },
14576
+ ':dir': function(param) {
14577
+ return function(el) {
14578
+ while (el) {
14579
+ if (el.dir) return el.dir === param;
14580
+ el = el.parentNode;
14581
+ }
14582
+ };
14583
+ },
14584
+ ':scope': function(el, con) {
14585
+ var context = con || el.ownerDocument;
14586
+ if (context.nodeType === 9) {
14587
+ return el === context.documentElement;
14588
+ }
14589
+ return el === context;
14590
+ },
14591
+ ':any-link': function(el) {
14592
+ return typeof el.href === 'string';
14593
+ },
14594
+ ':local-link': function(el) {
14595
+ if (el.nodeName) {
14596
+ return el.href && el.host === window.location.host;
14597
+ }
14598
+ var param = +el + 1;
14599
+ return function(el) {
14600
+ if (!el.href) return;
14601
+
14602
+ var url = window.location + ''
14603
+ , href = el + '';
14604
+
14605
+ return truncateUrl(url, param) === truncateUrl(href, param);
14606
+ };
14607
+ },
14608
+ ':default': function(el) {
14609
+ return !!el.defaultSelected;
14610
+ },
14611
+ ':valid': function(el) {
14612
+ return el.willValidate || (el.validity && el.validity.valid);
14613
+ },
14614
+ ':invalid': function(el) {
14615
+ return !selectors[':valid'](el);
14616
+ },
14617
+ ':in-range': function(el) {
14618
+ return el.value > el.min && el.value <= el.max;
14619
+ },
14620
+ ':out-of-range': function(el) {
14621
+ return !selectors[':in-range'](el);
14622
+ },
14623
+ ':required': function(el) {
14624
+ return !!el.required;
14625
+ },
14626
+ ':optional': function(el) {
14627
+ return !el.required;
14628
+ },
14629
+ ':read-only': function(el) {
14630
+ if (el.readOnly) return true;
14631
+
14632
+ var attr = el.getAttribute('contenteditable')
14633
+ , prop = el.contentEditable
14634
+ , name = el.nodeName.toLowerCase();
14635
+
14636
+ name = name !== 'input' && name !== 'textarea';
14637
+
14638
+ return (name || el.disabled) && attr == null && prop !== 'true';
14639
+ },
14640
+ ':read-write': function(el) {
14641
+ return !selectors[':read-only'](el);
14642
+ },
14643
+ ':hover': function() {
14644
+ throw new Error(':hover is not supported.');
14645
+ },
14646
+ ':active': function() {
14647
+ throw new Error(':active is not supported.');
14648
+ },
14649
+ ':link': function() {
14650
+ throw new Error(':link is not supported.');
14651
+ },
14652
+ ':visited': function() {
14653
+ throw new Error(':visited is not supported.');
14654
+ },
14655
+ ':column': function() {
14656
+ throw new Error(':column is not supported.');
14657
+ },
14658
+ ':nth-column': function() {
14659
+ throw new Error(':nth-column is not supported.');
14660
+ },
14661
+ ':nth-last-column': function() {
14662
+ throw new Error(':nth-last-column is not supported.');
14663
+ },
14664
+ ':current': function() {
14665
+ throw new Error(':current is not supported.');
14666
+ },
14667
+ ':past': function() {
14668
+ throw new Error(':past is not supported.');
14669
+ },
14670
+ ':future': function() {
14671
+ throw new Error(':future is not supported.');
14672
+ },
14673
+ // Non-standard, for compatibility purposes.
14674
+ ':contains': function(param) {
14675
+ return function(el) {
14676
+ var text = el.innerText || el.textContent || el.value || '';
14677
+ return !!~text.indexOf(param);
14678
+ };
14679
+ },
14680
+ ':has': function(param) {
14681
+ return function(el) {
14682
+ return zest(param, el).length > 0;
14683
+ };
14684
+ }
14685
+ // Potentially add more pseudo selectors for
14686
+ // compatibility with sizzle and most other
14687
+ // selector engines (?).
14688
+ };
14689
+
14690
+ /**
14691
+ * Attribute Operators
14692
+ */
14693
+
14694
+ var operators = {
14695
+ '-': function() {
14696
+ return true;
14697
+ },
14698
+ '=': function(attr, val) {
14699
+ return attr === val;
14700
+ },
14701
+ '*=': function(attr, val) {
14702
+ return attr.indexOf(val) !== -1;
14703
+ },
14704
+ '~=': function(attr, val) {
14705
+ var i = attr.indexOf(val)
14706
+ , f
14707
+ , l;
14708
+
14709
+ if (i === -1) return;
14710
+ f = attr[i - 1];
14711
+ l = attr[i + val.length];
14712
+
14713
+ return (!f || f === ' ') && (!l || l === ' ');
14714
+ },
14715
+ '|=': function(attr, val) {
14716
+ var i = attr.indexOf(val)
14717
+ , l;
14718
+
14719
+ if (i !== 0) return;
14720
+ l = attr[i + val.length];
14721
+
14722
+ return l === '-' || !l;
14723
+ },
14724
+ '^=': function(attr, val) {
14725
+ return attr.indexOf(val) === 0;
14726
+ },
14727
+ '$=': function(attr, val) {
14728
+ return attr.indexOf(val) + val.length === attr.length;
14729
+ },
14730
+ // non-standard
14731
+ '!=': function(attr, val) {
14732
+ return attr !== val;
14733
+ }
14734
+ };
14735
+
14736
+ /**
14737
+ * Combinator Logic
14738
+ */
14739
+
14740
+ var combinators = {
14741
+ ' ': function(test) {
14742
+ return function(el) {
14743
+ while (el = el.parentNode) {
14744
+ if (test(el)) return el;
14745
+ }
14746
+ };
14747
+ },
14748
+ '>': function(test) {
14749
+ return function(el) {
14750
+ return test(el = el.parentNode) && el;
14751
+ };
14752
+ },
14753
+ '+': function(test) {
14754
+ return function(el) {
14755
+ return test(el = prev(el)) && el;
14756
+ };
14757
+ },
14758
+ '~': function(test) {
14759
+ return function(el) {
14760
+ while (el = prev(el)) {
14761
+ if (test(el)) return el;
14762
+ }
14763
+ };
14764
+ },
14765
+ 'noop': function(test) {
14766
+ return function(el) {
14767
+ return test(el) && el;
14768
+ };
14769
+ },
14770
+ 'ref': function(test, name) {
14771
+ var node;
14772
+
14773
+ function ref(el) {
14774
+ var doc = el.ownerDocument
14775
+ , nodes = doc.getElementsByTagName('*')
14776
+ , i = nodes.length;
14777
+
14778
+ while (i--) {
14779
+ node = nodes[i];
14780
+ if (ref.test(el)) {
14781
+ node = null;
14782
+ return true;
14783
+ }
14784
+ }
14785
+
14786
+ node = null;
14787
+ }
14788
+
14789
+ ref.combinator = function(el) {
14790
+ if (!node || !node.getAttribute) return;
14791
+
14792
+ var attr = node.getAttribute(name) || '';
14793
+ if (attr[0] === '#') attr = attr.substring(1);
14794
+
14795
+ if (attr === el.id && test(node)) {
14796
+ return node;
14797
+ }
14798
+ };
14799
+
14800
+ return ref;
14801
+ }
14802
+ };
14803
+
14804
+ /**
14805
+ * Grammar
14806
+ */
14807
+
14808
+ var rules = {
14809
+ qname: /^ *([\w\-]+|\*)/,
14810
+ simple: /^(?:([.#][\w\-]+)|pseudo|attr)/,
14811
+ ref: /^ *\/([\w\-]+)\/ */,
14812
+ combinator: /^(?: +([^ \w*]) +|( )+|([^ \w*]))(?! *$)/,
14813
+ attr: /^\[([\w\-]+)(?:([^\w]?=)(inside))?\]/,
14814
+ pseudo: /^(:[\w\-]+)(?:\((inside)\))?/,
14815
+ inside: /(?:"(?:\\"|[^"])*"|'(?:\\'|[^'])*'|<[^"'>]*>|\\["'>]|[^"'>])*/
14816
+ };
14817
+
14818
+ rules.inside = replace(rules.inside, '[^"\'>]*', rules.inside);
14819
+ rules.attr = replace(rules.attr, 'inside', makeInside('\\[', '\\]'));
14820
+ rules.pseudo = replace(rules.pseudo, 'inside', makeInside('\\(', '\\)'));
14821
+ rules.simple = replace(rules.simple, 'pseudo', rules.pseudo);
14822
+ rules.simple = replace(rules.simple, 'attr', rules.attr);
14823
+
14824
+ /**
14825
+ * Compiling
14826
+ */
14827
+
14828
+ var compile = function(sel) {
14829
+ var sel = sel.replace(/^\s+|\s+$/g, '')
14830
+ , test
14831
+ , filter = []
14832
+ , buff = []
14833
+ , subject
14834
+ , qname
14835
+ , cap
14836
+ , op
14837
+ , ref;
14838
+
14839
+ while (sel) {
14840
+ if (cap = rules.qname.exec(sel)) {
14841
+ sel = sel.substring(cap[0].length);
14842
+ qname = cap[1];
14843
+ buff.push(tok(qname, true));
14844
+ } else if (cap = rules.simple.exec(sel)) {
14845
+ sel = sel.substring(cap[0].length);
14846
+ qname = '*';
14847
+ buff.push(tok(qname, true));
14848
+ buff.push(tok(cap));
14849
+ } else {
14850
+ throw new Error('Invalid selector.');
14851
+ }
14852
+
14853
+ while (cap = rules.simple.exec(sel)) {
14854
+ sel = sel.substring(cap[0].length);
14855
+ buff.push(tok(cap));
14856
+ }
14857
+
14858
+ if (sel[0] === '!') {
14859
+ sel = sel.substring(1);
14860
+ subject = makeSubject();
14861
+ subject.qname = qname;
14862
+ buff.push(subject.simple);
14863
+ }
14864
+
14865
+ if (cap = rules.ref.exec(sel)) {
14866
+ sel = sel.substring(cap[0].length);
14867
+ ref = combinators.ref(makeSimple(buff), cap[1]);
14868
+ filter.push(ref.combinator);
14869
+ buff = [];
14870
+ continue;
14871
+ }
14872
+
14873
+ if (cap = rules.combinator.exec(sel)) {
14874
+ sel = sel.substring(cap[0].length);
14875
+ op = cap[1] || cap[2] || cap[3];
14876
+ if (op === ',') {
14877
+ filter.push(combinators.noop(makeSimple(buff)));
14878
+ break;
14879
+ }
14880
+ } else {
14881
+ op = 'noop';
14882
+ }
14883
+
14884
+ filter.push(combinators[op](makeSimple(buff)));
14885
+ buff = [];
14886
+ }
14887
+
14888
+ test = makeTest(filter);
14889
+ test.qname = qname;
14890
+ test.sel = sel;
14891
+
14892
+ if (subject) {
14893
+ subject.lname = test.qname;
14894
+
14895
+ subject.test = test;
14896
+ subject.qname = subject.qname;
14897
+ subject.sel = test.sel;
14898
+ test = subject;
14899
+ }
14900
+
14901
+ if (ref) {
14902
+ ref.test = test;
14903
+ ref.qname = test.qname;
14904
+ ref.sel = test.sel;
14905
+ test = ref;
14906
+ }
14907
+
14908
+ return test;
14909
+ };
14910
+
14911
+ var tok = function(cap, qname) {
14912
+ // qname
14913
+ if (qname) {
14914
+ return cap === '*'
14915
+ ? selectors['*']
14916
+ : selectors.type(cap);
14917
+ }
14918
+
14919
+ // class/id
14920
+ if (cap[1]) {
14921
+ return cap[1][0] === '.'
14922
+ ? selectors.attr('class', '~=', cap[1].substring(1))
14923
+ : selectors.attr('id', '=', cap[1].substring(1));
14924
+ }
14925
+
14926
+ // pseudo-name
14927
+ // inside-pseudo
14928
+ if (cap[2]) {
14929
+ return cap[3]
14930
+ ? selectors[cap[2]](unquote(cap[3]))
14931
+ : selectors[cap[2]];
14932
+ }
14933
+
14934
+ // attr name
14935
+ // attr op
14936
+ // attr value
14937
+ if (cap[4]) {
14938
+ var i;
14939
+ if (cap[6]) {
14940
+ i = cap[6].length;
14941
+ cap[6] = cap[6].replace(/ +i$/, '');
14942
+ i = i > cap[6].length;
14943
+ }
14944
+ return selectors.attr(cap[4], cap[5] || '-', unquote(cap[6]), i);
14945
+ }
14946
+
14947
+ throw new Error('Unknown Selector.');
14948
+ };
14949
+
14950
+ var makeSimple = function(func) {
14951
+ var l = func.length
14952
+ , i;
14953
+
14954
+ // Potentially make sure
14955
+ // `el` is truthy.
14956
+ if (l < 2) return func[0];
14957
+
14958
+ return function(el) {
14959
+ if (!el) return;
14960
+ for (i = 0; i < l; i++) {
14961
+ if (!func[i](el)) return;
14962
+ }
14963
+ return true;
14964
+ };
14965
+ };
14966
+
14967
+ var makeTest = function(func) {
14968
+ if (func.length < 2) {
14969
+ return function(el) {
14970
+ return !!func[0](el);
14971
+ };
14972
+ }
14973
+ return function(el) {
14974
+ var i = func.length;
14975
+ while (i--) {
14976
+ if (!(el = func[i](el))) return;
14977
+ }
14978
+ return true;
14979
+ };
14980
+ };
14981
+
14982
+ var makeSubject = function() {
14983
+ var target;
14984
+
14985
+ function subject(el) {
14986
+ var node = el.ownerDocument
14987
+ , scope = node.getElementsByTagName(subject.lname)
14988
+ , i = scope.length;
14989
+
14990
+ while (i--) {
14991
+ if (subject.test(scope[i]) && target === el) {
14992
+ target = null;
14993
+ return true;
14994
+ }
14995
+ }
14996
+
14997
+ target = null;
14998
+ }
14999
+
15000
+ subject.simple = function(el) {
15001
+ target = el;
15002
+ return true;
15003
+ };
15004
+
15005
+ return subject;
15006
+ };
15007
+
15008
+ var compileGroup = function(sel) {
15009
+ var test = compile(sel)
15010
+ , tests = [ test ];
15011
+
15012
+ while (test.sel) {
15013
+ test = compile(test.sel);
15014
+ tests.push(test);
15015
+ }
15016
+
15017
+ if (tests.length < 2) return test;
15018
+
15019
+ return function(el) {
15020
+ var l = tests.length
15021
+ , i = 0;
15022
+
15023
+ for (; i < l; i++) {
15024
+ if (tests[i](el)) return true;
15025
+ }
15026
+ };
15027
+ };
15028
+
15029
+ /**
15030
+ * Selection
15031
+ */
15032
+
15033
+ var find = function(sel, node) {
15034
+ var results = []
15035
+ , test = compile(sel)
15036
+ , scope = node.getElementsByTagName(test.qname)
15037
+ , i = 0
15038
+ , el;
15039
+
15040
+ while (el = scope[i++]) {
15041
+ if (test(el)) results.push(el);
15042
+ }
15043
+
15044
+ if (test.sel) {
15045
+ while (test.sel) {
15046
+ test = compile(test.sel);
15047
+ scope = node.getElementsByTagName(test.qname);
15048
+ i = 0;
15049
+ while (el = scope[i++]) {
15050
+ if (test(el) && !~indexOf.call(results, el)) {
15051
+ results.push(el);
15052
+ }
15053
+ }
15054
+ }
15055
+ results.sort(order);
15056
+ }
15057
+
15058
+ return results;
15059
+ };
15060
+
15061
+ /**
15062
+ * Native
15063
+ */
15064
+
15065
+ var select = (function() {
15066
+ var slice = (function() {
15067
+ try {
15068
+ Array.prototype.slice.call(document.getElementsByTagName('zest'));
15069
+ return Array.prototype.slice;
15070
+ } catch(e) {
15071
+ e = null;
15072
+ return function() {
15073
+ var a = [], i = 0, l = this.length;
15074
+ for (; i < l; i++) a.push(this[i]);
15075
+ return a;
15076
+ };
15077
+ }
15078
+ })();
15079
+
15080
+ if (document.querySelectorAll) {
15081
+ return function(sel, node) {
15082
+ try {
15083
+ return slice.call(node.querySelectorAll(sel));
15084
+ } catch(e) {
15085
+ return find(sel, node);
15086
+ }
15087
+ };
15088
+ }
15089
+
15090
+ return function(sel, node) {
15091
+ try {
15092
+ if (sel[0] === '#' && /^#[\w\-]+$/.test(sel)) {
15093
+ return [node.getElementById(sel.substring(1))];
15094
+ }
15095
+ if (sel[0] === '.' && /^\.[\w\-]+$/.test(sel)) {
15096
+ sel = node.getElementsByClassName(sel.substring(1));
15097
+ return slice.call(sel);
15098
+ }
15099
+ if (/^[\w\-]+$/.test(sel)) {
15100
+ return slice.call(node.getElementsByTagName(sel));
15101
+ }
15102
+ } catch(e) {
15103
+ ;
15104
+ }
15105
+ return find(sel, node);
15106
+ };
15107
+ })();
15108
+
15109
+ /**
15110
+ * Zest
15111
+ */
15112
+
15113
+ var zest = function(sel, node) {
15114
+ try {
15115
+ sel = select(sel, node || document);
15116
+ } catch(e) {
15117
+ if (window.ZEST_DEBUG) {
15118
+ console.log(e.stack || e + '');
15119
+ }
15120
+ sel = [];
15121
+ }
15122
+ return sel;
15123
+ };
15124
+
15125
+ /**
15126
+ * Expose
15127
+ */
15128
+
15129
+ zest.selectors = selectors;
15130
+ zest.operators = operators;
15131
+ zest.combinators = combinators;
15132
+ zest.compile = compileGroup;
15133
+
15134
+ zest.matches = function(el, sel) {
15135
+ return !!compileGroup(sel)(el);
15136
+ };
15137
+
15138
+ zest.cache = function() {
15139
+ if (compile.raw) return;
15140
+
15141
+ var raw = compile
15142
+ , cache = {};
15143
+
15144
+ compile = function(sel) {
15145
+ return cache[sel]
15146
+ || (cache[sel] = raw(sel));
15147
+ };
15148
+
15149
+ compile.raw = raw;
15150
+ zest._cache = cache;
15151
+ };
15152
+
15153
+ zest.noCache = function() {
15154
+ if (!compile.raw) return;
15155
+ compile = compile.raw;
15156
+ delete zest._cache;
15157
+ };
15158
+
15159
+ zest.noConflict = function() {
15160
+ window.zest = old;
15161
+ return zest;
15162
+ };
15163
+
15164
+ zest.noNative = function() {
15165
+ select = find;
15166
+ };
15167
+
15168
+ if (typeof module !== 'undefined') {
15169
+ module.exports = zest;
15170
+ } else {
15171
+ this.zest = zest;
15172
+ }
15173
+
15174
+ if (window.ZEST_DEBUG) {
15175
+ zest.noNative();
15176
+ } else {
15177
+ zest.cache();
15178
+ }
15179
+
15180
+ }).call(function() {
15181
+ return this || (typeof window !== 'undefined' ? window : global);
15182
+ }());
15183
+
15184
+ /*!
15185
+ * Reqwest! A general purpose XHR connection manager
15186
+ * (c) Dustin Diaz 2011
15187
+ * https://github.com/ded/reqwest
15188
+ * license MIT
15189
+ */
15190
+ !function (name, definition) {
15191
+ if (typeof module != 'undefined') module.exports = definition()
15192
+ else if (typeof define == 'function' && define.amd) define(name, definition)
15193
+ else this[name] = definition()
15194
+ }('reqwest', function () {
15195
+
15196
+ var context = this
15197
+ , win = window
15198
+ , doc = document
15199
+ , old = context.reqwest
15200
+ , twoHundo = /^20\d$/
15201
+ , byTag = 'getElementsByTagName'
15202
+ , readyState = 'readyState'
15203
+ , contentType = 'Content-Type'
15204
+ , requestedWith = 'X-Requested-With'
15205
+ , head = doc[byTag]('head')[0]
15206
+ , uniqid = 0
15207
+ , lastValue // data stored by the most recent JSONP callback
15208
+ , xmlHttpRequest = 'XMLHttpRequest'
15209
+ , isArray = typeof Array.isArray == 'function' ? Array.isArray : function (a) {
15210
+ return a instanceof Array
15211
+ }
15212
+ , defaultHeaders = {
15213
+ contentType: 'application/x-www-form-urlencoded'
15214
+ , accept: {
15215
+ '*': 'text/javascript, text/html, application/xml, text/xml, */*'
15216
+ , xml: 'application/xml, text/xml'
15217
+ , html: 'text/html'
15218
+ , text: 'text/plain'
15219
+ , json: 'application/json, text/javascript'
15220
+ , js: 'application/javascript, text/javascript'
15221
+ }
15222
+ , requestedWith: xmlHttpRequest
15223
+ }
15224
+ , xhr = win[xmlHttpRequest] ?
15225
+ function () {
15226
+ return new XMLHttpRequest()
15227
+ } :
15228
+ function () {
15229
+ return new ActiveXObject('Microsoft.XMLHTTP')
15230
+ }
15231
+
15232
+ function handleReadyState(o, success, error) {
15233
+ return function () {
15234
+ if (o && o[readyState] == 4) {
15235
+ if (twoHundo.test(o.status)) {
15236
+ success(o)
15237
+ } else {
15238
+ error(o)
15239
+ }
15240
+ }
15241
+ }
15242
+ }
15243
+
15244
+ function setHeaders(http, o) {
15245
+ var headers = o.headers || {}, h
15246
+ headers.Accept = headers.Accept || defaultHeaders.accept[o.type] || defaultHeaders.accept['*']
15247
+ // breaks cross-origin requests with legacy browsers
15248
+ if (!o.crossOrigin && !headers[requestedWith]) headers[requestedWith] = defaultHeaders.requestedWith
15249
+ if (!headers[contentType]) headers[contentType] = o.contentType || defaultHeaders.contentType
15250
+ for (h in headers) {
15251
+ headers.hasOwnProperty(h) && http.setRequestHeader(h, headers[h])
15252
+ }
15253
+ }
15254
+
15255
+ function generalCallback(data) {
15256
+ lastValue = data
15257
+ }
15258
+
15259
+ function urlappend(url, s) {
15260
+ return url + (/\?/.test(url) ? '&' : '?') + s
15261
+ }
15262
+
15263
+ function handleJsonp(o, fn, err, url) {
15264
+ var reqId = uniqid++
15265
+ , cbkey = o.jsonpCallback || 'callback' // the 'callback' key
15266
+ , cbval = o.jsonpCallbackName || ('reqwest_' + reqId) // the 'callback' value
15267
+ , cbreg = new RegExp('((^|\\?|&)' + cbkey + ')=([^&]+)')
15268
+ , match = url.match(cbreg)
15269
+ , script = doc.createElement('script')
15270
+ , loaded = 0
15271
+
15272
+ if (match) {
15273
+ if (match[3] === '?') {
15274
+ url = url.replace(cbreg, '$1=' + cbval) // wildcard callback func name
15275
+ } else {
15276
+ cbval = match[3] // provided callback func name
15277
+ }
15278
+ } else {
15279
+ url = urlappend(url, cbkey + '=' + cbval) // no callback details, add 'em
15280
+ }
15281
+
15282
+ win[cbval] = generalCallback
15283
+
15284
+ script.type = 'text/javascript'
15285
+ script.src = url
15286
+ script.async = true
15287
+ if (typeof script.onreadystatechange !== 'undefined') {
15288
+ // need this for IE due to out-of-order onreadystatechange(), binding script
15289
+ // execution to an event listener gives us control over when the script
15290
+ // is executed. See http://jaubourg.net/2010/07/loading-script-as-onclick-handler-of.html
15291
+ script.event = 'onclick'
15292
+ script.htmlFor = script.id = '_reqwest_' + reqId
15293
+ }
15294
+
15295
+ script.onload = script.onreadystatechange = function () {
15296
+ if ((script[readyState] && script[readyState] !== 'complete' && script[readyState] !== 'loaded') || loaded) {
15297
+ return false
15298
+ }
15299
+ script.onload = script.onreadystatechange = null
15300
+ script.onclick && script.onclick()
15301
+ // Call the user callback with the last value stored and clean up values and scripts.
15302
+ o.success && o.success(lastValue)
15303
+ lastValue = undefined
15304
+ head.removeChild(script)
15305
+ loaded = 1
15306
+ }
15307
+
15308
+ // Add the script to the DOM head
15309
+ head.appendChild(script)
15310
+ }
15311
+
15312
+ function getRequest(o, fn, err) {
15313
+ var method = (o.method || 'GET').toUpperCase()
15314
+ , url = typeof o === 'string' ? o : o.url
15315
+ // convert non-string objects to query-string form unless o.processData is false
15316
+ , data = (o.processData !== false && o.data && typeof o.data !== 'string')
15317
+ ? reqwest.toQueryString(o.data)
15318
+ : (o.data || null)
15319
+ , http
15320
+
15321
+ // if we're working on a GET request and we have data then we should append
15322
+ // query string to end of URL and not post data
15323
+ if ((o.type == 'jsonp' || method == 'GET') && data) {
15324
+ url = urlappend(url, data)
15325
+ data = null
15326
+ }
15327
+
15328
+ if (o.type == 'jsonp') return handleJsonp(o, fn, err, url)
15329
+
15330
+ http = xhr()
15331
+ http.open(method, url, true)
15332
+ setHeaders(http, o)
15333
+ http.onreadystatechange = handleReadyState(http, fn, err)
15334
+ o.before && o.before(http)
15335
+ http.send(data)
15336
+ return http
15337
+ }
15338
+
15339
+ function Reqwest(o, fn) {
15340
+ this.o = o
15341
+ this.fn = fn
15342
+ init.apply(this, arguments)
15343
+ }
15344
+
15345
+ function setType(url) {
15346
+ var m = url.match(/\.(json|jsonp|html|xml)(\?|$)/)
15347
+ return m ? m[1] : 'js'
15348
+ }
15349
+
15350
+ function init(o, fn) {
15351
+ this.url = typeof o == 'string' ? o : o.url
15352
+ this.timeout = null
15353
+ var type = o.type || setType(this.url)
15354
+ , self = this
15355
+ fn = fn || function () {}
15356
+
15357
+ if (o.timeout) {
15358
+ this.timeout = setTimeout(function () {
15359
+ self.abort()
15360
+ }, o.timeout)
15361
+ }
15362
+
15363
+ function complete(resp) {
15364
+ o.timeout && clearTimeout(self.timeout)
15365
+ self.timeout = null
15366
+ o.complete && o.complete(resp)
15367
+ }
15368
+
15369
+ function success(resp) {
15370
+ var r = resp.responseText
15371
+ if (r) {
15372
+ switch (type) {
15373
+ case 'json':
15374
+ try {
15375
+ resp = win.JSON ? win.JSON.parse(r) : eval('(' + r + ')')
15376
+ } catch (err) {
15377
+ return error(resp, 'Could not parse JSON in response', err)
15378
+ }
15379
+ break;
15380
+ case 'js':
15381
+ resp = eval(r)
15382
+ break;
15383
+ case 'html':
15384
+ resp = r
15385
+ break;
15386
+ }
15387
+ }
15388
+
15389
+ fn(resp)
15390
+ o.success && o.success(resp)
15391
+
15392
+ complete(resp)
15393
+ }
15394
+
15395
+ function error(resp, msg, t) {
15396
+ o.error && o.error(resp, msg, t)
15397
+ complete(resp)
15398
+ }
15399
+
15400
+ this.request = getRequest(o, success, error)
15401
+ }
15402
+
15403
+ Reqwest.prototype = {
15404
+ abort: function () {
15405
+ this.request.abort()
15406
+ }
15407
+
15408
+ , retry: function () {
15409
+ init.call(this, this.o, this.fn)
15410
+ }
15411
+ }
15412
+
15413
+ function reqwest(o, fn) {
15414
+ return new Reqwest(o, fn)
15415
+ }
15416
+
15417
+ // normalize newline variants according to spec -> CRLF
15418
+ function normalize(s) {
15419
+ return s ? s.replace(/\r?\n/g, '\r\n') : ''
15420
+ }
15421
+
15422
+ function serial(el, cb) {
15423
+ var n = el.name
15424
+ , t = el.tagName.toLowerCase()
15425
+ , optCb = function(o) {
15426
+ // IE gives value="" even where there is no value attribute
15427
+ // 'specified' ref: http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-862529273
15428
+ if (o && !o.disabled)
15429
+ cb(n, normalize(o.attributes.value && o.attributes.value.specified ? o.value : o.text))
15430
+ }
15431
+
15432
+ // don't serialize elements that are disabled or without a name
15433
+ if (el.disabled || !n) return;
15434
+
15435
+ switch (t) {
15436
+ case 'input':
15437
+ if (!/reset|button|image|file/i.test(el.type)) {
15438
+ var ch = /checkbox/i.test(el.type)
15439
+ , ra = /radio/i.test(el.type)
15440
+ , val = el.value;
15441
+ // WebKit gives us "" instead of "on" if a checkbox has no value, so correct it here
15442
+ (!(ch || ra) || el.checked) && cb(n, normalize(ch && val === '' ? 'on' : val))
15443
+ }
15444
+ break;
15445
+ case 'textarea':
15446
+ cb(n, normalize(el.value))
15447
+ break;
15448
+ case 'select':
15449
+ if (el.type.toLowerCase() === 'select-one') {
15450
+ optCb(el.selectedIndex >= 0 ? el.options[el.selectedIndex] : null)
15451
+ } else {
15452
+ for (var i = 0; el.length && i < el.length; i++) {
15453
+ el.options[i].selected && optCb(el.options[i])
15454
+ }
15455
+ }
15456
+ break;
15457
+ }
15458
+ }
15459
+
15460
+ // collect up all form elements found from the passed argument elements all
15461
+ // the way down to child elements; pass a '<form>' or form fields.
15462
+ // called with 'this'=callback to use for serial() on each element
15463
+ function eachFormElement() {
15464
+ var cb = this
15465
+ , e, i, j
15466
+ , serializeSubtags = function(e, tags) {
15467
+ for (var i = 0; i < tags.length; i++) {
15468
+ var fa = e[byTag](tags[i])
15469
+ for (j = 0; j < fa.length; j++) serial(fa[j], cb)
15470
+ }
15471
+ }
15472
+
15473
+ for (i = 0; i < arguments.length; i++) {
15474
+ e = arguments[i]
15475
+ if (/input|select|textarea/i.test(e.tagName)) serial(e, cb)
15476
+ serializeSubtags(e, [ 'input', 'select', 'textarea' ])
15477
+ }
15478
+ }
15479
+
15480
+ // standard query string style serialization
15481
+ function serializeQueryString() {
15482
+ return reqwest.toQueryString(reqwest.serializeArray.apply(null, arguments))
15483
+ }
15484
+
15485
+ // { 'name': 'value', ... } style serialization
15486
+ function serializeHash() {
15487
+ var hash = {}
15488
+ eachFormElement.apply(function (name, value) {
15489
+ if (name in hash) {
15490
+ hash[name] && !isArray(hash[name]) && (hash[name] = [hash[name]])
15491
+ hash[name].push(value)
15492
+ } else hash[name] = value
15493
+ }, arguments)
15494
+ return hash
15495
+ }
15496
+
15497
+ // [ { name: 'name', value: 'value' }, ... ] style serialization
15498
+ reqwest.serializeArray = function () {
15499
+ var arr = []
15500
+ eachFormElement.apply(function(name, value) {
15501
+ arr.push({name: name, value: value})
15502
+ }, arguments)
15503
+ return arr
15504
+ }
15505
+
15506
+ reqwest.serialize = function () {
15507
+ if (arguments.length === 0) return ''
15508
+ var opt, fn
15509
+ , args = Array.prototype.slice.call(arguments, 0)
15510
+
15511
+ opt = args.pop()
15512
+ opt && opt.nodeType && args.push(opt) && (opt = null)
15513
+ opt && (opt = opt.type)
15514
+
15515
+ if (opt == 'map') fn = serializeHash
15516
+ else if (opt == 'array') fn = reqwest.serializeArray
15517
+ else fn = serializeQueryString
15518
+
15519
+ return fn.apply(null, args)
15520
+ }
15521
+
15522
+ reqwest.toQueryString = function (o) {
15523
+ var qs = '', i
15524
+ , enc = encodeURIComponent
15525
+ , push = function (k, v) {
15526
+ qs += enc(k) + '=' + enc(v) + '&'
15527
+ }
15528
+
15529
+ if (isArray(o)) {
15530
+ for (i = 0; o && i < o.length; i++) push(o[i].name, o[i].value)
15531
+ } else {
15532
+ for (var k in o) {
15533
+ if (!Object.hasOwnProperty.call(o, k)) continue;
15534
+ var v = o[k]
15535
+ if (isArray(v)) {
15536
+ for (i = 0; i < v.length; i++) push(k, v[i])
15537
+ } else push(k, o[k])
15538
+ }
15539
+ }
15540
+
15541
+ // spaces should be + according to spec
15542
+ return qs.replace(/&$/, '').replace(/%20/g,'+')
15543
+ }
15544
+
15545
+ // jQuery and Zepto compatibility, differences can be remapped here so you can call
15546
+ // .ajax.compat(options, callback)
15547
+ reqwest.compat = function (o, fn) {
15548
+ if (o) {
15549
+ o.type && (o.method = o.type) && delete o.type
15550
+ o.dataType && (o.type = o.dataType)
15551
+ o.jsonpCallback && (o.jsonpCallbackName = o.jsonpCallback) && delete o.jsonpCallback
15552
+ o.jsonp && (o.jsonpCallback = o.jsonp)
15553
+ }
15554
+ return new Reqwest(o, fn)
15555
+ }
15556
+
15557
+ return reqwest
15558
+ });
15559
+
15560
+ (function() {
15561
+ var SAFARI_CONTAINS_IS_BROKEN, version;
15562
+
15563
+ if (/Safari/.test(navigator.userAgent)) {
15564
+ version = /WebKit\/(\S+)/.exec(navigator.userAgent);
15565
+ if (version && parseFloat(version) < 540) {
15566
+ SAFARI_CONTAINS_IS_BROKEN = true;
15567
+ }
15568
+ }
15569
+
15570
+ (typeof window !== "undefined" && window !== null ? window : global).containsNode = function(parent, child) {
15571
+ if (parent === child) {
15572
+ return true;
15573
+ }
15574
+ if (parent.contains && !SAFARI_CONTAINS_IS_BROKEN) {
15575
+ return parent.contains(child);
15576
+ }
15577
+ if (parent.compareDocumentPosition) {
15578
+ return !!(parent.compareDocumentPosition(child) & 16);
15579
+ }
15580
+ while (child && parent !== child) {
15581
+ child = child.parentNode;
15582
+ }
15583
+ return child === parent;
15584
+ };
15585
+
15586
+ }).call(this);
15587
+
15588
+ (function() {
15589
+ Batman.extend(Batman.DOM, {
15590
+ querySelectorAll: function(node, selector) {
15591
+ return zest(selector, node);
15592
+ },
15593
+ querySelector: function(node, selector) {
15594
+ return zest(selector, node)[0];
15595
+ },
15596
+ setInnerHTML: function(node, html) {
15597
+ return node != null ? node.innerHTML = html : void 0;
15598
+ },
15599
+ containsNode: function(parent, child) {
15600
+ if (!child) {
15601
+ child = parent;
15602
+ parent = document.body;
15603
+ }
15604
+ return window.containsNode(parent, child);
15605
+ },
15606
+ textContent: function(node) {
15607
+ var _ref;
15608
+ return (_ref = node.textContent) != null ? _ref : node.innerText;
15609
+ },
15610
+ destroyNode: function(node) {
15611
+ var _ref;
15612
+ Batman.DOM.cleanupNode(node);
15613
+ return node != null ? (_ref = node.parentNode) != null ? _ref.removeChild(node) : void 0 : void 0;
15614
+ }
15615
+ });
15616
+
15617
+ Batman.extend(Batman.Request.prototype, {
15618
+ _parseResponseHeaders: function(xhr) {
15619
+ var headers;
15620
+ return headers = xhr.getAllResponseHeaders().split('\n').reduce(function(acc, header) {
15621
+ var key, matches, value;
15622
+ if (matches = header.match(/([^:]*):\s*(.*)/)) {
15623
+ key = matches[1];
15624
+ value = matches[2];
15625
+ acc[key] = value;
15626
+ }
15627
+ return acc;
15628
+ }, {});
15629
+ },
15630
+ send: function(data) {
15631
+ var options, xhr, _ref,
15632
+ _this = this;
15633
+ if (data == null) {
15634
+ data = this.get('data');
15635
+ }
15636
+ this.fire('loading');
15637
+ options = {
15638
+ url: this.get('url'),
15639
+ method: this.get('method'),
15640
+ type: this.get('type'),
15641
+ headers: this.get('headers'),
15642
+ success: function(response) {
15643
+ _this.mixin({
15644
+ xhr: xhr,
15645
+ response: response,
15646
+ status: typeof xhr !== "undefined" && xhr !== null ? xhr.status : void 0,
15647
+ responseHeaders: _this._parseResponseHeaders(xhr)
15648
+ });
15649
+ return _this.fire('success', response);
15650
+ },
15651
+ error: function(xhr) {
15652
+ _this.mixin({
15653
+ xhr: xhr,
15654
+ response: xhr.responseText || xhr.content,
15655
+ status: xhr.status,
15656
+ responseHeaders: _this._parseResponseHeaders(xhr)
15657
+ });
15658
+ xhr.request = _this;
15659
+ return _this.fire('error', xhr);
15660
+ },
15661
+ complete: function() {
15662
+ return _this.fire('loaded');
15663
+ }
15664
+ };
15665
+ if ((_ref = options.method) === 'PUT' || _ref === 'POST') {
15666
+ if (this.hasFileUploads()) {
15667
+ options.data = this.constructor.objectToFormData(data);
15668
+ } else {
15669
+ options.contentType = this.get('contentType');
15670
+ options.data = Batman.URI.queryFromParams(data);
15671
+ }
15672
+ } else {
15673
+ options.data = data;
15674
+ }
15675
+ return xhr = (reqwest(options)).request;
15676
+ }
15677
+ });
15678
+
15679
+ }).call(this);
15680
+
15681
+ (function() {
15682
+
15683
+
13243
15684
  }).call(this);