Skip to content
Browse files

fixes another EventEmitter bug and cleans up model.js

  • Loading branch information...
1 parent 7754612 commit 67fb00c4f88e881eb80c2a91aee258b58cef41a3 @semmypurewal committed Dec 10, 2012
Showing with 189 additions and 71 deletions.
  1. +1 −1 build/jermaine-min.js
  2. +81 −35 build/jermaine.js
  3. +26 −0 spec/core/model.js
  4. +81 −35 src/core/model.js
View
2 build/jermaine-min.js
@@ -9,4 +9,4 @@ if(!Array.prototype.indexOf){Array.prototype.indexOf=function(c){if(this==null){
*
* + See issue 24 on github
*/
-window.jermaine.util.namespace("window.jermaine",function(a){var b=function(f){var h=[],l=this,m="invalid setter call for "+f,j,k,e,c,o=false,d,n={},p=window.jermaine.AttrList,g=window.jermaine.Validator;if(f===undefined||typeof(f)!=="string"){throw new Error("Attr: constructor requires a name parameter which must be a string")}d=function(i){for(k=0;k<h.length;++k){h[k](i)}return true};this.validatesWith=function(i){if(typeof(i)==="function"){h.push(new g(i));return this}else{throw new Error("Attr: validator must be a function")}};this.defaultsTo=function(i){j=i;return this};this.isReadOnly=function(){o=true;return this};this.isWritable=function(){o=false;return this};this.on=function(i,q){if(i!=="set"&&i!=="get"){throw new Error("Attr: first argument to the 'on' method should be 'set' or 'get'")}else{if(typeof(q)!=="function"){throw new Error("Attr: second argument to the 'on' method should be a function")}else{n[i]=q}}};this.name=function(){return f};this.validator=function(){return d};this.and=this;this.which=this;this.isImmutable=this.isReadOnly;this.isMutable=this.isWritable;this.clone=function(){var q,r;q=this instanceof p?new p(f):new b(f);for(r=0;r<h.length;++r){q.validatesWith(h[r])}q.defaultsTo(j);if(o){q.isImmutable()}return q};this.addTo=function(s){var q,r,i;if(!s||typeof(s)!=="object"){throw new Error("Attr: addAttr method requires an object parameter")}s[f]=function(u){var t;if(u!==undefined){if(o&&q!==undefined){throw new Error("cannot set the immutable property "+f+" after it has been set")}else{if(!d(u)){throw new Error(m)}else{t=q;q=u;if(n.set!==undefined){n.set.call(s,u,t)}}}return s}else{if(n.get!==undefined){n.get.call(s,q)}return q}};i=typeof(j)==="function"?j():j;if(i!==undefined&&d(i)){s[f](i)}else{if(i!==undefined&&!d(i)){throw new Error("Attr: Default value of "+i+" does not pass validation for "+f)}}};c=function(i){l[i]=function(q){h.push(g.getValidator(i)(q));return l}};for(k=0;k<g.validators().length;++k){c(g.validators()[k])}};a.Attr=b});window.jermaine.util.namespace("window.jermaine",function(a){function b(c){var e=this;a.Attr.call(this,c);var d=function(g,f){return function(){return g[f].apply(g,arguments)}};this.validateWith=this.validatesWith;this.defaultsTo=function(){};this.isImmutable=function(){};this.isMutable=function(){};this.eachOfWhich=this;this.addTo=function(h){var i,f=[],g={};if(!h||typeof(h)!=="object"){throw new Error("AttrList: addTo method requires an object parameter")}else{g.pop=d(f,"pop");g.add=function(j){if((e.validator())(j)){f.push(j);return this}else{throw new Error(e.errorMessage())}};g.replace=function(j,k){if((typeof(j)!=="number")||(parseInt(j,10)!==j)){throw new Error("AttrList: replace method requires index parameter to be an integer")}if(j<0||j>=this.size()){throw new Error("AttrList: replace method index parameter out of bounds")}if(!(e.validator())(k)){throw new Error(e.errorMessage())}f[j]=k;return this};g.at=function(j){if(j<0||j>=this.size()){throw new Error("AttrList: Index out of bounds")}return f[j]};g.get=g.at;g.size=function(){return f.length};g.toJSON=function(m){var k=[],n,l;if(m!==undefined){for(n=0;n<m.length;++n){if(m[n].object===this){k=m[n].JSONrep}}}for(n=0;n<f.length;++n){if(f[n].toJSON){k.push(f[n].toJSON(m))}else{k.push(f[n])}}return k};h[c]=function(){return g}}}}b.prototype=new window.jermaine.Attr(name);a.AttrList=b});window.jermaine.util.namespace("window.jermaine",function(a){var b=function(c,d){if(!c||typeof(c)!=="string"){throw new Error("Method: constructor requires a name parameter which must be a string")}else{if(!d||typeof(d)!=="function"){throw new Error("Method: second parameter must be a function")}}this.addTo=function(e){if(!e||typeof(e)!=="object"){throw new Error("Method: addTo method requires an object parameter")}e[c]=d}};a.Method=b});window.jermaine.util.namespace("window.jermaine",function(a){var b=function(u){var q={},i={},p,f=true,d=[],n=[],r=[],c=a.Method,s=a.Attr,l=a.AttrList,h=a.util.EventEmitter,g,v,k,j,t=function(){},o=function(){},e=function(){if(f){e.validate();k()}return o.apply(this,arguments)};if(arguments.length>1){u=arguments[arguments.length-1]}if(u&&typeof(u)==="function"){e=new b();u.call(e);return e}else{if(u){throw new Error("Model: specification parameter must be a function")}}var m=function(y,x){var A,w,z;A=y==="Attr"?s:l;w=y==="Attr"?"hasA":"hasMany";f=true;if(typeof(x)==="string"){z=new A(x);i[x]=z;return z}else{throw new Error("Model: "+w+" parameter must be a string")}};g=function(y,x){var w;if(typeof(x)!=="string"){throw new Error("Model: expected string argument to "+y+" method, but recieved "+x)}w=y==="attribute"?i[x]:q[x];if(w===undefined){throw new Error("Model: "+y+" "+x+" does not exist!")}return w};v=function(y){var x,z=[],w=y==="attributes"?i:q;for(x in w){if(w.hasOwnProperty(x)){z.push(x)}}return z};k=function(){o=function(){var C,B,z,x,y=e.attributes(),A=e.methods(),w=new h(),F,G={},E,D,H;if(!(this instanceof e)){throw new Error("Model: instances must be created using the new operator")}this.emitter=function(){return w};this.on=this.emitter().on;this.emit=this.emitter().emit;this.toJSON=function(J){var M,K,I,L={},N;if(J===undefined){J=[];J.push({object:this,JSONrep:L})}else{if(typeof(J)!=="object"){throw new Error("Instance: toJSON should not take a parameter (unless called recursively)")}else{for(K=0;K<J.length;++K){if(J[K].object===this){L=J[K].JSONrep}}}}for(K=0;K<y.length;++K){N=null;M=this[y[K]]();for(I=0;I<J.length;++I){if(J[I].object===M){N=J[I].JSONrep}}if(M!==undefined&&M!==null&&M.toJSON!==undefined&&N===null){N=(i[y[K]] instanceof l)?[]:{};J.push({object:M,JSONrep:N});J[J.length-1].JSONrep=M.toJSON(J)}if(N===null){L[y[K]]=M}else{L[y[K]]=N}}return L};this.toString=(p!==undefined)?p:function(){return"Jermaine Model Instance"};E=function(I){I.on("set",function(L,K){var J=this;if(G[I.name()]===undefined){G[I.name()]=function(O){var N=[],M=true;for(C=0;C<O.length&&M===true;++C){N.push(O[C]);if(O[C].origin===this){M=false}}if(M){N.push({key:I.name(),origin:this});this.emit("change",N)}}}if(D!==undefined){if(K!==undefined&&K!==null&&K.emitter!==undefined){K.emitter().removeListener("change",D);D=undefined}}if(L!==null&&typeof(L)==="object"&&L.on!==undefined&&L.emitter!==undefined){if(K!==undefined&&K!==null&&D!==undefined){K.emitter().removeListener("change",D)}D=function(M){G[I.name()].call(J,M)};L.emitter().on("change",D)}this.emit("change",[{key:I.name(),value:L,origin:this}])})};for(C=0;C<y.length;++C){F=e.attribute(y[C]);if(!(F instanceof l)){E.call(this,F)}}for(C=0;C<y.length+A.length;++C){if(C<y.length){if(j){e.attribute(y[C]).isImmutable()}e.attribute(y[C]).addTo(this)}else{e.method(A[C-y.length]).addTo(this)}}if(arguments.length>0){if(arguments.length<d.length){z="Constructor requires ";for(C=0;C<d.length;++C){z+=d[C];z+=C===d.length-1?"":", "}z+=" to be specified";throw new Error(z)}if(arguments.length>d.length+n.length){throw new Error("Too many arguments to constructor. Expected "+d.length+" required arguments and "+n.length+" optional arguments")}else{for(C=0;C<arguments.length;++C){x=C<d.length?d[C]:n[C-d.length];if(e.attribute(x) instanceof l){if(Object.prototype.toString.call(arguments[C])!=="[object Array]"){throw new Error("Model: Constructor requires 'names' attribute to be set with an Array")}else{for(B=0;B<arguments[C].length;++B){this[x]().add(arguments[C][B])}}}else{this[x](arguments[C])}}}}t.call(this)}};e.hasA=function(w){return m("Attr",w)};e.hasAn=e.hasA;e.hasSome=e.hasA;e.hasMany=function(w){return m("AttrList",w)};e.isA=function(y){var x,w,A,z;f=true;z=function(C){var B,D=new b();for(B in D){if(D.hasOwnProperty(B)&&typeof(C[B])!==typeof(D[B])){return false}}return true};if(typeof(y)!=="function"||!z(y)){throw new Error("Model: parameter sent to isA function must be a Model")}if(r.length===0){r.push(y)}else{throw new Error("Model: Model only supports single inheritance at this time")}w=r[0].attributes();for(x=0;x<w.length;++x){if(i[w[x]]===undefined){i[w[x]]=r[0].attribute(w[x]).clone();i[w[x]].isMutable()}}A=r[0].methods();for(x=0;x<A.length;++x){if(q[A[x]]===undefined){q[A[x]]=r[0].method(A[x])}}for(x=0;x<r.length;x++){e.prototype=new r[x]()}};e.isAn=e.isA;e.parent=function(){return r[0].apply(this,arguments)};e.attribute=function(w){return g("attribute",w)};e.attributes=function(){return v("attributes")};e.method=function(w){return g("method",w)};e.methods=function(){return v("methods")};e.isBuiltWith=function(){var w=false,x;f=true;d=[];n=[];for(x=0;x<arguments.length;++x){if(typeof(arguments[x])==="string"&&arguments[x].charAt(0)!=="%"){if(w){throw new Error("Model: isBuiltWith requires parameters preceded with a % to be the final parameters before the optional function")}else{d.push(arguments[x])}}else{if(typeof(arguments[x])==="string"&&arguments[x].charAt(0)==="%"){w=true;n.push(arguments[x].slice(1))}else{if(typeof(arguments[x])==="function"&&x===arguments.length-1){t=arguments[x]}else{throw new Error("Model: isBuiltWith parameters must be strings except for a function as the optional final parameter")}}}}};e.isImmutable=function(){j=true};e.looksLike=function(w){f=true;p=w};e.respondsTo=function(x,y){var w=new c(x,y);f=true;q[x]=w};e.validate=function(){var y,x=this.attributes(),w=this.methods();for(y=0;y<d.length;++y){try{this.attribute(d[y])}catch(z){throw new Error(d[y]+", specified in the isBuiltWith method, is not an attribute")}}for(y=0;y<n.length;++y){try{this.attribute(n[y])}catch(z){throw new Error(n[y]+", specified in the isBuiltWith method, is not an attribute")}}for(y=0;y<x.length;y++){if(w.indexOf(x[y])>-1){throw new Error("Model: invalid model specification to "+x[y]+" being both an attribute and method")}}if(j){for(y=0;y<x.length;y++){if(d.indexOf(x[y])<0){throw new Error("immutable objects must have all attributes required in a call to isBuiltWith")}}}f=false};return e};a.Model=b});
+window.jermaine.util.namespace("window.jermaine",function(a){var b=function(f){var h=[],l=this,m="invalid setter call for "+f,j,k,e,c,o=false,d,n={},p=window.jermaine.AttrList,g=window.jermaine.Validator;if(f===undefined||typeof(f)!=="string"){throw new Error("Attr: constructor requires a name parameter which must be a string")}d=function(i){for(k=0;k<h.length;++k){h[k](i)}return true};this.validatesWith=function(i){if(typeof(i)==="function"){h.push(new g(i));return this}else{throw new Error("Attr: validator must be a function")}};this.defaultsTo=function(i){j=i;return this};this.isReadOnly=function(){o=true;return this};this.isWritable=function(){o=false;return this};this.on=function(i,q){if(i!=="set"&&i!=="get"){throw new Error("Attr: first argument to the 'on' method should be 'set' or 'get'")}else{if(typeof(q)!=="function"){throw new Error("Attr: second argument to the 'on' method should be a function")}else{n[i]=q}}};this.name=function(){return f};this.validator=function(){return d};this.and=this;this.which=this;this.isImmutable=this.isReadOnly;this.isMutable=this.isWritable;this.clone=function(){var q,r;q=this instanceof p?new p(f):new b(f);for(r=0;r<h.length;++r){q.validatesWith(h[r])}q.defaultsTo(j);if(o){q.isImmutable()}return q};this.addTo=function(s){var q,r,i;if(!s||typeof(s)!=="object"){throw new Error("Attr: addAttr method requires an object parameter")}s[f]=function(u){var t;if(u!==undefined){if(o&&q!==undefined){throw new Error("cannot set the immutable property "+f+" after it has been set")}else{if(!d(u)){throw new Error(m)}else{t=q;q=u;if(n.set!==undefined){n.set.call(s,u,t)}}}return s}else{if(n.get!==undefined){n.get.call(s,q)}return q}};i=typeof(j)==="function"?j():j;if(i!==undefined&&d(i)){s[f](i)}else{if(i!==undefined&&!d(i)){throw new Error("Attr: Default value of "+i+" does not pass validation for "+f)}}};c=function(i){l[i]=function(q){h.push(g.getValidator(i)(q));return l}};for(k=0;k<g.validators().length;++k){c(g.validators()[k])}};a.Attr=b});window.jermaine.util.namespace("window.jermaine",function(a){function b(c){var e=this;a.Attr.call(this,c);var d=function(g,f){return function(){return g[f].apply(g,arguments)}};this.validateWith=this.validatesWith;this.defaultsTo=function(){};this.isImmutable=function(){};this.isMutable=function(){};this.eachOfWhich=this;this.addTo=function(h){var i,f=[],g={};if(!h||typeof(h)!=="object"){throw new Error("AttrList: addTo method requires an object parameter")}else{g.pop=d(f,"pop");g.add=function(j){if((e.validator())(j)){f.push(j);return this}else{throw new Error(e.errorMessage())}};g.replace=function(j,k){if((typeof(j)!=="number")||(parseInt(j,10)!==j)){throw new Error("AttrList: replace method requires index parameter to be an integer")}if(j<0||j>=this.size()){throw new Error("AttrList: replace method index parameter out of bounds")}if(!(e.validator())(k)){throw new Error(e.errorMessage())}f[j]=k;return this};g.at=function(j){if(j<0||j>=this.size()){throw new Error("AttrList: Index out of bounds")}return f[j]};g.get=g.at;g.size=function(){return f.length};g.toJSON=function(m){var k=[],n,l;if(m!==undefined){for(n=0;n<m.length;++n){if(m[n].object===this){k=m[n].JSONrep}}}for(n=0;n<f.length;++n){if(f[n].toJSON){k.push(f[n].toJSON(m))}else{k.push(f[n])}}return k};h[c]=function(){return g}}}}b.prototype=new window.jermaine.Attr(name);a.AttrList=b});window.jermaine.util.namespace("window.jermaine",function(a){var b=function(c,d){if(!c||typeof(c)!=="string"){throw new Error("Method: constructor requires a name parameter which must be a string")}else{if(!d||typeof(d)!=="function"){throw new Error("Method: second parameter must be a function")}}this.addTo=function(e){if(!e||typeof(e)!=="object"){throw new Error("Method: addTo method requires an object parameter")}e[c]=d}};a.Method=b});window.jermaine.util.namespace("window.jermaine",function(a){var b=function(u){var q={},i={},p,f=true,d=[],n=[],r=[],c=a.Method,s=a.Attr,l=a.AttrList,h=a.util.EventEmitter,g,v,k,j,t=function(){},o=function(){},e=function(){if(f){e.validate();k()}return o.apply(this,arguments)};if(arguments.length>1){u=arguments[arguments.length-1]}if(u&&typeof(u)==="function"){e=new b();u.call(e);return e}else{if(u){throw new Error("Model: specification parameter must be a function")}}var m=function(y,x){var A,w,z;A=y==="Attr"?s:l;w=y==="Attr"?"hasA":"hasMany";f=true;if(typeof(x)==="string"){z=new A(x);i[x]=z;return z}else{throw new Error("Model: "+w+" parameter must be a string")}};g=function(y,x){var w;if(typeof(x)!=="string"){throw new Error("Model: expected string argument to "+y+" method, but recieved "+x)}w=y==="attribute"?i[x]:q[x];if(w===undefined){throw new Error("Model: "+y+" "+x+" does not exist!")}return w};v=function(y){var x,z=[],w=y==="attributes"?i:q;for(x in w){if(w.hasOwnProperty(x)){z.push(x)}}return z};k=function(){o=function(){var C,B,z,x,y=e.attributes(),A=e.methods(),w=new h(),F,G={},E,H,D=this;if(!(this instanceof e)){throw new Error("Model: instances must be created using the new operator")}this.emitter=function(){return w};this.emitter().removeJermaineChangeListener=function(I,J){if(typeof(I)!=="string"){throw new Error("attrName must be a string")}else{if(typeof(J)!=="object"||J.toJSON===undefined||J.emitter===undefined){throw new Error("obj must be a jermaine object")}else{J.emitter().removeListener("change",G[I])}}};this.emitter().addJermaineChangeListener=function(I,J){if(typeof(I)!=="string"){throw new Error("attrName must be a string")}else{if(typeof(J)!=="object"||J.toJSON===undefined||J.emitter===undefined){throw new Error("obj must be a jermaine object")}else{if(G[I]===undefined){G[I]=function(M){var L=[],K=true;for(C=0;C<M.length&&K===true;++C){L.push(M[C]);if(M[C].origin===D){K=false}}if(K){L.push({key:I,origin:D});D.emit("change",L)}}}J.emitter().on("change",G[I])}}};this.on=this.emitter().on;this.emit=this.emitter().emit;this.toJSON=function(J){var M,K,I,L={},N;if(J===undefined){J=[];J.push({object:this,JSONrep:L})}else{if(typeof(J)!=="object"){throw new Error("Instance: toJSON should not take a parameter (unless called recursively)")}else{for(K=0;K<J.length;++K){if(J[K].object===this){L=J[K].JSONrep}}}}for(K=0;K<y.length;++K){N=null;M=this[y[K]]();for(I=0;I<J.length;++I){if(J[I].object===M){N=J[I].JSONrep}}if(M!==undefined&&M!==null&&M.toJSON!==undefined&&N===null){N=(i[y[K]] instanceof l)?[]:{};J.push({object:M,JSONrep:N});J[J.length-1].JSONrep=M.toJSON(J)}if(N===null){L[y[K]]=M}else{L[y[K]]=N}}return L};this.toString=(p!==undefined)?p:function(){return"Jermaine Model Instance"};E=function(I){I.on("set",function(K,J){if(J!==undefined&&J!==null&&J.on!==undefined&&J.toJSON!==undefined&&J.emitter!==undefined){if(J.emitter().listeners("change").length<1){throw new Error("preValue should always have a listener defined if it is a model")}this.emitter().removeJermaineChangeListener(I.name(),J)}if(K!==undefined&&K!==null&&K.on!==undefined&&K.toJSON!==undefined&&K.emitter!==undefined){this.emitter().addJermaineChangeListener(I.name(),K)}this.emit("change",[{key:I.name(),value:K,origin:this}])})};for(C=0;C<y.length;++C){F=e.attribute(y[C]);if(!(F instanceof l)){E.call(this,F)}}for(C=0;C<y.length+A.length;++C){if(C<y.length){if(j){e.attribute(y[C]).isImmutable()}e.attribute(y[C]).addTo(this)}else{e.method(A[C-y.length]).addTo(this)}}if(arguments.length>0){if(arguments.length<d.length){z="Constructor requires ";for(C=0;C<d.length;++C){z+=d[C];z+=C===d.length-1?"":", "}z+=" to be specified";throw new Error(z)}if(arguments.length>d.length+n.length){throw new Error("Too many arguments to constructor. Expected "+d.length+" required arguments and "+n.length+" optional arguments")}else{for(C=0;C<arguments.length;++C){x=C<d.length?d[C]:n[C-d.length];if(e.attribute(x) instanceof l){if(Object.prototype.toString.call(arguments[C])!=="[object Array]"){throw new Error("Model: Constructor requires 'names' attribute to be set with an Array")}else{for(B=0;B<arguments[C].length;++B){this[x]().add(arguments[C][B])}}}else{this[x](arguments[C])}}}}t.call(this)}};e.hasA=function(w){return m("Attr",w)};e.hasAn=e.hasA;e.hasSome=e.hasA;e.hasMany=function(w){return m("AttrList",w)};e.isA=function(y){var x,w,A,z;f=true;z=function(C){var B,D=new b();for(B in D){if(D.hasOwnProperty(B)&&typeof(C[B])!==typeof(D[B])){return false}}return true};if(typeof(y)!=="function"||!z(y)){throw new Error("Model: parameter sent to isA function must be a Model")}if(r.length===0){r.push(y)}else{throw new Error("Model: Model only supports single inheritance at this time")}w=r[0].attributes();for(x=0;x<w.length;++x){if(i[w[x]]===undefined){i[w[x]]=r[0].attribute(w[x]).clone();i[w[x]].isMutable()}}A=r[0].methods();for(x=0;x<A.length;++x){if(q[A[x]]===undefined){q[A[x]]=r[0].method(A[x])}}for(x=0;x<r.length;x++){e.prototype=new r[x]()}};e.isAn=e.isA;e.parent=function(){return r[0].apply(this,arguments)};e.attribute=function(w){return g("attribute",w)};e.attributes=function(){return v("attributes")};e.method=function(w){return g("method",w)};e.methods=function(){return v("methods")};e.isBuiltWith=function(){var w=false,x;f=true;d=[];n=[];for(x=0;x<arguments.length;++x){if(typeof(arguments[x])==="string"&&arguments[x].charAt(0)!=="%"){if(w){throw new Error("Model: isBuiltWith requires parameters preceded with a % to be the final parameters before the optional function")}else{d.push(arguments[x])}}else{if(typeof(arguments[x])==="string"&&arguments[x].charAt(0)==="%"){w=true;n.push(arguments[x].slice(1))}else{if(typeof(arguments[x])==="function"&&x===arguments.length-1){t=arguments[x]}else{throw new Error("Model: isBuiltWith parameters must be strings except for a function as the optional final parameter")}}}}};e.isImmutable=function(){j=true};e.looksLike=function(w){f=true;p=w};e.respondsTo=function(x,y){var w=new c(x,y);f=true;q[x]=w};e.validate=function(){var y,x=this.attributes(),w=this.methods();for(y=0;y<d.length;++y){try{this.attribute(d[y])}catch(z){throw new Error(d[y]+", specified in the isBuiltWith method, is not an attribute")}}for(y=0;y<n.length;++y){try{this.attribute(n[y])}catch(z){throw new Error(n[y]+", specified in the isBuiltWith method, is not an attribute")}}for(y=0;y<x.length;y++){if(w.indexOf(x[y])>-1){throw new Error("Model: invalid model specification to "+x[y]+" being both an attribute and method")}}if(j){for(y=0;y<x.length;y++){if(d.indexOf(x[y])<0){throw new Error("immutable objects must have all attributes required in a call to isBuiltWith")}}}f=false};return e};a.Model=b});
View
116 build/jermaine.js
@@ -939,8 +939,8 @@ window.jermaine.util.namespace("window.jermaine", function (ns) {
attr,
attrChangeListeners = {},
setHandler,
- lastListener,
- addProperties;
+ addProperties,
+ that = this;
if (!(this instanceof model)) {
throw new Error("Model: instances must be created using the new operator");
@@ -951,6 +951,13 @@ window.jermaine.util.namespace("window.jermaine", function (ns) {
////////////// PUBLIC API FOR ALL INSTANCES ////////////////////
////////////////////////////////////////////////////////////////
+ // this is a method associated with unit test
+ // it("should not increment the listeners associated with the last object created"
+ // it has been removed now that the bug has been fixed
+ /*this.attrChangeListeners = function () {
+ return attrChangeListeners;
+ };*/
+
/**
* Returns the EventEmitter associated with this instance.
*
@@ -960,6 +967,53 @@ window.jermaine.util.namespace("window.jermaine", function (ns) {
};
/**
+ * Wrapper methods added to the internal EventEmitter object
+ *
+ */
+
+ this.emitter().removeJermaineChangeListener = function (attrName, obj) {
+ if (typeof(attrName) !== "string") {
+ throw new Error("attrName must be a string");
+ } else if (typeof(obj) !== "object" || obj.toJSON === undefined ||
+ obj.emitter === undefined) {
+ throw new Error("obj must be a jermaine object");
+ } else {
+ obj.emitter().removeListener("change", attrChangeListeners[attrName]);
+ }
+ };
+
+ this.emitter().addJermaineChangeListener = function (attrName, obj) {
+ if (typeof(attrName) !== "string") {
+ throw new Error("attrName must be a string");
+ } else if (typeof(obj) !== "object" || obj.toJSON === undefined ||
+ obj.emitter === undefined) {
+ throw new Error("obj must be a jermaine object");
+ } else {
+ if (attrChangeListeners[attrName] === undefined) {
+ attrChangeListeners[attrName] = function (data) {
+ var newData = [],
+ emit = true;
+
+ for (i = 0; i < data.length && emit === true; ++i) {
+ newData.push(data[i]);
+ if (data[i].origin === that) {
+ emit = false;
+ }
+ }
+
+ if (emit) {
+ newData.push({key:attrName, origin:that});
+ that.emit("change", newData);
+ }
+ };
+
+ }
+ obj.emitter().on("change", attrChangeListeners[attrName]);
+ }
+ };
+
+
+ /**
* Registers a listener for this instance's changes.
*
*/
@@ -1033,51 +1087,43 @@ window.jermaine.util.namespace("window.jermaine", function (ns) {
return "Jermaine Model Instance";
};
+
////////////////////////////////////////////////////////////////
////////////// END PUBLIC API FOR ALL INSTANCES ////////////////
////////////////////////////////////////////////////////////////
+ /**
+ * This is a private method that sets up handling for the setter
+ * It attaches a change listener on new objects
+ * and it removes the change listener from old objects
+ */
setHandler = function (attr) {
//when set handler is called, this should be the current object
attr.on("set", function (newValue, preValue) {
- var that = this;
-
- if (attrChangeListeners[attr.name()] === undefined) {
- attrChangeListeners[attr.name()] = function (data) {
- var newData = [],
- emit = true;
-
- for (i = 0; i < data.length && emit === true; ++i) {
- newData.push(data[i]);
- if (data[i].origin === this) {
- emit = false;
- }
- }
-
- if (emit) {
- newData.push({key:attr.name(), origin:this});
- this.emit("change", newData);
- }
- };
- }
-
- if (lastListener !== undefined) {
- if (preValue !== undefined && preValue !== null && preValue.emitter !== undefined) {
- preValue.emitter().removeListener("change", lastListener);
- lastListener = undefined;
+ // if preValue is a model instance, we need to remove the listener from it
+ if (preValue !== undefined && preValue !== null && preValue.on !== undefined &&
+ preValue.toJSON !== undefined && preValue.emitter !== undefined) {
+ // we now assume preValue is a model instance
+
+ // sanity check 1
+ if (preValue.emitter().listeners("change").length < 1) {
+ throw new Error("preValue should always have a listener defined if it is a model");
}
+
+ this.emitter().removeJermaineChangeListener(attr.name(), preValue);
}
- if (newValue !== null && typeof(newValue) === "object" && newValue.on !== undefined && newValue.emitter !== undefined) {
- if (preValue !== undefined && preValue !== null && lastListener !== undefined) {
- preValue.emitter().removeListener("change", lastListener);
- }
- lastListener = function (data) {
- attrChangeListeners[attr.name()].call(that, data);
- };
- newValue.emitter().on("change", lastListener);
+ // if newValue is a model instance, we need to attach a listener to it
+ if (newValue !== undefined && newValue !== null && newValue.on !== undefined &&
+ newValue.toJSON !== undefined && newValue.emitter !== undefined) {
+ // we now assume newValue is a model instance
+
+ // attach a listener
+ this.emitter().addJermaineChangeListener(attr.name(), newValue);
}
+
+ // finally emit that a change has happened
this.emit("change", [{key:attr.name(), value:newValue, origin:this}]);
});
};
View
26 spec/core/model.js
@@ -783,6 +783,32 @@ describe("Model", function () {
expect(Person.attribute("things").on).not.toHaveBeenCalled();
});
+ // this was a bug, but I had to add to the public API
+ xit("should not increment the listeners associated with the last object created", function () {
+ var Dog = new Model(function () {
+ this.hasA("breed").which.isA("string");
+ this.isBuiltWith("breed");
+ });
+
+ var Person = new Model(function () {
+ this.hasA("name").which.isA("string");
+ this.hasA("dog").which.validatesWith(function (dog) {
+ return dog instanceof Dog;
+ });
+ this.isBuiltWith("name");
+ });
+
+ var s = new Person("Semmy");
+ var m = new Person("Mark");
+ var d1 = new Dog("chow");
+ var d2 = new Dog("shepherd");
+
+ s.dog(d1);
+
+ expect(s.attrChangeListeners().dog).not.toBeUndefined();
+ expect(m.attrChangeListeners().dog).toBeUndefined();
+ });
+
it("should create an object that has an 'on' method and an 'emitter' method", function () {
p = new Person();
expect(p.on).toBeDefined();
View
116 src/core/model.js
@@ -111,8 +111,8 @@ window.jermaine.util.namespace("window.jermaine", function (ns) {
attr,
attrChangeListeners = {},
setHandler,
- lastListener,
- addProperties;
+ addProperties,
+ that = this;
if (!(this instanceof model)) {
throw new Error("Model: instances must be created using the new operator");
@@ -123,6 +123,13 @@ window.jermaine.util.namespace("window.jermaine", function (ns) {
////////////// PUBLIC API FOR ALL INSTANCES ////////////////////
////////////////////////////////////////////////////////////////
+ // this is a method associated with unit test
+ // it("should not increment the listeners associated with the last object created"
+ // it has been removed now that the bug has been fixed
+ /*this.attrChangeListeners = function () {
+ return attrChangeListeners;
+ };*/
+
/**
* Returns the EventEmitter associated with this instance.
*
@@ -132,6 +139,53 @@ window.jermaine.util.namespace("window.jermaine", function (ns) {
};
/**
+ * Wrapper methods added to the internal EventEmitter object
+ *
+ */
+
+ this.emitter().removeJermaineChangeListener = function (attrName, obj) {
+ if (typeof(attrName) !== "string") {
+ throw new Error("attrName must be a string");
+ } else if (typeof(obj) !== "object" || obj.toJSON === undefined ||
+ obj.emitter === undefined) {
+ throw new Error("obj must be a jermaine object");
+ } else {
+ obj.emitter().removeListener("change", attrChangeListeners[attrName]);
+ }
+ };
+
+ this.emitter().addJermaineChangeListener = function (attrName, obj) {
+ if (typeof(attrName) !== "string") {
+ throw new Error("attrName must be a string");
+ } else if (typeof(obj) !== "object" || obj.toJSON === undefined ||
+ obj.emitter === undefined) {
+ throw new Error("obj must be a jermaine object");
+ } else {
+ if (attrChangeListeners[attrName] === undefined) {
+ attrChangeListeners[attrName] = function (data) {
+ var newData = [],
+ emit = true;
+
+ for (i = 0; i < data.length && emit === true; ++i) {
+ newData.push(data[i]);
+ if (data[i].origin === that) {
+ emit = false;
+ }
+ }
+
+ if (emit) {
+ newData.push({key:attrName, origin:that});
+ that.emit("change", newData);
+ }
+ };
+
+ }
+ obj.emitter().on("change", attrChangeListeners[attrName]);
+ }
+ };
+
+
+ /**
* Registers a listener for this instance's changes.
*
*/
@@ -205,51 +259,43 @@ window.jermaine.util.namespace("window.jermaine", function (ns) {
return "Jermaine Model Instance";
};
+
////////////////////////////////////////////////////////////////
////////////// END PUBLIC API FOR ALL INSTANCES ////////////////
////////////////////////////////////////////////////////////////
+ /**
+ * This is a private method that sets up handling for the setter
+ * It attaches a change listener on new objects
+ * and it removes the change listener from old objects
+ */
setHandler = function (attr) {
//when set handler is called, this should be the current object
attr.on("set", function (newValue, preValue) {
- var that = this;
-
- if (attrChangeListeners[attr.name()] === undefined) {
- attrChangeListeners[attr.name()] = function (data) {
- var newData = [],
- emit = true;
-
- for (i = 0; i < data.length && emit === true; ++i) {
- newData.push(data[i]);
- if (data[i].origin === this) {
- emit = false;
- }
- }
-
- if (emit) {
- newData.push({key:attr.name(), origin:this});
- this.emit("change", newData);
- }
- };
- }
-
- if (lastListener !== undefined) {
- if (preValue !== undefined && preValue !== null && preValue.emitter !== undefined) {
- preValue.emitter().removeListener("change", lastListener);
- lastListener = undefined;
+ // if preValue is a model instance, we need to remove the listener from it
+ if (preValue !== undefined && preValue !== null && preValue.on !== undefined &&
+ preValue.toJSON !== undefined && preValue.emitter !== undefined) {
+ // we now assume preValue is a model instance
+
+ // sanity check 1
+ if (preValue.emitter().listeners("change").length < 1) {
+ throw new Error("preValue should always have a listener defined if it is a model");
}
+
+ this.emitter().removeJermaineChangeListener(attr.name(), preValue);
}
- if (newValue !== null && typeof(newValue) === "object" && newValue.on !== undefined && newValue.emitter !== undefined) {
- if (preValue !== undefined && preValue !== null && lastListener !== undefined) {
- preValue.emitter().removeListener("change", lastListener);
- }
- lastListener = function (data) {
- attrChangeListeners[attr.name()].call(that, data);
- };
- newValue.emitter().on("change", lastListener);
+ // if newValue is a model instance, we need to attach a listener to it
+ if (newValue !== undefined && newValue !== null && newValue.on !== undefined &&
+ newValue.toJSON !== undefined && newValue.emitter !== undefined) {
+ // we now assume newValue is a model instance
+
+ // attach a listener
+ this.emitter().addJermaineChangeListener(attr.name(), newValue);
}
+
+ // finally emit that a change has happened
this.emit("change", [{key:attr.name(), value:newValue, origin:this}]);
});
};

0 comments on commit 67fb00c

Please sign in to comment.
Something went wrong with that request. Please try again.