Merge pull request #39 from mikeric/adapter-bypass
Additional syntax to bypass the adapter
Showing
4 changed files
with
91 additions
and
40 deletions
... | @@ -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() { |
73 | var _ref; | ||
74 | if (this.options.bypass) { | ||
75 | this.set(this.model[this.keypath]); | ||
76 | } else { | ||
68 | Rivets.config.adapter.subscribe(this.model, this.keypath, this.set); | 77 | Rivets.config.adapter.subscribe(this.model, this.keypath, this.set); |
69 | if (Rivets.config.preloadData) { | 78 | if (Rivets.config.preloadData) { |
70 | this.set(Rivets.config.adapter.read(this.model, this.keypath)); | 79 | this.set(Rivets.config.adapter.read(this.model, this.keypath)); |
71 | } | 80 | } |
72 | if (this.bindType === "bidirectional") { | 81 | } |
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: => |
58 | if @options.bypass | ||
59 | @set @model[@keypath] | ||
60 | else | ||
51 | Rivets.config.adapter.subscribe @model, @keypath, @set | 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) -> | ... | ... |
-
Please register or sign in to post a comment