962107e9 by Michael Richards

Merge pull request #39 from mikeric/adapter-bypass

Additional syntax to bypass the adapter
2 parents 982e2361 15f661c9
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
3 // author: Michael Richards 3 // author: Michael Richards
4 // license: MIT 4 // license: MIT
5 (function() { 5 (function() {
6 var Rivets, attributeBinding, bidirectionals, bindEvent, eventBinding, getInputValue, rivets, unbindEvent, 6 var Rivets, attributeBinding, bindEvent, eventBinding, getInputValue, rivets, unbindEvent,
7 __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, 7 __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
8 __slice = [].slice, 8 __slice = [].slice,
9 __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; }; 9 __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; };
...@@ -18,13 +18,12 @@ ...@@ -18,13 +18,12 @@
18 18
19 Rivets.Binding = (function() { 19 Rivets.Binding = (function() {
20 20
21 function Binding(el, type, bindType, model, keypath, formatters) { 21 function Binding(el, type, model, keypath, options) {
22 this.el = el; 22 this.el = el;
23 this.type = type; 23 this.type = type;
24 this.bindType = bindType;
25 this.model = model; 24 this.model = model;
26 this.keypath = keypath; 25 this.keypath = keypath;
27 this.formatters = formatters != null ? formatters : []; 26 this.options = options != null ? options : {};
28 this.unbind = __bind(this.unbind, this); 27 this.unbind = __bind(this.unbind, this);
29 28
30 this.publish = __bind(this.publish, this); 29 this.publish = __bind(this.publish, this);
...@@ -35,41 +34,52 @@ ...@@ -35,41 +34,52 @@
35 34
36 this.formattedValue = __bind(this.formattedValue, this); 35 this.formattedValue = __bind(this.formattedValue, this);
37 36
38 if (this.bindType === "event") { 37 if (this.options.special === "event") {
39 this.routine = eventBinding(this.type); 38 this.routine = eventBinding(this.type);
40 } else { 39 } else {
41 this.routine = Rivets.routines[this.type] || attributeBinding(this.type); 40 this.routine = Rivets.routines[this.type] || attributeBinding(this.type);
42 } 41 }
42 this.formatters = this.options.formatters || [];
43 } 43 }
44 44
45 Binding.prototype.bidirectionals = ['value', 'checked', 'unchecked'];
46
45 Binding.prototype.formattedValue = function(value) { 47 Binding.prototype.formattedValue = function(value) {
46 var args, formatter, id, _i, _len, _ref, _ref1; 48 var args, formatter, id, _i, _len, _ref, _ref1, _ref2;
47 _ref = this.formatters; 49 _ref = this.formatters;
48 for (_i = 0, _len = _ref.length; _i < _len; _i++) { 50 for (_i = 0, _len = _ref.length; _i < _len; _i++) {
49 formatter = _ref[_i]; 51 formatter = _ref[_i];
50 args = formatter.split(/\s+/); 52 args = formatter.split(/\s+/);
51 id = args.shift(); 53 id = args.shift();
52 value = (_ref1 = Rivets.config.formatters)[id].apply(_ref1, [value].concat(__slice.call(args))); 54 value = Rivets.config.formatters && Rivets.config.formatters[id] ? (_ref1 = Rivets.config.formatters)[id].apply(_ref1, [value].concat(__slice.call(args))) : (_ref2 = this.model)[id].apply(_ref2, [value].concat(__slice.call(args)));
53 } 55 }
54 return value; 56 return value;
55 }; 57 };
56 58
57 Binding.prototype.set = function(value) { 59 Binding.prototype.set = function(value) {
58 value = this.formattedValue(value); 60 value = this.formattedValue(value);
59 if (this.bindType === "event") { 61 if (this.options.special === "event") {
60 this.routine(this.el, value, this.currentListener); 62 this.routine(this.el, value, this.currentListener);
61 return this.currentListener = value; 63 return this.currentListener = value;
62 } else { 64 } else {
65 if (value instanceof Function) {
66 value = value();
67 }
63 return this.routine(this.el, value); 68 return this.routine(this.el, value);
64 } 69 }
65 }; 70 };
66 71
67 Binding.prototype.bind = function() { 72 Binding.prototype.bind = function() {
68 Rivets.config.adapter.subscribe(this.model, this.keypath, this.set); 73 var _ref;
69 if (Rivets.config.preloadData) { 74 if (this.options.bypass) {
70 this.set(Rivets.config.adapter.read(this.model, this.keypath)); 75 this.set(this.model[this.keypath]);
76 } else {
77 Rivets.config.adapter.subscribe(this.model, this.keypath, this.set);
78 if (Rivets.config.preloadData) {
79 this.set(Rivets.config.adapter.read(this.model, this.keypath));
80 }
71 } 81 }
72 if (this.bindType === "bidirectional") { 82 if (_ref = this.type, __indexOf.call(this.bidirectionals, _ref) >= 0) {
73 return bindEvent(this.el, 'change', this.publish); 83 return bindEvent(this.el, 'change', this.publish);
74 } 84 }
75 }; 85 };
...@@ -81,8 +91,9 @@ ...@@ -81,8 +91,9 @@
81 }; 91 };
82 92
83 Binding.prototype.unbind = function() { 93 Binding.prototype.unbind = function() {
94 var _ref;
84 Rivets.config.adapter.unsubscribe(this.model, this.keypath, this.set); 95 Rivets.config.adapter.unsubscribe(this.model, this.keypath, this.set);
85 if (this.bindType === "bidirectional") { 96 if (_ref = this.type, __indexOf.call(this.bidirectionals, _ref) >= 0) {
86 return this.el.removeEventListener('change', this.publish); 97 return this.el.removeEventListener('change', this.publish);
87 } 98 }
88 }; 99 };
...@@ -127,13 +138,13 @@ ...@@ -127,13 +138,13 @@
127 bindingRegExp = this.bindingRegExp(); 138 bindingRegExp = this.bindingRegExp();
128 eventRegExp = /^on-/; 139 eventRegExp = /^on-/;
129 parseNode = function(node) { 140 parseNode = function(node) {
130 var attribute, bindType, keypath, model, path, pipe, pipes, type, _i, _len, _ref, _results; 141 var attribute, keypath, model, options, path, pipe, pipes, type, _i, _len, _ref, _results;
131 _ref = node.attributes; 142 _ref = node.attributes;
132 _results = []; 143 _results = [];
133 for (_i = 0, _len = _ref.length; _i < _len; _i++) { 144 for (_i = 0, _len = _ref.length; _i < _len; _i++) {
134 attribute = _ref[_i]; 145 attribute = _ref[_i];
135 if (bindingRegExp.test(attribute.name)) { 146 if (bindingRegExp.test(attribute.name)) {
136 bindType = "attribute"; 147 options = {};
137 type = attribute.name.replace(bindingRegExp, ''); 148 type = attribute.name.replace(bindingRegExp, '');
138 pipes = (function() { 149 pipes = (function() {
139 var _j, _len1, _ref1, _results1; 150 var _j, _len1, _ref1, _results1;
...@@ -145,16 +156,16 @@ ...@@ -145,16 +156,16 @@
145 } 156 }
146 return _results1; 157 return _results1;
147 })(); 158 })();
148 path = pipes.shift().split('.'); 159 path = pipes.shift().split(/(\.|:)/);
160 options.formatters = pipes;
149 model = _this.models[path.shift()]; 161 model = _this.models[path.shift()];
150 keypath = path.join('.'); 162 options.bypass = path.shift() === ':';
163 keypath = path.join();
151 if (eventRegExp.test(type)) { 164 if (eventRegExp.test(type)) {
152 type = type.replace(eventRegExp, ''); 165 type = type.replace(eventRegExp, '');
153 bindType = "event"; 166 options.special = "event";
154 } else if (__indexOf.call(bidirectionals, type) >= 0) {
155 bindType = "bidirectional";
156 } 167 }
157 _results.push(_this.bindings.push(new Rivets.Binding(node, type, bindType, model, keypath, pipes))); 168 _results.push(_this.bindings.push(new Rivets.Binding(node, type, model, keypath, options)));
158 } else { 169 } else {
159 _results.push(void 0); 170 _results.push(void 0);
160 } 171 }
...@@ -256,8 +267,6 @@ ...@@ -256,8 +267,6 @@
256 }; 267 };
257 }; 268 };
258 269
259 bidirectionals = ['value', 'checked', 'unchecked'];
260
261 Rivets.routines = { 270 Rivets.routines = {
262 enabled: function(el, value) { 271 enabled: function(el, value) {
263 return el.disabled = !value; 272 return el.disabled = !value;
......
...@@ -2,4 +2,4 @@ ...@@ -2,4 +2,4 @@
2 // version: 0.2.5 2 // version: 0.2.5
3 // author: Michael Richards 3 // author: Michael Richards
4 // license: MIT 4 // license: MIT
5 (function(){var a,b,c,d,e,f,g,h,i=function(a,b){return function(){return a.apply(b,arguments)}},j=[].slice,k=[].indexOf||function(a){for(var b=0,c=this.length;b<c;b++)if(b in this&&this[b]===a)return b;return-1};a={},String.prototype.trim||(String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g,"")}),a.Binding=function(){function c(c,d,f,g,h,j){this.el=c,this.type=d,this.bindType=f,this.model=g,this.keypath=h,this.formatters=j!=null?j:[],this.unbind=i(this.unbind,this),this.publish=i(this.publish,this),this.bind=i(this.bind,this),this.set=i(this.set,this),this.formattedValue=i(this.formattedValue,this),this.bindType==="event"?this.routine=e(this.type):this.routine=a.routines[this.type]||b(this.type)}return c.prototype.formattedValue=function(b){var c,d,e,f,g,h,i;h=this.formatters;for(f=0,g=h.length;f<g;f++)d=h[f],c=d.split(/\s+/),e=c.shift(),b=(i=a.config.formatters)[e].apply(i,[b].concat(j.call(c)));return b},c.prototype.set=function(a){return a=this.formattedValue(a),this.bindType==="event"?(this.routine(this.el,a,this.currentListener),this.currentListener=a):this.routine(this.el,a)},c.prototype.bind=function(){a.config.adapter.subscribe(this.model,this.keypath,this.set),a.config.preloadData&&this.set(a.config.adapter.read(this.model,this.keypath));if(this.bindType==="bidirectional")return d(this.el,"change",this.publish)},c.prototype.publish=function(b){var c;return c=b.target||b.srcElement,a.config.adapter.publish(this.model,this.keypath,f(c))},c.prototype.unbind=function(){a.config.adapter.unsubscribe(this.model,this.keypath,this.set);if(this.bindType==="bidirectional")return this.el.removeEventListener("change",this.publish)},c}(),a.View=function(){function b(a,b){this.els=a,this.models=b,this.unbind=i(this.unbind,this),this.bind=i(this.bind,this),this.build=i(this.build,this),this.bindingRegExp=i(this.bindingRegExp,this),this.els.jquery||this.els instanceof Array||(this.els=[this.els]),this.build()}return b.prototype.bindingRegExp=function(){var b;return b=a.config.prefix,b?new RegExp("^data-"+b+"-"):/^data-/},b.prototype.build=function(){var b,d,e,f,g,h,i,j,l,m=this;this.bindings=[],b=this.bindingRegExp(),e=/^on-/,g=function(d){var f,g,h,i,j,l,n,o,p,q,r,s;r=d.attributes,s=[];for(p=0,q=r.length;p<q;p++)f=r[p],b.test(f.name)?(g="attribute",o=f.name.replace(b,""),n=function(){var a,b,c,d;c=f.value.split("|"),d=[];for(a=0,b=c.length;a<b;a++)l=c[a],d.push(l.trim());return d}(),j=n.shift().split("."),i=m.models[j.shift()],h=j.join("."),e.test(o)?(o=o.replace(e,""),g="event"):k.call(c,o)>=0&&(g="bidirectional"),s.push(m.bindings.push(new a.Binding(d,o,g,i,h,n)))):s.push(void 0);return s},j=this.els,l=[];for(h=0,i=j.length;h<i;h++)d=j[h],g(d),l.push(function(){var a,b,c,e;c=d.getElementsByTagName("*"),e=[];for(a=0,b=c.length;a<b;a++)f=c[a],e.push(g(f));return e}());return l},b.prototype.bind=function(){var a,b,c,d,e;d=this.bindings,e=[];for(b=0,c=d.length;b<c;b++)a=d[b],e.push(a.bind());return e},b.prototype.unbind=function(){var a,b,c,d,e;d=this.bindings,e=[];for(b=0,c=d.length;b<c;b++)a=d[b],e.push(a.unbind());return e},b}(),d=function(a,b,c){return window.addEventListener?a.addEventListener(b,c):a.attachEvent(b,c)},h=function(a,b,c){return window.removeEventListener?a.removeEventListener(b,c):a.detachEvent(b,c)},f=function(a){switch(a.type){case"text":case"textarea":case"password":case"select-one":case"radio":return a.value;case"checkbox":return a.checked}},e=function(a){return function(b,c,e){c&&d(b,a,c);if(e)return h(b,a,e)}},b=function(a){return function(b,c){return c?b.setAttribute(a,c):b.removeAttribute(a)}},c=["value","checked","unchecked"],a.routines={enabled:function(a,b){return a.disabled=!b},disabled:function(a,b){return a.disabled=!!b},checked:function(a,b){return a.type==="radio"?a.checked=a.value===b:a.checked=!!b},unchecked:function(a,b){return a.type==="radio"?a.checked=a.value!==b:a.checked=!b},show:function(a,b){return a.style.display=b?"":"none"},hide:function(a,b){return a.style.display=b?"none":""},html:function(a,b){return a.innerHTML=b||""},value:function(a,b){return a.value=b||""},text:function(a,b){return a.innerText!=null?a.innerText=b||"":a.textContent=b||""}},a.config={preloadData:!0},g={routines:a.routines,config:a.config,configure:function(b){var c,d,e;b==null&&(b={}),e=[];for(c in b)d=b[c],e.push(a.config[c]=d);return e},bind:function(b,c){var d;return c==null&&(c={}),d=new a.View(b,c),d.bind(),d}},typeof module!="undefined"&&module!==null?module.exports=g:this.rivets=g}).call(this);
...\ No newline at end of file ...\ No newline at end of file
5 (function(){var a,b,c,d,e,f,g,h=function(a,b){return function(){return a.apply(b,arguments)}},i=[].slice,j=[].indexOf||function(a){for(var b=0,c=this.length;b<c;b++)if(b in this&&this[b]===a)return b;return-1};a={},String.prototype.trim||(String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g,"")}),a.Binding=function(){function f(c,e,f,g,i){this.el=c,this.type=e,this.model=f,this.keypath=g,this.options=i!=null?i:{},this.unbind=h(this.unbind,this),this.publish=h(this.publish,this),this.bind=h(this.bind,this),this.set=h(this.set,this),this.formattedValue=h(this.formattedValue,this),this.options.special==="event"?this.routine=d(this.type):this.routine=a.routines[this.type]||b(this.type),this.formatters=this.options.formatters||[]}return f.prototype.bidirectionals=["value","checked","unchecked"],f.prototype.formattedValue=function(b){var c,d,e,f,g,h,j,k;h=this.formatters;for(f=0,g=h.length;f<g;f++)d=h[f],c=d.split(/\s+/),e=c.shift(),b=a.config.formatters&&a.config.formatters[e]?(j=a.config.formatters)[e].apply(j,[b].concat(i.call(c))):(k=this.model)[e].apply(k,[b].concat(i.call(c)));return b},f.prototype.set=function(a){return a=this.formattedValue(a),this.options.special==="event"?(this.routine(this.el,a,this.currentListener),this.currentListener=a):(a instanceof Function&&(a=a()),this.routine(this.el,a))},f.prototype.bind=function(){var b;this.options.bypass?this.set(this.model[this.keypath]):(a.config.adapter.subscribe(this.model,this.keypath,this.set),a.config.preloadData&&this.set(a.config.adapter.read(this.model,this.keypath)));if(b=this.type,j.call(this.bidirectionals,b)>=0)return c(this.el,"change",this.publish)},f.prototype.publish=function(b){var c;return c=b.target||b.srcElement,a.config.adapter.publish(this.model,this.keypath,e(c))},f.prototype.unbind=function(){var b;a.config.adapter.unsubscribe(this.model,this.keypath,this.set);if(b=this.type,j.call(this.bidirectionals,b)>=0)return this.el.removeEventListener("change",this.publish)},f}(),a.View=function(){function b(a,b){this.els=a,this.models=b,this.unbind=h(this.unbind,this),this.bind=h(this.bind,this),this.build=h(this.build,this),this.bindingRegExp=h(this.bindingRegExp,this),this.els.jquery||this.els instanceof Array||(this.els=[this.els]),this.build()}return b.prototype.bindingRegExp=function(){var b;return b=a.config.prefix,b?new RegExp("^data-"+b+"-"):/^data-/},b.prototype.build=function(){var b,c,d,e,f,g,h,i,j,k=this;this.bindings=[],b=this.bindingRegExp(),d=/^on-/,f=function(c){var e,f,g,h,i,j,l,m,n,o,p,q;p=c.attributes,q=[];for(n=0,o=p.length;n<o;n++)e=p[n],b.test(e.name)?(h={},m=e.name.replace(b,""),l=function(){var a,b,c,d;c=e.value.split("|"),d=[];for(a=0,b=c.length;a<b;a++)j=c[a],d.push(j.trim());return d}(),i=l.shift().split(/(\.|:)/),h.formatters=l,g=k.models[i.shift()],h.bypass=i.shift()===":",f=i.join(),d.test(m)&&(m=m.replace(d,""),h.special="event"),q.push(k.bindings.push(new a.Binding(c,m,g,f,h)))):q.push(void 0);return q},i=this.els,j=[];for(g=0,h=i.length;g<h;g++)c=i[g],f(c),j.push(function(){var a,b,d,g;d=c.getElementsByTagName("*"),g=[];for(a=0,b=d.length;a<b;a++)e=d[a],g.push(f(e));return g}());return j},b.prototype.bind=function(){var a,b,c,d,e;d=this.bindings,e=[];for(b=0,c=d.length;b<c;b++)a=d[b],e.push(a.bind());return e},b.prototype.unbind=function(){var a,b,c,d,e;d=this.bindings,e=[];for(b=0,c=d.length;b<c;b++)a=d[b],e.push(a.unbind());return e},b}(),c=function(a,b,c){return window.addEventListener?a.addEventListener(b,c):a.attachEvent(b,c)},g=function(a,b,c){return window.removeEventListener?a.removeEventListener(b,c):a.detachEvent(b,c)},e=function(a){switch(a.type){case"text":case"textarea":case"password":case"select-one":case"radio":return a.value;case"checkbox":return a.checked}},d=function(a){return function(b,d,e){d&&c(b,a,d);if(e)return g(b,a,e)}},b=function(a){return function(b,c){return c?b.setAttribute(a,c):b.removeAttribute(a)}},a.routines={enabled:function(a,b){return a.disabled=!b},disabled:function(a,b){return a.disabled=!!b},checked:function(a,b){return a.type==="radio"?a.checked=a.value===b:a.checked=!!b},unchecked:function(a,b){return a.type==="radio"?a.checked=a.value!==b:a.checked=!b},show:function(a,b){return a.style.display=b?"":"none"},hide:function(a,b){return a.style.display=b?"none":""},html:function(a,b){return a.innerHTML=b||""},value:function(a,b){return a.value=b||""},text:function(a,b){return a.innerText!=null?a.innerText=b||"":a.textContent=b||""}},a.config={preloadData:!0},f={routines:a.routines,config:a.config,configure:function(b){var c,d,e;b==null&&(b={}),e=[];for(c in b)d=b[c],e.push(a.config[c]=d);return e},bind:function(b,c){var d;return c==null&&(c={}),d=new a.View(b,c),d.bind(),d}},typeof module!="undefined"&&module!==null?module.exports=f:this.rivets=f}).call(this);
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -22,6 +22,41 @@ describe('Rivets.Binding', function() { ...@@ -22,6 +22,41 @@ describe('Rivets.Binding', function() {
22 expect(binding.routine).toBe(rivets.routines.text); 22 expect(binding.routine).toBe(rivets.routines.text);
23 }); 23 });
24 24
25 describe('bind()', function() {
26 it('subscribes to the model for changes via the adapter', function() {
27 spyOn(rivets.config.adapter, 'subscribe');
28 binding.bind();
29 expect(rivets.config.adapter.subscribe).toHaveBeenCalled();
30 });
31
32 describe('with preloadData set to true', function() {
33 beforeEach(function() {
34 rivets.config.preloadData = true;
35 });
36
37 it('sets the initial value via the adapter', function() {
38 spyOn(binding, 'set');
39 spyOn(rivets.config.adapter, 'read');
40 binding.bind();
41 expect(binding.set).toHaveBeenCalled();
42 expect(rivets.config.adapter.read).toHaveBeenCalled();
43 });
44 });
45
46 describe('with the bypass option set to true', function() {
47 beforeEach(function() {
48 binding.options.bypass = true;
49 });
50
51 it('sets the initial value from the model directly', function() {
52 spyOn(binding, 'set');
53 binding.model.name = 'espresso';
54 binding.bind();
55 expect(binding.set).toHaveBeenCalledWith('espresso');
56 });
57 });
58 });
59
25 describe('set()', function() { 60 describe('set()', function() {
26 it('performs the binding routine with the supplied value', function() { 61 it('performs the binding routine with the supplied value', function() {
27 spyOn(binding, 'routine'); 62 spyOn(binding, 'routine');
...@@ -41,7 +76,7 @@ describe('Rivets.Binding', function() { ...@@ -41,7 +76,7 @@ describe('Rivets.Binding', function() {
41 76
42 describe('on an event binding', function() { 77 describe('on an event binding', function() {
43 beforeEach(function() { 78 beforeEach(function() {
44 binding.bindType = 'event'; 79 binding.options.special = 'event';
45 }); 80 });
46 81
47 it('performs the binding routine with the supplied function and current listener', function() { 82 it('performs the binding routine with the supplied function and current listener', function() {
......
...@@ -12,14 +12,20 @@ unless String::trim then String::trim = -> @replace /^\s+|\s+$/g, "" ...@@ -12,14 +12,20 @@ unless String::trim then String::trim = -> @replace /^\s+|\s+$/g, ""
12 # A single binding between a model attribute and a DOM element. 12 # A single binding between a model attribute and a DOM element.
13 class Rivets.Binding 13 class Rivets.Binding
14 # All information about the binding is passed into the constructor; the DOM 14 # All information about the binding is passed into the constructor; the DOM
15 # element, the routine identifier, the model object and the keypath at which 15 # element, the type of binding, the model object and the keypath at which
16 # to listen for changes. 16 # to listen for changes.
17 constructor: (@el, @type, @bindType, @model, @keypath, @formatters = []) -> 17 constructor: (@el, @type, @model, @keypath, @options = {}) ->
18 if @bindType is "event" 18 if @options.special is "event"
19 @routine = eventBinding @type 19 @routine = eventBinding @type
20 else 20 else
21 @routine = Rivets.routines[@type] || attributeBinding @type 21 @routine = Rivets.routines[@type] || attributeBinding @type
22 22
23 @formatters = @options.formatters || []
24
25 # Bindings that should also observe the DOM element for changes in order to
26 # propagate those changes back to the model object.
27 bidirectionals: ['value', 'checked', 'unchecked']
28
23 # Applies all the current formatters to the supplied value and returns the 29 # Applies all the current formatters to the supplied value and returns the
24 # formatted value. 30 # formatted value.
25 formattedValue: (value) => 31 formattedValue: (value) =>
...@@ -38,22 +44,26 @@ class Rivets.Binding ...@@ -38,22 +44,26 @@ class Rivets.Binding
38 set: (value) => 44 set: (value) =>
39 value = @formattedValue value 45 value = @formattedValue value
40 46
41 if @bindType is "event" 47 if @options.special is "event"
42 @routine @el, value, @currentListener 48 @routine @el, value, @currentListener
43 @currentListener = value 49 @currentListener = value
44 else 50 else
51 value = value() if value instanceof Function
45 @routine @el, value 52 @routine @el, value
46 53
47 # Subscribes to the model for changes at the specified keypath. Bi-directional 54 # Subscribes to the model for changes at the specified keypath. Bi-directional
48 # routines will also listen for changes on the element to propagate them back 55 # routines will also listen for changes on the element to propagate them back
49 # to the model. 56 # to the model.
50 bind: => 57 bind: =>
51 Rivets.config.adapter.subscribe @model, @keypath, @set 58 if @options.bypass
59 @set @model[@keypath]
60 else
61 Rivets.config.adapter.subscribe @model, @keypath, @set
52 62
53 if Rivets.config.preloadData 63 if Rivets.config.preloadData
54 @set Rivets.config.adapter.read @model, @keypath 64 @set Rivets.config.adapter.read @model, @keypath
55 65
56 if @bindType is "bidirectional" 66 if @type in @bidirectionals
57 bindEvent @el, 'change', @publish 67 bindEvent @el, 'change', @publish
58 68
59 # Publishes the value currently set on the input element back to the model. 69 # Publishes the value currently set on the input element back to the model.
...@@ -65,7 +75,7 @@ class Rivets.Binding ...@@ -65,7 +75,7 @@ class Rivets.Binding
65 unbind: => 75 unbind: =>
66 Rivets.config.adapter.unsubscribe @model, @keypath, @set 76 Rivets.config.adapter.unsubscribe @model, @keypath, @set
67 77
68 if @bindType is "bidirectional" 78 if @type in @bidirectionals
69 @el.removeEventListener 'change', @publish 79 @el.removeEventListener 'change', @publish
70 80
71 # A collection of bindings built from a set of parent elements. 81 # A collection of bindings built from a set of parent elements.
...@@ -90,20 +100,21 @@ class Rivets.View ...@@ -90,20 +100,21 @@ class Rivets.View
90 parseNode = (node) => 100 parseNode = (node) =>
91 for attribute in node.attributes 101 for attribute in node.attributes
92 if bindingRegExp.test attribute.name 102 if bindingRegExp.test attribute.name
93 bindType = "attribute" 103 options = {}
104
94 type = attribute.name.replace bindingRegExp, '' 105 type = attribute.name.replace bindingRegExp, ''
95 pipes = (pipe.trim() for pipe in attribute.value.split '|') 106 pipes = (pipe.trim() for pipe in attribute.value.split '|')
96 path = pipes.shift().split '.' 107 path = pipes.shift().split(/(\.|:)/)
108 options.formatters = pipes
97 model = @models[path.shift()] 109 model = @models[path.shift()]
98 keypath = path.join '.' 110 options.bypass = path.shift() is ':'
111 keypath = path.join()
99 112
100 if eventRegExp.test type 113 if eventRegExp.test type
101 type = type.replace eventRegExp, '' 114 type = type.replace eventRegExp, ''
102 bindType = "event" 115 options.special = "event"
103 else if type in bidirectionals
104 bindType = "bidirectional"
105 116
106 @bindings.push new Rivets.Binding node, type, bindType, model, keypath, pipes 117 @bindings.push new Rivets.Binding node, type, model, keypath, options
107 118
108 for el in @els 119 for el in @els
109 parseNode el 120 parseNode el
...@@ -140,7 +151,7 @@ getInputValue = (el) -> ...@@ -140,7 +151,7 @@ getInputValue = (el) ->
140 when 'text', 'textarea', 'password', 'select-one', 'radio' then el.value 151 when 'text', 'textarea', 'password', 'select-one', 'radio' then el.value
141 when 'checkbox' then el.checked 152 when 'checkbox' then el.checked
142 153
143 # Returns an element binding routine for the specified attribute. 154 # Returns an event binding routine for the specified event.
144 eventBinding = (event) -> (el, bind, unbind) -> 155 eventBinding = (event) -> (el, bind, unbind) ->
145 bindEvent el, event, bind if bind 156 bindEvent el, event, bind if bind
146 unbindEvent el, event, unbind if unbind 157 unbindEvent el, event, unbind if unbind
...@@ -150,10 +161,6 @@ eventBinding = (event) -> (el, bind, unbind) -> ...@@ -150,10 +161,6 @@ eventBinding = (event) -> (el, bind, unbind) ->
150 attributeBinding = (attr) -> (el, value) -> 161 attributeBinding = (attr) -> (el, value) ->
151 if value then el.setAttribute attr, value else el.removeAttribute attr 162 if value then el.setAttribute attr, value else el.removeAttribute attr
152 163
153 # Bindings that should also be observed for changes on the DOM element in order
154 # to propagate those changes back to the model object.
155 bidirectionals = ['value', 'checked', 'unchecked']
156
157 # Core binding routines. 164 # Core binding routines.
158 Rivets.routines = 165 Rivets.routines =
159 enabled: (el, value) -> 166 enabled: (el, value) ->
......