878d3bbe by Michael Richards

Merge pull request #28 from mikeric/unbinding-listeners

Ability to unbind the listeners applied from a Rivets.Binding.
2 parents 3709889f 851640a0
1 # Rivets.js 1 # Rivets.js
2 2
3 Rivets.js is a declarative data binding facility that plays well with existing frameworks such as [Backbone.js](http://backbonejs.org), [Spine.js](http://spinejs.com) and [Stapes.js](http://hay.github.com/stapes/). It aims to be lightweight (1.2KB minified and gzipped), extensible, and configurable to work with any event-driven model. 3 Rivets.js is a declarative data binding facility that plays well with existing frameworks such as [Backbone.js](http://backbonejs.org), [Spine.js](http://spinejs.com) and [Stapes.js](http://hay.github.com/stapes/). It aims to be lightweight (1.4KB minified and gzipped), extensible, and configurable to work with any event-driven model.
4 4
5 --- 5 ---
6 6
...@@ -43,8 +43,12 @@ Rivets.js is model interface-agnostic, meaning it can work with any event-driven ...@@ -43,8 +43,12 @@ Rivets.js is model interface-agnostic, meaning it can work with any event-driven
43 rivets.configure({ 43 rivets.configure({
44 adapter: { 44 adapter: {
45 subscribe: function(obj, keypath, callback) { 45 subscribe: function(obj, keypath, callback) {
46 obj.on('change:' + keypath, function(m, v) { callback(v) }); 46 callback.wrapped = function(m, v) { callback(v) };
47 obj.on('change:' + keypath, callback.wrapped);
47 }, 48 },
49 unsubscribe: function(obj, keypath, callback) {
50 obj.off('change:' + keypath, callback.wrapped);
51 }
48 read: function(obj, keypath) { 52 read: function(obj, keypath) {
49 return obj.get(keypath); 53 return obj.get(keypath);
50 }, 54 },
......
...@@ -20,6 +20,8 @@ ...@@ -20,6 +20,8 @@
20 this.model = model; 20 this.model = model;
21 this.keypath = keypath; 21 this.keypath = keypath;
22 this.formatters = formatters != null ? formatters : []; 22 this.formatters = formatters != null ? formatters : [];
23 this.unbind = __bind(this.unbind, this);
24
23 this.publish = __bind(this.publish, this); 25 this.publish = __bind(this.publish, this);
24 26
25 this.bind = __bind(this.bind, this); 27 this.bind = __bind(this.bind, this);
...@@ -64,6 +66,13 @@ ...@@ -64,6 +66,13 @@
64 return Rivets.config.adapter.publish(this.model, this.keypath, getInputValue(el)); 66 return Rivets.config.adapter.publish(this.model, this.keypath, getInputValue(el));
65 }; 67 };
66 68
69 Binding.prototype.unbind = function() {
70 Rivets.config.adapter.unsubscribe(this.model, this.keypath, this.set);
71 if (this.bindType === "bidirectional") {
72 return this.el.removeEventListener('change', this.publish);
73 }
74 };
75
67 return Binding; 76 return Binding;
68 77
69 })(); 78 })();
...@@ -73,6 +82,8 @@ ...@@ -73,6 +82,8 @@
73 function View(el, models) { 82 function View(el, models) {
74 this.el = el; 83 this.el = el;
75 this.models = models; 84 this.models = models;
85 this.unbind = __bind(this.unbind, this);
86
76 this.bind = __bind(this.bind, this); 87 this.bind = __bind(this.bind, this);
77 88
78 this.build = __bind(this.build, this); 89 this.build = __bind(this.build, this);
...@@ -154,6 +165,17 @@ ...@@ -154,6 +165,17 @@
154 return _results; 165 return _results;
155 }; 166 };
156 167
168 View.prototype.unbind = function() {
169 var binding, _i, _len, _ref, _results;
170 _ref = this.bindings;
171 _results = [];
172 for (_i = 0, _len = _ref.length; _i < _len; _i++) {
173 binding = _ref[_i];
174 _results.push(binding.unbind());
175 }
176 return _results;
177 };
178
157 return View; 179 return View;
158 180
159 })(); 181 })();
......
1 (function(){var a,b,c,d,e,f,g,h,i=function(a,b){return function(){return a.apply(b,arguments)}},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 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.publish=i(this.publish,this),this.bind=i(this.bind,this),this.set=i(this.set,this),this.bindType==="event"?this.routine=e(this.type):this.routine=a.routines[this.type]||b(this.type)}return c.prototype.set=function(b){var c,d,e,f;f=this.formatters;for(d=0,e=f.length;d<e;d++)c=f[d],b=a.config.formatters[c](b);return this.bindType==="event"?(this.routine(this.el,b,this.currentListener),this.currentListener=b):this.routine(this.el,b)},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}(),a.View=function(){function b(a,b){this.el=a,this.models=b,this.bind=i(this.bind,this),this.build=i(this.build,this),this.bindingRegExp=i(this.bindingRegExp,this),this.el.jquery&&(this.el=this.el.get(0)),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,k,l,m,n,o,p,q,r;this.bindings=[],e=this.bindingRegExp(),f=/^on-/,q=this.el.getElementsByTagName("*"),r=[];for(o=0,p=q.length;o<p;o++)i=q[o],r.push(function(){var o,p,q,r;q=i.attributes,r=[];for(o=0,p=q.length;o<p;o++)b=q[o],e.test(b.name)?(d="attribute",n=b.name.replace(e,""),m=function(){var a,c,d,e;d=b.value.split("|"),e=[];for(a=0,c=d.length;a<c;a++)l=d[a],e.push(l.trim());return e}(),k=m.shift().split("."),h=this.models[k.shift()],g=k.join("."),f.test(n)?(n=n.replace(f,""),d="event"):j.call(c,n)>=0&&(d="bidirectional"),r.push(this.bindings.push(new a.Binding(i,n,d,h,g,m)))):r.push(void 0);return r}.call(this));return r},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}(),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":return a.value;case"checkbox":case"radio":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","selected","unselected"],a.routines={enabled:function(a,b){return a.disabled=!b},disabled:function(a,b){return a.disabled=!!b},checked:function(a,b){return a.checked=!!b},unchecked:function(a,b){return a.checked=!b},selected:function(a,b){return a.selected=!!b},unselected:function(a,b){return a.selected=!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={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},register:function(b,c){return a.routines[b]=c},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
1 (function(){var a,b,c,d,e,f,g,h,i=function(a,b){return function(){return a.apply(b,arguments)}},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 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.bindType==="event"?this.routine=e(this.type):this.routine=a.routines[this.type]||b(this.type)}return c.prototype.set=function(b){var c,d,e,f;f=this.formatters;for(d=0,e=f.length;d<e;d++)c=f[d],b=a.config.formatters[c](b);return this.bindType==="event"?(this.routine(this.el,b,this.currentListener),this.currentListener=b):this.routine(this.el,b)},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.el=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.el.jquery&&(this.el=this.el.get(0)),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,k,l,m,n,o,p,q,r;this.bindings=[],e=this.bindingRegExp(),f=/^on-/,q=this.el.getElementsByTagName("*"),r=[];for(o=0,p=q.length;o<p;o++)i=q[o],r.push(function(){var o,p,q,r;q=i.attributes,r=[];for(o=0,p=q.length;o<p;o++)b=q[o],e.test(b.name)?(d="attribute",n=b.name.replace(e,""),m=function(){var a,c,d,e;d=b.value.split("|"),e=[];for(a=0,c=d.length;a<c;a++)l=d[a],e.push(l.trim());return e}(),k=m.shift().split("."),h=this.models[k.shift()],g=k.join("."),f.test(n)?(n=n.replace(f,""),d="event"):j.call(c,n)>=0&&(d="bidirectional"),r.push(this.bindings.push(new a.Binding(i,n,d,h,g,m)))):r.push(void 0);return r}.call(this));return r},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":return a.value;case"checkbox":case"radio":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","selected","unselected"],a.routines={enabled:function(a,b){return a.disabled=!b},disabled:function(a,b){return a.disabled=!!b},checked:function(a,b){return a.checked=!!b},unchecked:function(a,b){return a.checked=!b},selected:function(a,b){return a.selected=!!b},unselected:function(a,b){return a.selected=!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={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},register:function(b,c){return a.routines[b]=c},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
......
...@@ -49,6 +49,13 @@ class Rivets.Binding ...@@ -49,6 +49,13 @@ class Rivets.Binding
49 el = e.target or e.srcElement 49 el = e.target or e.srcElement
50 Rivets.config.adapter.publish @model, @keypath, getInputValue el 50 Rivets.config.adapter.publish @model, @keypath, getInputValue el
51 51
52 # Unsubscribes from the model and the element.
53 unbind: =>
54 Rivets.config.adapter.unsubscribe @model, @keypath, @set
55
56 if @bindType is "bidirectional"
57 @el.removeEventListener 'change', @publish
58
52 # A collection of bindings for a parent element. 59 # A collection of bindings for a parent element.
53 class Rivets.View 60 class Rivets.View
54 # The parent DOM element and the model objects for binding are passed into the 61 # The parent DOM element and the model objects for binding are passed into the
...@@ -89,6 +96,10 @@ class Rivets.View ...@@ -89,6 +96,10 @@ class Rivets.View
89 bind: => 96 bind: =>
90 binding.bind() for binding in @bindings 97 binding.bind() for binding in @bindings
91 98
99 # Unbinds all of the current bindings for this view.
100 unbind: =>
101 binding.unbind() for binding in @bindings
102
92 # Cross-browser event binding 103 # Cross-browser event binding
93 bindEvent = (el, event, fn) -> 104 bindEvent = (el, event, fn) ->
94 # Check to see if addEventListener is available. 105 # Check to see if addEventListener is available.
...@@ -118,7 +129,7 @@ eventBinding = (event) -> (el, bind, unbind) -> ...@@ -118,7 +129,7 @@ eventBinding = (event) -> (el, bind, unbind) ->
118 unbindEvent el, event, unbind if unbind 129 unbindEvent el, event, unbind if unbind
119 130
120 # Returns an attribute binding routine for the specified attribute. This is what 131 # Returns an attribute binding routine for the specified attribute. This is what
121 # `registerBinding` falls back to when there is no routine for the binding type. 132 # is used when there are no matching routines for an identifier.
122 attributeBinding = (attr) -> (el, value) -> 133 attributeBinding = (attr) -> (el, value) ->
123 if value then el.setAttribute attr, value else el.removeAttribute attr 134 if value then el.setAttribute attr, value else el.removeAttribute attr
124 135
......