From 4460328bc1173f5d97fb4ff54edc041968486fce Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Sat, 23 Jan 2010 15:54:58 -0800 Subject: lots of cleanup to get it ready for OS --- angular-minified.js | 198 ++++++++++---------- angular.js | 380 +++++++++++++++++---------------------- example/tweeter/tweeterclient.js | 5 +- jsTestDriver.conf | 2 + src/Angular.js | 328 ++++++++++++++------------------- src/Binder.js | 19 +- src/JSON.js | 18 +- src/Users.js | 4 +- src/Widgets.js | 11 +- test/AngularTest.js | 15 -- test/BinderTest.js | 105 ++++++----- test/ControlBarTest.js | 2 - test/ExternalApiTest.js | 15 -- test/JsonTest.js | 11 ++ test/ScenarioSpec.js | 66 +++++++ test/ScopeTest.js | 2 +- test/formsTest.js | 18 -- test/testabilityPatch.js | 8 +- 18 files changed, 572 insertions(+), 635 deletions(-) delete mode 100644 test/ControlBarTest.js delete mode 100644 test/ExternalApiTest.js create mode 100644 test/ScenarioSpec.js delete mode 100644 test/formsTest.js diff --git a/angular-minified.js b/angular-minified.js index 2209c269..c9103560 100644 --- a/angular-minified.js +++ b/angular-minified.js @@ -1,99 +1,99 @@ -function K(){return function(){}}function L(o){return function(){return o}} -(function(o,y){function n(){}function w(a,b,c){var d=o.console;switch(arguments.length){case 1:d.log(a);break;case 2:d.log(a,b);break;default:d.log(a,b,c);break}}function W(a,b,c){var d=o.console;switch(arguments.length){case 1:d.error(a);break;case 2:d.error(a,b);break;default:d.error(a,b,c);break}}function da(a,b){var c=y.createElement("div");c.className=a;for(var d=a="",e=0;e/g,">")}function Da(a){if(!a||!a.replace)return a;return a.replace(//g,">").replace(/\"/g,""")}function ga(a,b){if(!a)throw"Missing this";if(!_.isFunction(b))throw"Missing function";return function(){return b.apply(a,arguments)}}function Ea(a,b){return function(){for(var c= -[this],d=0;d>2;c=(c&3)<<4|d>>4;g=(d&15)<<2|e>>6;h=e&63;if(isNaN(d))g=h=64;else if(isNaN(e))h= -64;b=b+this.u.charAt(f)+this.u.charAt(c)+this.u.charAt(g)+this.u.charAt(h)}return b},td:function(a){var b="",c,d,e,f,g,h=0;for(a=a.replace(/[^A-Za-z0-9\+\/\=]/g,"");h>4;d=(d&15)<<4|f>>2;e=(f&3)<<6|g;b+=String.fromCharCode(c);if(f!=64)b+=String.fromCharCode(d);if(g!=64)b+=String.fromCharCode(e)}return b=ba.ob(b)},pb:function(a){a=a.replace(/\r\n/g,"\n"); -for(var b="",c=0;c127&&d<2048)b+=String.fromCharCode(d>>6|192);else{b+=String.fromCharCode(d>>12|224);b+=String.fromCharCode(d>>6&63|128)}b+=String.fromCharCode(d&63|128)}}return b},ob:function(a){for(var b="",c=0,d=c1=c2=0;c191&&d<224){c2=a.charCodeAt(c+1);b+=String.fromCharCode((d&31)<<6|c2&63);c+=2}else{c2=a.charCodeAt(c+1);c3=a.charCodeAt(c+ -2);b+=String.fromCharCode((d&15)<<12|(c2&63)<<6|c3&63);c+=3}}return b}};if(typeof y.getAttribute=="undefined")y.getAttribute=K();if(typeof Node=="undefined")Node={ELEMENT_NODE:1,ATTRIBUTE_NODE:2,TEXT_NODE:3,CDATA_SECTION_NODE:4,ENTITY_REFERENCE_NODE:5,ENTITY_NODE:6,PROCESSING_INSTRUCTION_NODE:7,COMMENT_NODE:8,DOCUMENT_NODE:9,DOCUMENT_TYPE_NODE:10,DOCUMENT_FRAGMENT_NODE:11,NOTATION_NODE:12};o.console||(o.console={log:n,error:n});var R,q=_.each,S=_.extend,k=o.jQuery,X=k.browser.msie,m=o.angular||(o.angular= -{}),Ca=m.validator||(m.validator={}),r=m.filter||(m.filter={}),ca=m.callbacks||(m.callbacks={});m.alert||(m.alert=function(){w(arguments);o.alert.apply(o,arguments)});ia.prototype={load:function(){this.Db();w("Server: "+this.j.H);this.Cb();this.Bb();this.xb()},Cb:function(){w("Angular.configureJQueryPlugins()");k.fn.scope=function(){for(var a=this;a&&a.get(0);){var b=a.data("scope");if(b)return b;a=a.parent()}return null};k.fn.controller=function(){return this.data("controller")||Q.Wb}},Gc:function(){return""+ -(new Date).getTime()},Bb:function(){var a=this.j;if(!a.F){var b=a.H.match(/https?:\/\/([\w]*)/);a.F=b?b[1]:"$MEMORY"}},xb:function(){function a(){g.Qb(function(l){!l&&c.find("[ng-auth=eager]").length&&g.ka()})}w("Angular.bindHtml()");var b=this.Nc=new ja(this.location),c=this.document,d=new na(this.j.H,this.j.F),e=new x(c[0],d,b,this.j);d.rc=Ea(e,e.e);b=new P(c.find("body"),this.j.H);var f=this.j.F=="$MEMORY"?new aa(this.window):new ka(this.j.H,k.getScript);f=new la(f,new U(k(c.body)),function(){e.d()}); -var g=new ma(f,b),h="/data/"+this.j.F,i=new T(function(l,p){f.N("POST",h,l,p)},g,e.anchor);e.ab.push(function(){i.Ea()});var j=new v({$anchor:e.anchor,$binder:e,$config:this.j,$console:o.console,$datastore:i,$save:function(l){i.Ac(j.q,l,e.anchor)},$window:o,$uid:this.Gc,$users:g},"ROOT");c.data("scope",j);w("$binder.entity()");e.K(j);w("$binder.compile()");e.compile();w("ControlBar.bind()");b.bind();w("$users.fetchCurrentUser()");a();w("PopUp.bind()");(new B(c)).bind();w("$binder.parseAnchor()"); -e.Ta();c.find("body").show();w("ready()")},Db:function(){var a=o.location.href+"#";a=a.split("#")[1];var b={Jb:null};a=a.split("&");for(var c=0;c=0&&a.splice(c,1);return b},find:function(a,b,c){if(b){var d=m.Function.compile(b);_.detect(a,function(e){if(d(e)){c=e;return true}});return c}},findById:function(a,b){return m.Wc.find(a,function(c){return c.z==b},null)},filter:function(a, -b){var c=[];c.yb=function(j){for(var l=0;l-1;case "object":for(var p in j)if(p.charAt(0)!=="$"&&e(j[p],l))return true;return false;case "array":for(p=0;p=0&&a.splice(d,1);a.unshift((c?"-":"+")+b);return a},orderByDirection:function(a,b,c,d){c=c||"ng-ascend";d=d||"ng-descend";a=a[0]||"";var e=true;if(a.charAt(0)=="-"){a=a.substring(1); -e=false}else if(a.charAt(0)=="+")a=a.substring(1);return a==b?e?c:d:""},merge:function(a,b,c){var d=a[b];if(!d){d={};a[b]=d}Z(c,d);return a}},Ga={quote:function(a){return'"'+a.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\n/g,"\\n").replace(/\f/g,"\\f").replace(/\r/g,"\\r").replace(/\t/g,"\\t").replace(/\v/g,"\\v")+'"'},quoteUnicode:function(a){a=m.String.quote(a);for(var b=[],c=0;c-1;){c1||x.J(a[0])!==null};x.J=function(a){return(a=a.replace(/\n/gm," ").match(/^\{\{(.*)\}\}$/))? -a[1]:null};x.prototype={uc:function(a){var b={};a.replace(/(?:^|&)([^&=]*)=?([^&]*)/g,function(c,d,e){if(d)b[decodeURIComponent(d)]=decodeURIComponent(e)});return b},Ta:function(a){var b=this;a=a||this.qa.Ga();var c=a.indexOf("#");if(!(c<0)){a=this.uc(a.substring(c+1));q(b.anchor,function(d,e){delete b.anchor[e]});q(a,function(d,e){b.anchor[e]=d})}},tc:function(a){this.Ta(a);this.d()},Hc:function(){var a=this.qa.Ga(),b=a.indexOf("#");if(b>-1)a=a.substring(0,b);a+="#";b="";for(var c in this.anchor){var d= -this.anchor[c];if(typeof d==="undefined"||d===null)delete this.anchor[c];else{a+=b+encodeURIComponent(c);if(d!==true)a+="="+encodeURIComponent(d);b="&"}}this.qa.Dc(a);return a},d:function(){(new Date).getTime();var a=k(this.B).scope();a.i("$invalidWidgets",[]);a.d();(new Date).getTime();this.Hc();_.each(this.ab,function(b){b()})},ha:function(a){var b=k(this.B),c=b.find(a);if(b.is(a))c=c.andSelf();return c},K:function(a){this.ha("[ng-entity]").attr("ng-watch",function(){try{var b=k(this);return a.K(b.attr("ng-entity"))+ -(b.attr("ng-watch")||"")}catch(c){alert(c)}})},compile:function(){var a=k(this.B);if(this.j.ad){var b=this.ha(":submit").not("[ng-action]");b.attr("ng-action","$save()");b.not(":disabled").not("ng-bind-attr").attr("ng-bind-attr",'{disabled:"{{$invalidWidgets}}"}')}this.Ua(this.B)(this.B,a.scope(),"");this.ha("a[ng-action]").live("click",function(){var c=k(this),d=c.scope();try{d.eval(c.attr("ng-action"));c.removeAttr("ng-error");c.removeClass("ng-exception")}catch(e){c.addClass("ng-exception");c.attr("ng-error", -z(e,true))}d.eval("$binder.updateView()");return false})},Fc:function(a,b,c){b=b.concat();var d=b.pop(),e=x.Z(a.nodeValue);if(e.length>1||x.J(e[0])){var f=a.parentNode;if(ea(f)){f.setAttribute("ng-bind-template",a.nodeValue);c.push({path:b,b:function(l){return new N(l,l.getAttribute("ng-bind-template"))}})}else for(var g=0;g2&&a.setAttribute("ng-bind-attr",d)}a.getAttribute||w(a);var j=a.getAttribute("ng-repeat");if(j){a.removeAttribute("ng-repeat");var l=this.Ua(a);d=y.createComment("ng-repeat: "+j);e=a.parentNode;e.insertBefore(d,a);e.removeChild(a);function p(s,t,A){var J= -k(a).clone();J.css("display","");J.attr("ng-repeat-index",""+A);J.data("scope",s);l(J[0],s,t+A+":");return J}c.push({path:b,b:function(s,t,A){return new Ba(k(s),j,p,A)}})}else{a.getAttribute("ng-eval")&&c.push({path:b,b:this.lc});a.getAttribute("ng-bind")&&c.push({path:b,b:this.Ra});a.getAttribute("ng-bind-attr")&&c.push({path:b,b:this.hc});a.getAttribute("ng-hide")&&c.push({path:b,b:this.mc});a.getAttribute("ng-show")&&c.push({path:b,b:this.nc});a.getAttribute("ng-class")&&c.push({path:b,b:this.ic}); -a.getAttribute("ng-class-odd")&&c.push({path:b,b:this.kc});a.getAttribute("ng-class-even")&&c.push({path:b,b:this.jc});a.getAttribute("ng-style")&&c.push({path:b,b:this.oc});a.getAttribute("ng-watch")&&c.push({path:b,b:this.pc});d=a.nodeName;if(d=="INPUT"||d=="TEXTAREA"||d=="SELECT"||d=="BUTTON"){var C=this;c.push({path:b,b:function(s,t,A){s.name=A+s.name.split(":").pop();return C.Oc.Eb(k(s),t)}})}if(d=="OPTION")if(!k("')};M.prototype={e:function(a){var b=this.view.find("input").attr("checked")?this.value:null;if(this.c===b)return false;else{a.i(this.Ya,b);return true}},d:function(a){if((a=a.get(this.Ya))&&this.value!==a){this.value=a;this.view.find("a").attr("href",this.value.url).text(this.value.text); -this.view.find("span").text(m.filter.bytes(this.value.size))}this.view.find("input").attr("checked",!!a)}};Q.prototype={e:L(true),d:n};Q.Wb=new Q;var Ja=Q;oa.prototype={e:function(a){var b=this.view.value;if(this.c===b)return false;else{a.m(this.exp,b);this.c=b;return true}},d:function(a){var b=this.view,c=a.get(this.exp);if(typeof c==="undefined"){c=this.p;a.m(this.exp,c)}c=c?c:"";if(this.c!=c)this.c=b.value=c;var d=false;b.removeAttribute("ng-error");if(this.zc)d=!(c&&c.length>0);var e=d?"Required Value": -null;if(!d&&this.O&&c){e=a.Kc(this.O,c);d=!!e}if(this.Ja!==e){this.Ja=d;if(e!==null){b.setAttribute("ng-error",e);a.dc(this)}k(b).toggleClass("ng-validation-error",d)}}};pa.prototype={e:function(a){var b=this.view;b=b.checked?b.value:"";if(this.c===b)return false;else{a.m(this.exp,b);this.c=b;return true}},d:function(a){var b=this.view,c=a.eval(this.exp);if(typeof c==="undefined"){c=this.p;a.m(this.exp,c)}b.checked=b.value==""+c}};qa.prototype={e:function(a){if(this.view.selectedIndex<0)a.m(this.exp, -null);else{var b=this.view.value;if(this.c===b)return false;else{a.m(this.exp,b);this.c=b;return true}}},d:function(a){var b=this.view,c=a.get(this.exp);if(typeof c==="undefined"){c=this.p;a.m(this.exp,c)}if(c!==this.c){b.value=c?c:"";this.c=c}}};ra.prototype={selected:function(){for(var a=[],b=this.view.options,c=0;cd;--b){var p=this.children.pop().element[0];p.parentNode.removeChild(p)}if(h&&h.element[0].nodeName==="OPTION")if(d=k(h.element[0].parentNode).data("controller")){d.c=undefined;d.d(a)}})}};B.wa="mouseleave mouseout click dblclick keypress keyup";B.sc=function(){B.na();var a=k(this);a.bind(B.wa,B.na);var b=a.position(),c=y.documentElement,d=(self.innerWidth||c&&c.clientWidth|| -y.body.clientWidth)-b.left;c=a.hasClass("ng-exception")?"EXCEPTION:":"Validation error...";a=a.attr("ng-error");d=d>375?"left":"right";c=k("
"+c+"
"+a+"
");k("body").append(c);if(d==="left")a=b.left+this.offsetWidth+11;else{a=b.left-315;c.find(".ng-arrow-right").css({left:301})}c.css({left:a+"px",top:b.top-3+"px"});return true};B.na=function(){k("#ng-callout").unbind(B.wa, -B.na).remove();return true};B.prototype={bind:function(){this.B.find(".ng-validation-error,.ng-exception").live("mouseover",B.sc)}};U.ib='
loading....
';U.prototype={wb:function(){this.ba===0&&this.Na.show();this.ba++},Mb:function(){this.ba--;this.ba===0&&this.Na.hide("fold")}}})(window,document); +function J(){return function(){}}function N(q){return function(){return q}} +(function(q,y){function n(){}function K(a,b,c){var d=q.console;switch(arguments.length){case 1:d.log(a);break;case 2:d.log(a,b);break;default:d.log(a,b,c);break}}function W(a,b,c){var d=q.console;switch(arguments.length){case 1:d.error(a);break;case 2:d.error(a,b);break;default:d.error(a,b,c);break}}function da(a,b){var c=y.createElement("div");c.className=a;for(var d=a="",e=0;e/g,">")}function Ca(a){if(!a||!a.replace)return a;return a.replace(//g,">").replace(/\"/g,""")}function Da(a,b){if(!a)throw"Missing this";if(!_.isFunction(b))throw"Missing function";return function(){return b.apply(a,arguments)}}function ga(a){var b=y.createElement("div"); +b.appendChild(a);var c=b.innerHTML;b.removeChild(a);return c}function Y(a){var b=(""+a).toLowerCase();if(b=="f"||b=="0"||b=="false"||b=="no")a=false;return!!a}function Z(a,b){for(var c in a){var d=b[c],e=typeof d;if(e=="undefined")b[c]=P(w(a[c]));else e=="object"&&d.constructor!=array&&c.substring(0,1)!="$"&&Z(a[c],d)}}function ha(a){this.location=a;this.delay=25;this.setTimeout=function(b,c){q.setTimeout(b,c)};this.Wb=function(b){return b};this.I=a.href}function Ea(){K("Angular.configureJQueryPlugins()"); +var a=k.fn;a.scope=function(){for(var b=this;b&&b.get(0);){var c=b.data("scope");if(c)return c;b=b.parent()}return null};a.controller=function(){return this.data("controller")||Q.Rb}}function Fa(a){if(a.hd=="console"&&!O){O=y.createElement("div");O.id="ng-console";y.getElementsByTagName("body")[0].appendChild(O);K=function(){da("ng-console-info",arguments)};console.error=function(){da("ng-console-error",arguments)}}}function ia(a,b){var c={};o(b,function(d,e){c[e]=_(d).bind(a)});return c}function Ga(a, +b){var c=new ja(b.server,b.database),d=new x(a[0],c,b.location,b);c=new R(a.find("body"),b.oa);var e=b.fa=="$MEMORY"?new $(this.window):new ka(b.oa,k.getScript);e=new la(e,new S(k(a.body)),function(){d.d()});var f=new ma(e,c),g="/data/"+b.fa,h=new T(function(l,p){e.K("POST",g,l,p)},f,d.anchor);d.$a.push(function(){h.ga()});var i=new v({$anchor:d.anchor,$updateView:_(d.d).bind(d),$config:b,$console:q.console,$datastore:ia(h,{load:h.load,loadMany:h.ja,loadOrCreate:h.Ja,loadAll:h.Ia,save:h.save,remove:h.remove, +flush:h.ga,query:h.Ua,entity:h.C,entities:h.entities,documentCountsByUser:h.Fb,userDocumentIdsByEntity:h.yc,join:h.join}),$save:function(l){h.sc(i.p,l,d.anchor)},$window:q,$uid:function(){return""+(new Date).getTime()},$users:f},"ROOT");a.data("scope",i);d.C(i);d.compile();c.bind();(new A(a)).bind();var j=_(ia(i,{updateView:i.d,set:i.h,get:i.get,eval:i.eval})).extend({init:function(){b.location.listen(_(d.mc).bind(d));d.Ra();d.Lb();i.d();return j},element:a[0],config:b});return j}function G(a,b,c){var d= +_.last(b);o(c,function(e){d[e]=_[e]});m[a]=m[a]||{};o(b,function(e){U(m[a],e)})}function x(a,b,c,d){this.B=a;this.location=c;this.anchor={};this.Dc=b;this.zb=d||{};this.$a=[]}function R(a,b){this.document=a;this.aa=b;this.window=q;this.D=[]}function T(a,b,c){this.post=a;this.bb=b;this.z={M:[]};this.anchor=c;this.P=[]}function w(a,b){var c=[];aa(c,a,b?"\n ":null,_([]));return c.join("")}function P(a){try{var b=new D(a,true),c=b.Z();b.G();return c()}catch(d){W("fromJson error: ",a,d);throw d;}}function aa(a, +b,c,d){if(typeof b=="object"){if(d.include(b)){a.push("RECURSION");return}d.push(b)}var e=typeof b;if(b===null)a.push("null");else if(e==="function")return;else if(e==="boolean")a.push(""+b);else if(e==="number")isNaN(b)?a.push("null"):a.push(""+b);else if(e==="string")return a.push(m.String.quoteUnicode(b));else if(e==="object")if(b instanceof Array){a.push("[");var f=b.length;e=false;for(var g=0;g>2;c=(c&3)<<4|d>>4;g=(d&15)<<2|e>>6;h=e&63;if(isNaN(d))g=h=64;else if(isNaN(e))h=64;b=b+this.s.charAt(f)+this.s.charAt(c)+this.s.charAt(g)+this.s.charAt(h)}return b}, +jd:function(a){var b="",c,d,e,f,g,h=0;for(a=a.replace(/[^A-Za-z0-9\+\/\=]/g,"");h>4;d=(d&15)<<4|f>>2;e=(f&3)<<6|g;b+=String.fromCharCode(c);if(f!=64)b+=String.fromCharCode(d);if(g!=64)b+=String.fromCharCode(e)}return b=ba.nb(b)},ob:function(a){a=a.replace(/\r\n/g,"\n");for(var b="",c=0;c127&&d<2048)b+=String.fromCharCode(d>>6|192);else{b+=String.fromCharCode(d>>12|224);b+=String.fromCharCode(d>>6&63|128)}b+=String.fromCharCode(d&63|128)}}return b},nb:function(a){for(var b="",c=0,d=c1=c2=0;c191&&d<224){c2=a.charCodeAt(c+1);b+=String.fromCharCode((d&31)<<6|c2&63);c+=2}else{c2=a.charCodeAt(c+1);c3=a.charCodeAt(c+2);b+=String.fromCharCode((d&15)<<12|(c2&63)<<6|c3&63);c+=3}}return b}};if(typeof y.getAttribute== +"undefined")y.getAttribute=J();if(typeof Node=="undefined")Node={ELEMENT_NODE:1,ATTRIBUTE_NODE:2,TEXT_NODE:3,CDATA_SECTION_NODE:4,ENTITY_REFERENCE_NODE:5,ENTITY_NODE:6,PROCESSING_INSTRUCTION_NODE:7,COMMENT_NODE:8,DOCUMENT_NODE:9,DOCUMENT_TYPE_NODE:10,DOCUMENT_FRAGMENT_NODE:11,NOTATION_NODE:12};q.console||(q.console={log:n,error:n});var O,o=_.each,U=_.extend,k=q.jQuery,X=k.browser.msie,m=q.angular||(q.angular={}),Ba=m.validator||(m.validator={}),r=m.filter||(m.filter={}),ca=m.callbacks||(m.callbacks= +{});m.alert||(m.alert=function(){K(arguments);q.alert.apply(q,arguments)});ha.prototype={watch:function(){var a=this;function b(){if(a.I!==a.location.href){var c=a.location.hash.match(/^#\$iframe_notify=(.*)$/);if(c){a.I.match(/#/)||(a.I+="#");a.location.href=a.I;c="_iframe_notify_"+c[1];var d=ca[c];delete ca[c];try{(d||n)()}catch(e){alert(e)}}else{a.Wb(a.location.href);a.I=a.location.href}}a.setTimeout(b,a.delay)}b();return this},h:function(a){var b=this.location.href;b.match(/#/)||(b+="#");if(b!= +a)this.location.href=a;this.md=a},get:function(){return q.location.href}};m.startUrlWatcher=function(){return(new ha(q.location)).watch()};m.compile=function(a,b){b=_({server:"",location:{get:n,set:n,listen:n}}).extend(b||{});Fa(b);Ea();return Ga(k(a),b)};var H={typeOf:function(a){if(a===null)return"null";var b=typeof a;if(b=="object"){if(a instanceof Array)return"array";if(a instanceof Date)return"date";if(a.nodeType==1)return"element"}return b}},V={},Ha={includeIf:function(a,b,c){var d=_.indexOf(a, +b);if(c)d==-1&&a.push(b);else a.splice(d,1);return a},sum:function(a,b){b=m.Function.compile(b);for(var c=0,d=0;d=0&&a.splice(c,1);return b},find:function(a,b,c){if(b){var d=m.Function.compile(b);_.detect(a,function(e){if(d(e)){c=e;return true}});return c}},findById:function(a,b){return m.Lc.find(a,function(c){return c.w==b},null)},filter:function(a,b){var c=[];c.wb=function(j){for(var l=0;l-1;case "object":for(var p in j)if(p.charAt(0)!=="$"&&e(j[p],l))return true;return false;case "array":for(p=0;p=0&&a.splice(d,1);a.unshift((c?"-":"+")+b);return a},orderByDirection:function(a,b,c,d){c=c||"ng-ascend";d=d||"ng-descend";a=a[0]||"";var e=true;if(a.charAt(0)=="-"){a=a.substring(1);e=false}else if(a.charAt(0)=="+")a=a.substring(1);return a==b?e?c:d:""},merge:function(a, +b,c){var d=a[b];if(!d){d={};a[b]=d}Z(c,d);return a}},Ia={quote:function(a){return'"'+a.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\n/g,"\\n").replace(/\f/g,"\\f").replace(/\r/g,"\\r").replace(/\t/g,"\\t").replace(/\v/g,"\\v")+'"'},quoteUnicode:function(a){a=m.String.quote(a);for(var b=[],c=0;c-1;){c1||x.H(a[0])!==null};x.H=function(a){return(a=a.replace(/\n/gm," ").match(/^\{\{(.*)\}\}$/))?a[1]:null};x.prototype={nc:function(a){var b={};a.replace(/(?:^|&)([^&=]*)=?([^&]*)/g,function(c,d, +e){if(d)b[decodeURIComponent(d)]=decodeURIComponent(e)});return b},Ra:function(){var a=this,b=this.location.get()||"",c=b.indexOf("#");if(!(c<0)){b=this.nc(b.substring(c+1));o(a.anchor,function(d,e){delete a.anchor[e]});o(b,function(d,e){a.anchor[e]=d})}},mc:function(){this.Ra();this.d()},xc:function(){var a=this.location.get(),b=a.indexOf("#");if(b>-1)a=a.substring(0,b);a+="#";b="";for(var c in this.anchor){var d=this.anchor[c];if(typeof d==="undefined"||d===null)delete this.anchor[c];else{a+=b+ +encodeURIComponent(c);if(d!==true)a+="="+encodeURIComponent(d);b="&"}}this.location.h(a);return a},d:function(){(new Date).getTime();var a=k(this.B).scope();a.h("$invalidWidgets",[]);a.d();(new Date).getTime();this.xc();_.each(this.$a,function(b){b()})},R:function(a){var b=k(this.B),c=b.find(a);if(b.is(a))c=c.andSelf();return c},Lb:function(){this.R("[ng-init]").each(function(){var a=k(this),b=a.scope();try{b.eval(a.attr("ng-init"))}catch(c){alert("EVAL ERROR:\n"+a.attr("ng-init")+"\n"+w(c,true))}})}, +C:function(a){this.R("[ng-entity]").attr("ng-watch",function(){try{var b=k(this);return a.C(b.attr("ng-entity"))+(b.attr("ng-watch")||"")}catch(c){alert(c)}})},compile:function(){var a=k(this.B);if(this.zb.Qc){var b=this.R(":submit").not("[ng-action]");b.attr("ng-action","$save()");b.not(":disabled").not("ng-bind-attr").attr("ng-bind-attr",'{disabled:"{{$invalidWidgets}}"}')}this.Sa(this.B)(this.B,a.scope(),"");this.R("a[ng-action]").live("click",function(){var c=k(this),d=c.scope();try{d.eval(c.attr("ng-action")); +c.removeAttr("ng-error");c.removeClass("ng-exception")}catch(e){c.addClass("ng-exception");c.attr("ng-error",w(e,true))}d.get("$updateView")();return false})},wc:function(a,b,c){b=b.concat();var d=b.pop(),e=x.X(a.nodeValue);if(e.length>1||x.H(e[0])){var f=a.parentNode;if(ea(f)){f.setAttribute("ng-bind-template",a.nodeValue);c.push({path:b,b:function(l){return new M(l,l.getAttribute("ng-bind-template"))}})}else for(var g=0;g2&&a.setAttribute("ng-bind-attr",d)}a.getAttribute||K(a);var j=a.getAttribute("ng-repeat");if(j){a.removeAttribute("ng-repeat");var l=this.Sa(a);d=y.createComment("ng-repeat: "+ +j);e=a.parentNode;e.insertBefore(d,a);e.removeChild(a);function p(s,t,z){var I=k(a).clone();I.css("display","");I.attr("ng-repeat-index",""+z);I.data("scope",s);l(I[0],s,t+z+":");return I}c.push({path:b,b:function(s,t,z){return new Aa(k(s),j,p,z)}})}else{a.getAttribute("ng-eval")&&c.push({path:b,b:this.fc});a.getAttribute("ng-bind")&&c.push({path:b,b:this.Pa});a.getAttribute("ng-bind-attr")&&c.push({path:b,b:this.bc});a.getAttribute("ng-hide")&&c.push({path:b,b:this.gc});a.getAttribute("ng-show")&& +c.push({path:b,b:this.hc});a.getAttribute("ng-class")&&c.push({path:b,b:this.cc});a.getAttribute("ng-class-odd")&&c.push({path:b,b:this.ec});a.getAttribute("ng-class-even")&&c.push({path:b,b:this.dc});a.getAttribute("ng-style")&&c.push({path:b,b:this.ic});a.getAttribute("ng-watch")&&c.push({path:b,b:this.jc});d=a.nodeName;if(d=="INPUT"||d=="TEXTAREA"||d=="SELECT"||d=="BUTTON"){var B=this;c.push({path:b,b:function(s,t,z){s.name=z+s.name.split(":").pop();return B.Dc.Ab(k(s),t)}})}if(d=="OPTION")if(!k("')};L.prototype={e:function(a){var b=this.view.find("input").attr("checked")? +this.value:null;if(this.c===b)return false;else{a.h(this.Xa,b);return true}},d:function(a){if((a=a.get(this.Xa))&&this.value!==a){this.value=a;this.view.find("a").attr("href",this.value.url).text(this.value.text);this.view.find("span").text(m.filter.bytes(this.value.size))}this.view.find("input").attr("checked",!!a)}};Q.prototype={e:N(true),d:n};Q.Rb=new Q;var La=Q;na.prototype={e:function(a){var b=this.view.value;if(this.c===b)return false;else{a.l(this.exp,b);this.c=b;return true}},d:function(a){var b= +this.view,c=a.get(this.exp);if(typeof c==="undefined"){c=this.o;a.l(this.exp,c)}c=c?c:"";if(this.c!=c)this.c=b.value=c;var d=false;b.removeAttribute("ng-error");if(this.rc)d=!(c&&c.length>0);var e=d?"Required Value":null;if(!d&&this.L&&c){e=a.Ac(this.L,c);d=!!e}if(this.Ga!==e){this.Ga=d;if(e!==null){b.setAttribute("ng-error",e);a.Yb(this)}k(b).toggleClass("ng-validation-error",d)}}};oa.prototype={e:function(a){var b=this.view;b=b.checked?b.value:"";if(this.c===b)return false;else{a.l(this.exp,b); +this.c=b;return true}},d:function(a){var b=this.view,c=a.eval(this.exp);if(typeof c==="undefined"){c=this.o;a.l(this.exp,c)}b.checked=b.value==""+c}};pa.prototype={e:function(a){if(this.view.selectedIndex<0)a.l(this.exp,null);else{var b=this.view.value;if(this.c===b)return false;else{a.l(this.exp,b);this.c=b;return true}}},d:function(a){var b=this.view,c=a.get(this.exp);if(typeof c==="undefined"){c=this.o;a.l(this.exp,c)}if(c!==this.c){b.value=c?c:"";this.c=c}}};qa.prototype={selected:function(){for(var a= +[],b=this.view.options,c=0;cd;--b){var p=this.children.pop().element[0];p.parentNode.removeChild(p)}if(h&&h.element[0].nodeName==="OPTION")if(d=k(h.element[0].parentNode).data("controller")){d.c= +undefined;d.d(a)}})}};A.va="mouseleave mouseout click dblclick keypress keyup";A.lc=function(){A.ma();var a=k(this);a.bind(A.va,A.ma);var b=a.position(),c=y.documentElement,d=(self.innerWidth||c&&c.clientWidth||y.body.clientWidth)-b.left;c=a.hasClass("ng-exception")?"EXCEPTION:":"Validation error...";a=a.attr("ng-error");d=d>375?"left":"right";c=k("
"+c+"
"+a+"
");k("body").append(c); +if(d==="left")a=b.left+this.offsetWidth+11;else{a=b.left-315;c.find(".ng-arrow-right").css({left:301})}c.css({left:a+"px",top:b.top-3+"px"});return true};A.ma=function(){k("#ng-callout").unbind(A.va,A.ma).remove();return true};A.prototype={bind:function(){this.B.find(".ng-validation-error,.ng-exception").live("mouseover",A.lc)}};S.hb='
loading....
';S.prototype={vb:function(){this.$===0&&this.Ka.show();this.$++},Hb:function(){this.$--;this.$===0&& +this.Ka.hide("fold")}}})(window,document); diff --git a/angular.js b/angular.js index 9609929c..adfa3611 100644 --- a/angular.js +++ b/angular.js @@ -299,16 +299,6 @@ function bind(_this, _function) { }; } -function shiftBind(_this, _function) { - return function() { - var args = [ this ]; - for ( var i = 0; i < arguments.length; i++) { - args.push(arguments[i]); - } - return _function.apply(_this, args); - }; -} - function outerHTML(node) { var temp = document.createElement('div'); temp.appendChild(node); @@ -342,159 +332,9 @@ function merge(src, dst) { } // //////////////////////////// -// Angular +// UrlWatcher // //////////////////////////// -function Angular(document, head, config) { - this.document = jQuery(document); - this.head = jQuery(head); - this.config = config; - this.location = window.location; -} - -Angular.prototype = { - load: function() { - this.configureLogging(); - log("Server: " + this.config.server); - this.configureJQueryPlugins(); - this.computeConfiguration(); - this.bindHtml(); - }, - - configureJQueryPlugins: function() { - log('Angular.configureJQueryPlugins()'); - jQuery['fn']['scope'] = function() { - var element = this; - while (element && element.get(0)) { - var scope = element.data("scope"); - if (scope) - return scope; - element = element.parent(); - } - return null; - }; - jQuery['fn']['controller'] = function() { - return this.data('controller') || NullController.instance; - }; - }, - - uid: function() { - return "" + new Date().getTime(); - }, - - computeConfiguration: function() { - var config = this.config; - if (!config.database) { - var match = config.server.match(/https?:\/\/([\w]*)/); - config.database = match ? match[1] : "$MEMORY"; - } - }, - - bindHtml: function() { - log('Angular.bindHtml()'); - var watcher = this.watcher = new UrlWatcher(this.location); - var document = this.document; - var widgetFactory = new WidgetFactory(this.config.server, this.config.database); - var binder = new Binder(document[0], widgetFactory, watcher, this.config); - widgetFactory.onChangeListener = shiftBind(binder, binder.updateModel); - var controlBar = new ControlBar(document.find('body'), this.config.server); - var onUpdate = function(){binder.updateView();}; - var server = this.config.database=="$MEMORY" ? - new FrameServer(this.window) : - new Server(this.config.server, jQuery.getScript); - server = new VisualServer(server, new Status(jQuery(document.body)), onUpdate); - var users = new Users(server, controlBar); - var databasePath = '/data/' + this.config.database; - var post = function(request, callback){ - server.request("POST", databasePath, request, callback); - }; - var datastore = new DataStore(post, users, binder.anchor); - binder.updateListeners.push(function(){datastore.flush();}); - var scope = new Scope( { - '$anchor' : binder.anchor, - '$binder' : binder, - '$config' : this.config, - '$console' : window.console, - '$datastore' : datastore, - '$save' : function(callback) { - datastore.saveScope(scope.state, callback, binder.anchor); - }, - '$window' : window, - '$uid' : this.uid, - '$users' : users - }, "ROOT"); - - document.data('scope', scope); - log('$binder.entity()'); - binder.entity(scope); - - log('$binder.compile()'); - binder.compile(); - - log('ControlBar.bind()'); - controlBar.bind(); - - log('$users.fetchCurrentUser()'); - function fetchCurrentUser() { - users.fetchCurrentUser(function(u) { - if (!u && document.find("[ng-auth=eager]").length) { - users.login(); - } - }); - } - fetchCurrentUser(); - - log('PopUp.bind()'); - new PopUp(document).bind(); - - log('$binder.parseAnchor()'); - binder.parseAnchor(); - - document.find("body").show(); - log('ready()'); - }, - - visualPost: function(delegate) { - var status = new Status(jQuery(document.body)); - return function(request, delegateCallback) { - status.beginRequest(request); - var callback = function() { - status.endRequest(); - try { - delegateCallback.apply(this, arguments); - } catch (e) { - alert(toJson(e)); - } - }; - delegate(request, callback); - }; - }, - - configureLogging: function() { - var url = window.location.href + '#'; - url = url.split('#')[1]; - var config = { - debug : null - }; - var configs = url.split('&'); - for ( var i = 0; i < configs.length; i++) { - var part = (configs[i] + '=').split('='); - config[part[0]] = part[1]; - } - if (config.debug == 'console') { - consoleNode = document.createElement("div"); - consoleNode.id = 'ng-console'; - document.getElementsByTagName('body')[0].appendChild(consoleNode); - log = function() { - consoleLog('ng-console-info', arguments); - }; - console.error = function() { - consoleLog('ng-console-error', arguments); - }; - } - } -}; - function UrlWatcher(location) { this.location = location; this.delay = 25; @@ -508,6 +348,9 @@ function UrlWatcher(location) { } UrlWatcher.prototype = { + listen: function(fn){ + this.listener = fn; + }, watch: function() { var self = this; var pull = function() { @@ -534,50 +377,151 @@ UrlWatcher.prototype = { self.setTimeout(pull, self.delay); }; pull(); + return this; }, - setUrl: function(url) { - //TODO: conditionaly? - var existingURL = window.location.href; + set: function(url) { + var existingURL = this.location.href; if (!existingURL.match(/#/)) existingURL += '#'; if (existingURL != url) - window.location.href = url; + this.location.href = url; this.existingURL = url; }, - getUrl: function() { + get: function() { return window.location.href; } }; - -angular['compile'] = function(root, config) { - config = config || {}; - var defaults = { - 'server': "", - 'addUrlChangeListener': noop - }; - //todo: don't start watcher - var angular = new Angular(root, jQuery("head"), _(defaults).extend(config)); - //todo: don't load stylesheet by default - // loader.loadCss('/stylesheets/jquery-ui/smoothness/jquery-ui-1.7.1.css'); - // loader.loadCss('/stylesheets/css'); - angular.load(); - var scope = jQuery(root).scope(); - //TODO: cleanup - return { - 'updateView':function(){return scope.updateView();}, - 'set':function(){return scope.set.apply(scope, arguments);}, - 'get':function(){return scope.get.apply(scope, arguments);}, - 'init':function(){scope.get('$binder.executeInit')(); scope.updateView();}, - 'watchUrl':function(){ - var binder = scope.get('$binder'); - var watcher = angular.watcher; - watcher.listener = bind(binder, binder.onUrlChange, watcher); - watcher.onUpdate = function(){alert("update");}; - watcher.watch(); + +///////////////////////////////////////////////// +function configureJQueryPlugins() { + log('Angular.configureJQueryPlugins()'); + var fn = jQuery['fn']; + fn['scope'] = function() { + var element = this; + while (element && element.get(0)) { + var scope = element.data("scope"); + if (scope) + return scope; + element = element.parent(); } + return null; + }; + fn['controller'] = function() { + return this.data('controller') || NullController.instance; + }; +} + +function configureLogging(config) { + if (config.debug == 'console' && !consoleNode) { + consoleNode = document.createElement("div"); + consoleNode.id = 'ng-console'; + document.getElementsByTagName('body')[0].appendChild(consoleNode); + log = function() { + consoleLog('ng-console-info', arguments); + }; + console.error = function() { + consoleLog('ng-console-error', arguments); + }; + } +} + +function exposeMethods(obj, methods){ + var bound = {}; + foreach(methods, function(fn, name){ + bound[name] = _(fn).bind(obj); + }); + return bound; +} + +function wireAngular(element, config) { + var widgetFactory = new WidgetFactory(config['server'], config['database']); + var binder = new Binder(element[0], widgetFactory, config['location'], config); + var controlBar = new ControlBar(element.find('body'), config.server); + var onUpdate = function(){binder.updateView();}; + var server = config.database=="$MEMORY" ? + new FrameServer(this.window) : + new Server(config.server, jQuery.getScript); + server = new VisualServer(server, new Status(jQuery(element.body)), onUpdate); + var users = new Users(server, controlBar); + var databasePath = '/data/' + config.database; + var post = function(request, callback){ + server.request("POST", databasePath, request, callback); }; + var datastore = new DataStore(post, users, binder.anchor); + binder.updateListeners.push(function(){datastore.flush();}); + var scope = new Scope({ + '$anchor' : binder.anchor, + '$updateView': _(binder.updateView).bind(binder), + '$config' : config, + '$console' : window.console, + '$datastore' : exposeMethods(datastore, { + 'load': datastore.load, + 'loadMany': datastore.loadMany, + 'loadOrCreate': datastore.loadOrCreate, + 'loadAll': datastore.loadAll, + 'save': datastore.save, + 'remove': datastore.remove, + 'flush': datastore.flush, + 'query': datastore.query, + 'entity': datastore.entity, + 'entities': datastore.entities, + 'documentCountsByUser': datastore.documentCountsByUser, + 'userDocumentIdsByEntity': datastore.userDocumentIdsByEntity, + 'join': datastore.join + }), + '$save' : function(callback) { + datastore.saveScope(scope.state, callback, binder.anchor); + }, + '$window' : window, + '$uid' : function() { + return "" + new Date().getTime(); + }, + '$users' : users + }, "ROOT"); + + element.data('scope', scope); + binder.entity(scope); + binder.compile(); + controlBar.bind(); + + //TODO: remove this code + new PopUp(element).bind(); + + var self = _(exposeMethods(scope, { + 'updateView': scope.updateView, + 'set': scope.set, + 'get': scope.get, + 'eval': scope.eval + })).extend({ + 'init':function(){ + config['location']['listen'](_(binder.onUrlChange).bind(binder)); + binder.parseAnchor(); + binder.executeInit(); + scope.updateView(); + return self; + }, + 'element':element[0], + 'config':config + }); + return self; +} + +angular['startUrlWatcher'] = function(){ + return new UrlWatcher(window['location']).watch(); +}; + +angular['compile'] = function(element, config) { + config = _({ + 'server': "", + 'location': {'get':noop, 'set':noop, 'listen':noop} + }).extend(config||{}); + + configureLogging(config); + configureJQueryPlugins(); + + return wireAngular(jQuery(element), config); };var angularGlobal = { 'typeOf':function(obj){ if (obj === null) return "null"; @@ -901,9 +845,9 @@ defineApi('String', [angularGlobal, angularString], []); defineApi('Date', [angularGlobal, angularDate], []); defineApi('Function', [angularGlobal, angularCollection, angularFunction], ['bind', 'bindAll', 'delay', 'defer', 'wrap', 'compose']); -function Binder(doc, widgetFactory, urlWatcher, config) { +function Binder(doc, widgetFactory, location, config) { this.doc = doc; - this.urlWatcher = urlWatcher; + this.location = location; this.anchor = {}; this.widgetFactory = widgetFactory; this.config = config || {}; @@ -951,9 +895,8 @@ Binder.prototype = { return params; }, - parseAnchor: function(url) { - var self = this; - url = url || this.urlWatcher.getUrl(); + parseAnchor: function() { + var self = this, url = this.location.get() || ""; var anchorIndex = url.indexOf('#'); if (anchorIndex < 0) return; @@ -968,13 +911,13 @@ Binder.prototype = { }); }, - onUrlChange: function (url) { - this.parseAnchor(url); + onUrlChange: function() { + this.parseAnchor(); this.updateView(); }, updateAnchor: function() { - var url = this.urlWatcher.getUrl(); + var url = this.location.get(); var anchorIndex = url.indexOf('#'); if (anchorIndex > -1) url = url.substring(0, anchorIndex); @@ -991,7 +934,7 @@ Binder.prototype = { sep = '&'; } } - this.urlWatcher.setUrl(url); + this.location.set(url); return url; }, @@ -1057,7 +1000,7 @@ Binder.prototype = { jNode.addClass("ng-exception"); jNode.attr('ng-error', toJson(e, true)); } - scope.eval('$binder.updateView()'); + scope.get('$updateView')(); return false; }); }, @@ -1980,7 +1923,7 @@ array = [].constructor; function toJson(obj, pretty){ var buf = []; - toJsonArray(buf, obj, pretty ? "\n " : null); + toJsonArray(buf, obj, pretty ? "\n " : null, _([])); return buf.join(''); }; @@ -2003,7 +1946,14 @@ function fromJson(json) { angular['toJson'] = toJson; angular['fromJson'] = fromJson; -function toJsonArray(buf, obj, pretty){ +function toJsonArray(buf, obj, pretty, stack){ + if (typeof obj == "object") { + if (stack.include(obj)) { + buf.push("RECURSION"); + return; + } + stack.push(obj); + } var type = typeof obj; if (obj === null) { buf.push("null"); @@ -2030,7 +1980,7 @@ function toJsonArray(buf, obj, pretty){ if (typeof item == 'function' || typeof item == 'undefined') { buf.push("null"); } else { - toJsonArray(buf, item, pretty); + toJsonArray(buf, item, pretty, stack); } sep = true; } @@ -2060,7 +2010,7 @@ function toJsonArray(buf, obj, pretty){ } buf.push(angular['String']['quote'](key)); buf.push(":"); - toJsonArray(buf, value, childPretty); + toJsonArray(buf, value, childPretty, stack); comma = true; } } catch (e) { @@ -2069,6 +2019,9 @@ function toJsonArray(buf, obj, pretty){ buf.push("}"); } } + if (typeof obj == "object") { + stack.pop(); + } }; // Single $ is special and does not get searched // Double $$ is special an is client only (does not get sent to server) @@ -3153,7 +3106,7 @@ Users.prototype = { 'fetchCurrentUser':function(callback) { var self = this; this.server.request("GET", "/account.json", {}, function(code, response){ - self.current = response.user; + self['current'] = response['user']; callback(response.user); }); }, @@ -3161,7 +3114,7 @@ Users.prototype = { 'logout': function(callback) { var self = this; this.controlBar.logout(function(){ - delete self.current; + delete self['current']; (callback||noop)(); }); }, @@ -3270,7 +3223,6 @@ function WidgetFactory(serverUrl, database) { alert("ERROR: swfobject not loaded!"); }; } - this.onChangeListener = function(){}; }; WidgetFactory.prototype = { @@ -3304,12 +3256,12 @@ WidgetFactory.prototype = { throw 'Unknown type: ' + type; } input.data('controller', controller); - var binder = scope.get('$binder'); + var updateView = scope.get('$updateView'); var action = function() { if (controller.updateModel(scope)) { var action = jQuery(controller.view).attr('ng-action') || ""; if (scope.evalWidget(controller, action)) { - binder.updateView(scope); + updateView(scope); } } return bubbleEvent; @@ -3335,12 +3287,6 @@ WidgetFactory.prototype = { var cntl = new FileController(view, fileInput[0].name, swfNode, this.serverUrl + "/data/" + this.database); jQuery(swfNode).data('controller', cntl); return cntl; - }, - - createTextWidget: function(textInput) { - var controller = new TextController(textInput); - controller.onChange(this.onChangeListener); - return controller; } }; ///////////////////// diff --git a/example/tweeter/tweeterclient.js b/example/tweeter/tweeterclient.js index 2fb60b0a..8c68fac0 100644 --- a/example/tweeter/tweeterclient.js +++ b/example/tweeter/tweeterclient.js @@ -1,6 +1,8 @@ function noop(){} $(document).ready(function(){ - var scope = window.scope = angular.compile(document); + var scope = window.scope = angular.compile(document, { + location:angular.startUrlWatcher() + }); scope.getJSON = function(url, callback) { var list = []; var self = this; @@ -32,6 +34,5 @@ $(document).ready(function(){ notes:'Author of & Ruby guru see: http://www.angularjs.org.', profile_image_url:'http://media.linkedin.com/mpr/mpr/shrink_80_80/p/2/000/005/0a8/044278d.jpg'} ]); - scope.watchUrl(); scope.init(); }); diff --git a/jsTestDriver.conf b/jsTestDriver.conf index 1fbe05a7..28958ee4 100644 --- a/jsTestDriver.conf +++ b/jsTestDriver.conf @@ -1,6 +1,8 @@ server: http://localhost:9876 load: + - lib/jasmine/jasmine-0.10.0.js + - lib/jasmine-jstd-adapter/JasmineAdapter.js - lib/webtoolkit/webtoolkit.base64.js - lib/jquery/jquery-1.3.2.js - lib/jquery/jquery-ui-1.7.1.custom.min.js diff --git a/src/Angular.js b/src/Angular.js index 3dc72ff7..51fca458 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -134,16 +134,6 @@ function bind(_this, _function) { }; } -function shiftBind(_this, _function) { - return function() { - var args = [ this ]; - for ( var i = 0; i < arguments.length; i++) { - args.push(arguments[i]); - } - return _function.apply(_this, args); - }; -} - function outerHTML(node) { var temp = document.createElement('div'); temp.appendChild(node); @@ -177,159 +167,9 @@ function merge(src, dst) { } // //////////////////////////// -// Angular +// UrlWatcher // //////////////////////////// -function Angular(document, head, config) { - this.document = jQuery(document); - this.head = jQuery(head); - this.config = config; - this.location = window.location; -} - -Angular.prototype = { - load: function() { - this.configureLogging(); - log("Server: " + this.config.server); - this.configureJQueryPlugins(); - this.computeConfiguration(); - this.bindHtml(); - }, - - configureJQueryPlugins: function() { - log('Angular.configureJQueryPlugins()'); - jQuery['fn']['scope'] = function() { - var element = this; - while (element && element.get(0)) { - var scope = element.data("scope"); - if (scope) - return scope; - element = element.parent(); - } - return null; - }; - jQuery['fn']['controller'] = function() { - return this.data('controller') || NullController.instance; - }; - }, - - uid: function() { - return "" + new Date().getTime(); - }, - - computeConfiguration: function() { - var config = this.config; - if (!config.database) { - var match = config.server.match(/https?:\/\/([\w]*)/); - config.database = match ? match[1] : "$MEMORY"; - } - }, - - bindHtml: function() { - log('Angular.bindHtml()'); - var watcher = this.watcher = new UrlWatcher(this.location); - var document = this.document; - var widgetFactory = new WidgetFactory(this.config.server, this.config.database); - var binder = new Binder(document[0], widgetFactory, watcher, this.config); - widgetFactory.onChangeListener = shiftBind(binder, binder.updateModel); - var controlBar = new ControlBar(document.find('body'), this.config.server); - var onUpdate = function(){binder.updateView();}; - var server = this.config.database=="$MEMORY" ? - new FrameServer(this.window) : - new Server(this.config.server, jQuery.getScript); - server = new VisualServer(server, new Status(jQuery(document.body)), onUpdate); - var users = new Users(server, controlBar); - var databasePath = '/data/' + this.config.database; - var post = function(request, callback){ - server.request("POST", databasePath, request, callback); - }; - var datastore = new DataStore(post, users, binder.anchor); - binder.updateListeners.push(function(){datastore.flush();}); - var scope = new Scope( { - '$anchor' : binder.anchor, - '$binder' : binder, - '$config' : this.config, - '$console' : window.console, - '$datastore' : datastore, - '$save' : function(callback) { - datastore.saveScope(scope.state, callback, binder.anchor); - }, - '$window' : window, - '$uid' : this.uid, - '$users' : users - }, "ROOT"); - - document.data('scope', scope); - log('$binder.entity()'); - binder.entity(scope); - - log('$binder.compile()'); - binder.compile(); - - log('ControlBar.bind()'); - controlBar.bind(); - - log('$users.fetchCurrentUser()'); - function fetchCurrentUser() { - users.fetchCurrentUser(function(u) { - if (!u && document.find("[ng-auth=eager]").length) { - users.login(); - } - }); - } - fetchCurrentUser(); - - log('PopUp.bind()'); - new PopUp(document).bind(); - - log('$binder.parseAnchor()'); - binder.parseAnchor(); - - document.find("body").show(); - log('ready()'); - }, - - visualPost: function(delegate) { - var status = new Status(jQuery(document.body)); - return function(request, delegateCallback) { - status.beginRequest(request); - var callback = function() { - status.endRequest(); - try { - delegateCallback.apply(this, arguments); - } catch (e) { - alert(toJson(e)); - } - }; - delegate(request, callback); - }; - }, - - configureLogging: function() { - var url = window.location.href + '#'; - url = url.split('#')[1]; - var config = { - debug : null - }; - var configs = url.split('&'); - for ( var i = 0; i < configs.length; i++) { - var part = (configs[i] + '=').split('='); - config[part[0]] = part[1]; - } - if (config.debug == 'console') { - consoleNode = document.createElement("div"); - consoleNode.id = 'ng-console'; - document.getElementsByTagName('body')[0].appendChild(consoleNode); - log = function() { - consoleLog('ng-console-info', arguments); - }; - console.error = function() { - consoleLog('ng-console-error', arguments); - }; - } - } -}; - function UrlWatcher(location) { this.location = location; this.delay = 25; @@ -343,6 +183,9 @@ function UrlWatcher(location) { } UrlWatcher.prototype = { + listen: function(fn){ + this.listener = fn; + }, watch: function() { var self = this; var pull = function() { @@ -369,48 +212,149 @@ UrlWatcher.prototype = { self.setTimeout(pull, self.delay); }; pull(); + return this; }, - setUrl: function(url) { - //TODO: conditionaly? - var existingURL = window.location.href; + set: function(url) { + var existingURL = this.location.href; if (!existingURL.match(/#/)) existingURL += '#'; if (existingURL != url) - window.location.href = url; + this.location.href = url; this.existingURL = url; }, - getUrl: function() { + get: function() { return window.location.href; } }; - -angular['compile'] = function(root, config) { - config = config || {}; - var defaults = { - 'server': "", - 'addUrlChangeListener': noop - }; - //todo: don't start watcher - var angular = new Angular(root, jQuery("head"), _(defaults).extend(config)); - //todo: don't load stylesheet by default - // loader.loadCss('/stylesheets/jquery-ui/smoothness/jquery-ui-1.7.1.css'); - // loader.loadCss('/stylesheets/css'); - angular.load(); - var scope = jQuery(root).scope(); - //TODO: cleanup - return { - 'updateView':function(){return scope.updateView();}, - 'set':function(){return scope.set.apply(scope, arguments);}, - 'get':function(){return scope.get.apply(scope, arguments);}, - 'init':function(){scope.get('$binder.executeInit')(); scope.updateView();}, - 'watchUrl':function(){ - var binder = scope.get('$binder'); - var watcher = angular.watcher; - watcher.listener = bind(binder, binder.onUrlChange, watcher); - watcher.onUpdate = function(){alert("update");}; - watcher.watch(); + +///////////////////////////////////////////////// +function configureJQueryPlugins() { + log('Angular.configureJQueryPlugins()'); + var fn = jQuery['fn']; + fn['scope'] = function() { + var element = this; + while (element && element.get(0)) { + var scope = element.data("scope"); + if (scope) + return scope; + element = element.parent(); } + return null; + }; + fn['controller'] = function() { + return this.data('controller') || NullController.instance; }; +} + +function configureLogging(config) { + if (config.debug == 'console' && !consoleNode) { + consoleNode = document.createElement("div"); + consoleNode.id = 'ng-console'; + document.getElementsByTagName('body')[0].appendChild(consoleNode); + log = function() { + consoleLog('ng-console-info', arguments); + }; + console.error = function() { + consoleLog('ng-console-error', arguments); + }; + } +} + +function exposeMethods(obj, methods){ + var bound = {}; + foreach(methods, function(fn, name){ + bound[name] = _(fn).bind(obj); + }); + return bound; +} + +function wireAngular(element, config) { + var widgetFactory = new WidgetFactory(config['server'], config['database']); + var binder = new Binder(element[0], widgetFactory, config['location'], config); + var controlBar = new ControlBar(element.find('body'), config.server); + var onUpdate = function(){binder.updateView();}; + var server = config.database=="$MEMORY" ? + new FrameServer(this.window) : + new Server(config.server, jQuery.getScript); + server = new VisualServer(server, new Status(jQuery(element.body)), onUpdate); + var users = new Users(server, controlBar); + var databasePath = '/data/' + config.database; + var post = function(request, callback){ + server.request("POST", databasePath, request, callback); + }; + var datastore = new DataStore(post, users, binder.anchor); + binder.updateListeners.push(function(){datastore.flush();}); + var scope = new Scope({ + '$anchor' : binder.anchor, + '$updateView': _(binder.updateView).bind(binder), + '$config' : config, + '$console' : window.console, + '$datastore' : exposeMethods(datastore, { + 'load': datastore.load, + 'loadMany': datastore.loadMany, + 'loadOrCreate': datastore.loadOrCreate, + 'loadAll': datastore.loadAll, + 'save': datastore.save, + 'remove': datastore.remove, + 'flush': datastore.flush, + 'query': datastore.query, + 'entity': datastore.entity, + 'entities': datastore.entities, + 'documentCountsByUser': datastore.documentCountsByUser, + 'userDocumentIdsByEntity': datastore.userDocumentIdsByEntity, + 'join': datastore.join + }), + '$save' : function(callback) { + datastore.saveScope(scope.state, callback, binder.anchor); + }, + '$window' : window, + '$uid' : function() { + return "" + new Date().getTime(); + }, + '$users' : users + }, "ROOT"); + + element.data('scope', scope); + binder.entity(scope); + binder.compile(); + controlBar.bind(); + + //TODO: remove this code + new PopUp(element).bind(); + + var self = _(exposeMethods(scope, { + 'updateView': scope.updateView, + 'set': scope.set, + 'get': scope.get, + 'eval': scope.eval + })).extend({ + 'init':function(){ + config['location']['listen'](_(binder.onUrlChange).bind(binder)); + binder.parseAnchor(); + binder.executeInit(); + scope.updateView(); + return self; + }, + 'element':element[0], + 'config':config + }); + return self; +} + +angular['startUrlWatcher'] = function(){ + return new UrlWatcher(window['location']).watch(); +}; + +angular['compile'] = function(element, config) { + config = _({ + 'server': "", + 'location': {'get':noop, 'set':noop, 'listen':noop} + }).extend(config||{}); + + configureLogging(config); + configureJQueryPlugins(); + + return wireAngular(jQuery(element), config); }; \ No newline at end of file diff --git a/src/Binder.js b/src/Binder.js index b687fb77..3fc45a20 100644 --- a/src/Binder.js +++ b/src/Binder.js @@ -1,6 +1,6 @@ -function Binder(doc, widgetFactory, urlWatcher, config) { +function Binder(doc, widgetFactory, location, config) { this.doc = doc; - this.urlWatcher = urlWatcher; + this.location = location; this.anchor = {}; this.widgetFactory = widgetFactory; this.config = config || {}; @@ -48,9 +48,8 @@ Binder.prototype = { return params; }, - parseAnchor: function(url) { - var self = this; - url = url || this.urlWatcher.getUrl(); + parseAnchor: function() { + var self = this, url = this.location.get() || ""; var anchorIndex = url.indexOf('#'); if (anchorIndex < 0) return; @@ -65,13 +64,13 @@ Binder.prototype = { }); }, - onUrlChange: function (url) { - this.parseAnchor(url); + onUrlChange: function() { + this.parseAnchor(); this.updateView(); }, updateAnchor: function() { - var url = this.urlWatcher.getUrl(); + var url = this.location.get(); var anchorIndex = url.indexOf('#'); if (anchorIndex > -1) url = url.substring(0, anchorIndex); @@ -88,7 +87,7 @@ Binder.prototype = { sep = '&'; } } - this.urlWatcher.setUrl(url); + this.location.set(url); return url; }, @@ -154,7 +153,7 @@ Binder.prototype = { jNode.addClass("ng-exception"); jNode.attr('ng-error', toJson(e, true)); } - scope.eval('$binder.updateView()'); + scope.get('$updateView')(); return false; }); }, diff --git a/src/JSON.js b/src/JSON.js index 0c842865..98dfddd2 100644 --- a/src/JSON.js +++ b/src/JSON.js @@ -2,7 +2,7 @@ array = [].constructor; function toJson(obj, pretty){ var buf = []; - toJsonArray(buf, obj, pretty ? "\n " : null); + toJsonArray(buf, obj, pretty ? "\n " : null, _([])); return buf.join(''); }; @@ -25,7 +25,14 @@ function fromJson(json) { angular['toJson'] = toJson; angular['fromJson'] = fromJson; -function toJsonArray(buf, obj, pretty){ +function toJsonArray(buf, obj, pretty, stack){ + if (typeof obj == "object") { + if (stack.include(obj)) { + buf.push("RECURSION"); + return; + } + stack.push(obj); + } var type = typeof obj; if (obj === null) { buf.push("null"); @@ -52,7 +59,7 @@ function toJsonArray(buf, obj, pretty){ if (typeof item == 'function' || typeof item == 'undefined') { buf.push("null"); } else { - toJsonArray(buf, item, pretty); + toJsonArray(buf, item, pretty, stack); } sep = true; } @@ -82,7 +89,7 @@ function toJsonArray(buf, obj, pretty){ } buf.push(angular['String']['quote'](key)); buf.push(":"); - toJsonArray(buf, value, childPretty); + toJsonArray(buf, value, childPretty, stack); comma = true; } } catch (e) { @@ -91,4 +98,7 @@ function toJsonArray(buf, obj, pretty){ buf.push("}"); } } + if (typeof obj == "object") { + stack.pop(); + } }; diff --git a/src/Users.js b/src/Users.js index 47da4f73..79ed3129 100644 --- a/src/Users.js +++ b/src/Users.js @@ -7,7 +7,7 @@ Users.prototype = { 'fetchCurrentUser':function(callback) { var self = this; this.server.request("GET", "/account.json", {}, function(code, response){ - self.current = response.user; + self['current'] = response['user']; callback(response.user); }); }, @@ -15,7 +15,7 @@ Users.prototype = { 'logout': function(callback) { var self = this; this.controlBar.logout(function(){ - delete self.current; + delete self['current']; (callback||noop)(); }); }, diff --git a/src/Widgets.js b/src/Widgets.js index f93f2476..a012adf3 100644 --- a/src/Widgets.js +++ b/src/Widgets.js @@ -9,7 +9,6 @@ function WidgetFactory(serverUrl, database) { alert("ERROR: swfobject not loaded!"); }; } - this.onChangeListener = function(){}; }; WidgetFactory.prototype = { @@ -43,12 +42,12 @@ WidgetFactory.prototype = { throw 'Unknown type: ' + type; } input.data('controller', controller); - var binder = scope.get('$binder'); + var updateView = scope.get('$updateView'); var action = function() { if (controller.updateModel(scope)) { var action = jQuery(controller.view).attr('ng-action') || ""; if (scope.evalWidget(controller, action)) { - binder.updateView(scope); + updateView(scope); } } return bubbleEvent; @@ -74,12 +73,6 @@ WidgetFactory.prototype = { var cntl = new FileController(view, fileInput[0].name, swfNode, this.serverUrl + "/data/" + this.database); jQuery(swfNode).data('controller', cntl); return cntl; - }, - - createTextWidget: function(textInput) { - var controller = new TextController(textInput); - controller.onChange(this.onChangeListener); - return controller; } }; ///////////////////// diff --git a/test/AngularTest.js b/test/AngularTest.js index 9610ef76..a9146adf 100644 --- a/test/AngularTest.js +++ b/test/AngularTest.js @@ -1,20 +1,5 @@ AngularTest = TestCase('AngularTest'); -AngularTest.prototype.testDefaultDatabasePathFromSubdomain = function() { - var loader = new Angular(null, null, {server:"http://account.getangular.com", database:"database"}); - loader.computeConfiguration(); - assertEquals("database", loader.config.database); - - loader = new Angular(null, null, {server:"http://account.getangular.com"}); - loader.computeConfiguration(); - assertEquals("account", loader.config.database); - - loader = new Angular(null, null, {server:"https://account.getangular.com"}); - loader.computeConfiguration(); - assertEquals("account", loader.config.database); -}; - - UrlWatcherTest = TestCase('UrlWatcherTest'); diff --git a/test/BinderTest.js b/test/BinderTest.js index 0ffd2120..bbb3eb8f 100644 --- a/test/BinderTest.js +++ b/test/BinderTest.js @@ -5,10 +5,10 @@ function compile(content, initialScope, config) { config = config || {autoSubmit:true}; var scope = new Scope(initialScope, "ROOT"); h.data('scope', scope); - var binder = new Binder(h[0], new WidgetFactory(), new MockUrlWatcher(), config); + var binder = new Binder(h[0], new WidgetFactory(), new MockLocation(), config); var datastore = new DataStore(); scope.set("$datastore", datastore); - scope.set("$binder", binder); + scope.set("$updateView", _(binder.updateView).bind(binder)); scope.set("$anchor", binder.anchor); binder.entity(scope); binder.compile(); @@ -120,7 +120,7 @@ BinderTest.prototype.testChangingTextareaUpdatesModel = function(){ var form = html(''); var scope = new Scope({model:{}}); form.data('scope', scope); - var binder = new Binder(form.get(0), new WidgetFactory(), new MockUrlWatcher()); + var binder = new Binder(form.get(0), new WidgetFactory(), new MockLocation()); binder.compile(); binder.updateView(); assertEquals(scope.get('model').note, 'abc'); @@ -131,7 +131,7 @@ BinderTest.prototype.testChangingRadioUpdatesModel = function(){ ''); var scope = new Scope({model:{}}); form.data('scope', scope); - var binder = new Binder(form.get(0), new WidgetFactory(), new MockUrlWatcher()); + var binder = new Binder(form.get(0), new WidgetFactory(), new MockLocation()); binder.compile(); binder.updateView(); assertEquals(scope.get('model').price, 'A'); @@ -141,7 +141,7 @@ BinderTest.prototype.testChangingCheckboxUpdatesModel = function(){ var form = html(''); var scope = new Scope({model:{}}); form.data('scope', scope); - var binder = new Binder(form.get(0), new WidgetFactory(), new MockUrlWatcher()); + var binder = new Binder(form.get(0), new WidgetFactory(), new MockLocation()); binder.compile(); binder.updateView(); assertEquals('A', scope.get('model').price); @@ -157,7 +157,7 @@ BinderTest.prototype.testChangingSelectNonSelectedUpdatesModel = function(){ var form = html(''); var scope = new Scope({model:{}}); form.data('scope', scope); - var binder = new Binder(form.get(0), new WidgetFactory(), new MockUrlWatcher()); + var binder = new Binder(form.get(0), new WidgetFactory(), new MockLocation()); binder.compile(); binder.updateView(); assertEquals('A', scope.get('model').price); @@ -171,7 +171,7 @@ BinderTest.prototype.testChangingMultiselectUpdatesModel = function(){ ''); var scope = new Scope({Invoice:{}}); form.data('scope', scope); - var binder = new Binder(form.get(0), new WidgetFactory(), new MockUrlWatcher()); + var binder = new Binder(form.get(0), new WidgetFactory(), new MockLocation()); binder.compile(); binder.updateView(); assertJsonEquals(["A", "B"], scope.get('Invoice').options); @@ -181,7 +181,7 @@ BinderTest.prototype.testChangingSelectSelectedUpdatesModel = function(){ var form = html(''); var scope = new Scope({model:{}}); form.data('scope', scope); - var binder = new Binder(form.get(0), new WidgetFactory(), new MockUrlWatcher()); + var binder = new Binder(form.get(0), new WidgetFactory(), new MockLocation()); binder.compile(); binder.updateView(); assertEquals(scope.get('model').price, 'b'); @@ -210,7 +210,7 @@ BinderTest.prototype.testApplyTextBindings = function(){ var form = html('
x
'); var scope = new Scope({model:{a:123}}); form.data('scope', scope); - var binder = new Binder(form.get(0), null, new MockUrlWatcher()); + var binder = new Binder(form.get(0), null, new MockLocation()); binder.compile(); binder.updateView(); assertEquals('123', form.text()); @@ -287,7 +287,7 @@ BinderTest.prototype.testExistingAttrbindingIsAppended = function() { BinderTest.prototype.testAttributesAreEvaluated = function(){ var form = html(''); form.data('scope', new Scope({a:1, b:2})); - var binder = new Binder(form.get(0), null, new MockUrlWatcher()); + var binder = new Binder(form.get(0), null, new MockLocation()); binder.compile(); binder.updateView(); var a = form.find("a"); @@ -304,7 +304,7 @@ BinderTest.prototype.testInputsAreUpdated = function(){ '' + '' + ''); - var binder = new Binder(form.get(0), new WidgetFactory(), new MockUrlWatcher()); + var binder = new Binder(form.get(0), new WidgetFactory(), new MockLocation()); form.data('scope', new Scope({A:{text:"t1", textarea:"t2", radio:"r", checkbox:"c", select:"S"}})); binder.compile(); binder.updateView(); @@ -348,21 +348,27 @@ BinderTest.prototype.testButtonElementActionExecutesInScope = function(){ }; BinderTest.prototype.testParseEmptyAnchor = function(){ - var binder = new Binder(null, null, new MockUrlWatcher()); + var location = new MockLocation(); + var binder = new Binder(null, null, location); var anchor = binder.anchor; - binder.parseAnchor("a#x=1"); + location.url = "a#x=1"; + binder.parseAnchor(); assertEquals(1, binder.anchor.x); - binder.parseAnchor("a#"); + location.url = "a#"; + binder.parseAnchor(); assertTrue("old values did not get removed", !binder.anchor.x); assertTrue("anchor gor replaced", anchor === binder.anchor); assertEquals('undefined', typeof (anchor[""])); }; BinderTest.prototype.testParseAnchor = function(){ - var binder = new Binder(null, null, new MockUrlWatcher()); - binder.parseAnchor("a#x=1"); + var location = new MockLocation(); + var binder = new Binder(null, null, location); + location.url = "a#x=1"; + binder.parseAnchor(); assertEquals(binder.anchor.x, "1"); - binder.parseAnchor("a#a=b&c=%20&d"); + location.url = "a#a=b&c=%20&d"; + binder.parseAnchor(); assertEquals(binder.anchor.a, 'b'); assertEquals(binder.anchor.c, ' '); assertTrue(binder.anchor.d !== null); @@ -370,27 +376,27 @@ BinderTest.prototype.testParseAnchor = function(){ }; BinderTest.prototype.testWriteAnchor = function(){ - var binder = new Binder(null, null, new MockUrlWatcher()); - binder.urlWatcher.setUrl('a'); + var binder = new Binder(null, null, new MockLocation()); + binder.location.set('a'); binder.anchor.a = 'b'; binder.anchor.c = ' '; binder.anchor.d = true; binder.updateAnchor(); - assertEquals(binder.urlWatcher.getUrl(), "a#a=b&c=%20&d"); + assertEquals(binder.location.get(), "a#a=b&c=%20&d"); }; BinderTest.prototype.testWriteAnchorAsPartOfTheUpdateView = function(){ - var binder = new Binder(html("
")[0], null, new MockUrlWatcher()); - binder.urlWatcher.setUrl('a'); + var binder = new Binder(html("
")[0], null, new MockLocation()); + binder.location.set('a'); $(binder.doc).data('scope', new Scope()); binder.anchor.a = 'b'; binder.updateView(); - assertEquals(binder.urlWatcher.getUrl(), "a#a=b"); + assertEquals(binder.location.get(), "a#a=b"); }; BinderTest.prototype.testRepeaterUpdateBindings = function(){ var form = html('
'); - var binder = new Binder(form.get(0), null, new MockUrlWatcher()); + var binder = new Binder(form.get(0), null, new MockLocation()); var items = [{a:"A"}, {a:"B"}]; form.data('scope', new Scope({model:{items:items}})); binder.compile(); @@ -423,7 +429,7 @@ BinderTest.prototype.testRepeaterUpdateBindings = function(){ BinderTest.prototype.testRepeaterContentDoesNotBind = function(){ var form = html('
'); form.data('scope', new Scope({model:{items:[{a:"A"}]}})); - var binder = new Binder(form.get(0), null, new MockUrlWatcher()); + var binder = new Binder(form.get(0), null, new MockLocation()); binder.compile(); binder.updateView(); assertEquals('
    ' + @@ -449,7 +455,7 @@ BinderTest.prototype.testRepeaterInputContentDoesNotBind = function(){ var form = html('
    • ' + '
    '); - var binder = new Binder(form.get(0), null, new MockUrlWatcher()); + var binder = new Binder(form.get(0), null, new MockLocation()); var items = [{a:"A"}]; form.data('scope', new Scope({model:{items:items}})); @@ -493,7 +499,7 @@ BinderTest.prototype.testReplaceFileUploadWithSwf = function(){ var form = jQuery("body").append('
    '); form.data('scope', new Scope()); var factory = {}; - var binder = new Binder(form.get(0), factory, new MockUrlWatcher()); + var binder = new Binder(form.get(0), factory, new MockLocation()); factory.createController = function(node){ assertEquals(node.attr('type'), 'file'); return {updateModel:function(){}}; @@ -504,7 +510,7 @@ BinderTest.prototype.testReplaceFileUploadWithSwf = function(){ BinderTest.prototype.testRepeaterAdd = function(){ var doc = $('
    '); - var binder = new Binder(doc[0], new WidgetFactory(), new MockUrlWatcher()); + var binder = new Binder(doc[0], new WidgetFactory(), new MockLocation()); doc.data('scope', new Scope({items:[{x:'a'}, {x:'b'}], $binder:binder})); binder.compile(); binder.updateView(); @@ -521,7 +527,7 @@ BinderTest.prototype.testIfTextBindingThrowsErrorDecorateTheSpan = function(){ var doc = $('
    {{error.throw()}}
    '); var scope = new Scope(); doc.data('scope', scope); - var binder = new Binder(doc[0], new WidgetFactory(), new MockUrlWatcher()); + var binder = new Binder(doc[0], new WidgetFactory(), new MockLocation()); binder.compile(); scope.set('error.throw', function(){throw "ErrorMsg1";}); @@ -549,7 +555,7 @@ BinderTest.prototype.testIfAttrBindingThrowsErrorDecorateTheSpan = function(){ var doc = $('
    '); var scope = new Scope(); doc.data('scope', scope); - var binder = new Binder(doc[0], new WidgetFactory(), new MockUrlWatcher()); + var binder = new Binder(doc[0], new WidgetFactory(), new MockLocation()); binder.compile(); scope.set('error.throw', function(){throw "ErrorMsg";}); @@ -571,7 +577,7 @@ BinderTest.prototype.testNestedRepeater = function() { '
    '); var scope = new Scope(); doc.data('scope', scope); - var binder = new Binder(doc[0], new WidgetFactory(), new MockUrlWatcher()); + var binder = new Binder(doc[0], new WidgetFactory(), new MockLocation()); binder.compile(); scope.set('model', [{name:'a', item:['a1', 'a2']}, {name:'b', item:['b1', 'b2']}]); @@ -595,7 +601,7 @@ BinderTest.prototype.testRadioButtonGetsPrefixed = function () { var doc = html(''); var scope = new Scope(); doc.data('scope', scope); - var binder = new Binder(doc[0], new WidgetFactory(), new MockUrlWatcher()); + var binder = new Binder(doc[0], new WidgetFactory(), new MockLocation()); binder.compile(); scope.set('model', ['a1', 'a2']); @@ -612,7 +618,7 @@ BinderTest.prototype.testHideBindingExpression = function() { var doc = html('
    '); var scope = new Scope(); doc.data('scope', scope); - var binder = new Binder(doc[0], new WidgetFactory(), new MockUrlWatcher()); + var binder = new Binder(doc[0], new WidgetFactory(), new MockLocation()); binder.compile(); scope.set('hidden', 3); @@ -630,7 +636,7 @@ BinderTest.prototype.testHideBinding = function() { var doc = html('
    '); var scope = new Scope(); doc.data('scope', scope); - var binder = new Binder(doc[0], new WidgetFactory(), new MockUrlWatcher()); + var binder = new Binder(doc[0], new WidgetFactory(), new MockLocation()); binder.compile(); scope.set('hidden', 'true'); @@ -653,7 +659,7 @@ BinderTest.prototype.testShowBinding = function() { var doc = html('
    '); var scope = new Scope(); doc.data('scope', scope); - var binder = new Binder(doc[0], new WidgetFactory(), new MockUrlWatcher()); + var binder = new Binder(doc[0], new WidgetFactory(), new MockLocation()); binder.compile(); scope.set('show', 'true'); @@ -685,7 +691,7 @@ BinderTest.prototype.testBindClass = function() { var doc = html('
    '); var scope = new Scope(); doc.data('scope', scope); - var binder = new Binder(doc[0], new WidgetFactory(), new MockUrlWatcher()); + var binder = new Binder(doc[0], new WidgetFactory(), new MockLocation()); binder.compile(); scope.set('class', 'testClass'); @@ -714,7 +720,7 @@ BinderTest.prototype.testBindStyle = function() { var doc = html('
    '); var scope = new Scope(); doc.data('scope', scope); - var binder = new Binder(doc[0], new WidgetFactory(), new MockUrlWatcher()); + var binder = new Binder(doc[0], new WidgetFactory(), new MockLocation()); binder.compile(); scope.eval('style={color:"red"}'); @@ -797,7 +803,7 @@ BinderTest.prototype.testDissableAutoSubmit = function() { BinderTest.prototype.testSettingAnchorToNullOrUndefinedRemovesTheAnchorFromURL = function() { var c = compile(''); - c.binder.urlWatcher.setUrl("http://server/#a=1&b=2"); + c.binder.location.set("http://server/#a=1&b=2"); c.binder.parseAnchor(); assertEquals('1', c.binder.anchor.a); assertEquals('2', c.binder.anchor.b); @@ -805,7 +811,7 @@ BinderTest.prototype.testSettingAnchorToNullOrUndefinedRemovesTheAnchorFromURL = c.binder.anchor.a = null; c.binder.anchor.b = null; c.binder.updateAnchor(); - assertEquals('http://server/#', c.binder.urlWatcher.getUrl()); + assertEquals('http://server/#', c.binder.location.get()); }; BinderTest.prototype.testFillInOptionValueWhenMissing = function() { @@ -875,15 +881,24 @@ BinderTest.prototype.testItShouldCallListenersWhenAnchorChanges = function() { log += oldValue + "->" + newValue + ";"; }); assertEquals(0, c.scope.get("count")); - c.binder.onUrlChange("#counter=1"); + c.binder.location.url = "#counter=1"; + c.binder.onUrlChange(); assertEquals(1, c.scope.get("count")); - c.binder.onUrlChange("#counter=1"); + + c.binder.location.url = "#counter=1"; + c.binder.onUrlChange(); assertEquals(1, c.scope.get("count")); - c.binder.onUrlChange("#counter=2"); + + c.binder.location.url = "#counter=2"; + c.binder.onUrlChange(); assertEquals(2, c.scope.get("count")); - c.binder.onUrlChange("#counter=2"); + + c.binder.location.url = "#counter=2"; + c.binder.onUrlChange(); assertEquals(2, c.scope.get("count")); - c.binder.onUrlChange("#"); + + c.binder.location.url = "#"; + c.binder.onUrlChange(); assertEquals("undefined->1;1->2;2->undefined;", log); assertEquals(3, c.scope.get("count")); }; @@ -904,7 +919,7 @@ BinderTest.prototype.testParseQueryString = function(){ BinderTest.prototype.testSetBinderAnchorTriggersListeners = function(){ expectAsserts(2); var doc = html("
    ")[0]; - var binder = new Binder(doc, null, new MockUrlWatcher()); + var binder = new Binder(doc, null, new MockLocation()); var scope = new Scope({$binder:binder, $anchor:binder.anchor}); jQuery(doc).data('scope', scope); diff --git a/test/ControlBarTest.js b/test/ControlBarTest.js deleted file mode 100644 index c914c8ff..00000000 --- a/test/ControlBarTest.js +++ /dev/null @@ -1,2 +0,0 @@ -ControlBarTest = TestCase("ControlBarTest"); - diff --git a/test/ExternalApiTest.js b/test/ExternalApiTest.js deleted file mode 100644 index cc102ae0..00000000 --- a/test/ExternalApiTest.js +++ /dev/null @@ -1,15 +0,0 @@ -ExternalApiTest = TestCase("ExternalApiTest"); - -ExternalApiTest.prototype = { - testItShouldExposefactory:function(){ - var node = $('
    {{b=a+1}}
    ')[0]; - var scope = angular.compile(node); - scope.init(); - assertEquals(1, scope.get('a')); - assertEquals(2, scope.get('b')); - }, - - testItShouldRegisterAnchorListener: function (){ - - } -}; diff --git a/test/JsonTest.js b/test/JsonTest.js index cf49bec3..9b275248 100644 --- a/test/JsonTest.js +++ b/test/JsonTest.js @@ -67,3 +67,14 @@ JsonTest.prototype.testItShouldUTCDates = function() { assertEquals(date.getTime(), fromJson('"2009-10-09T01:02:03Z"').getTime()); }; + +JsonTest.prototype.testItShouldPreventRecursion = function () { + var obj = {a:'b'}; + obj.recursion = obj; + assertEquals('{"a":"b","recursion":RECURSION}', angular.toJson(obj)); +}; + +JsonTest.prototype.testItShouldSerializeSameObjectsMultipleTimes = function () { + var obj = {a:'b'}; + assertEquals('{"A":{"a":"b"},"B":{"a":"b"}}', angular.toJson({A:obj, B:obj})); +}; diff --git a/test/ScenarioSpec.js b/test/ScenarioSpec.js new file mode 100644 index 00000000..c3c29f02 --- /dev/null +++ b/test/ScenarioSpec.js @@ -0,0 +1,66 @@ +describe("ScenarioSpec: Compilation", function(){ + it("should compile dom node and return scope", function(){ + var node = $('
    {{b=a+1}}
    ')[0]; + var scope = angular.compile(node); + scope.init(); + expect(scope.get('a')).toEqual(1); + expect(scope.get('b')).toEqual(2); + }); + + it("should compile jQuery node and return scope", function(){ + var scope = angular.compile($('
    {{a=123}}
    ')).init(); + expect($(scope.element).text()).toEqual('123'); + }); + + it("should compile text node and return scope", function(){ + var scope = angular.compile('
    {{a=123}}
    ').init(); + expect($(scope.element).text()).toEqual('123'); + }); +}); + +describe("ScenarioSpec: Scope", function(){ + it("should have set, get, eval, init, updateView methods", function(){ + var scope = angular.compile('
    {{a}}
    ').init(); + expect(scope.set("a", 2)).toEqual(2); + expect(scope.get("a")).toEqual(2); + expect(scope.eval("a=3")).toEqual(3); + scope.updateView(); + expect($(scope.element).text()).toEqual('3'); + }); + + it("should have config", function(){ + expect(angular.compile('', {a:'b'}).config.a).toEqual('b'); + }); + + it("should have $ objects", function(){ + var scope = angular.compile('
    ', {a:"b"}); + expect(scope.get('$anchor')).toBeDefined(); + expect(scope.get('$updateView')).toBeDefined(); + expect(scope.get('$config')).toBeDefined(); + expect(scope.get('$config.a')).toEqual("b"); + expect(scope.get('$datastore')).toBeDefined(); + }); +}); + +describe("ScenarioSpec: configuration", function(){ + it("should take location object", function(){ + var url = "http://server/#book=moby"; + var onUrlChange; + var location = { + listen:function(fn){onUrlChange=fn;}, + set:function(u){url = u;}, + get:function(){return url;} + }; + var scope = angular.compile("
    {{$anchor}}
    ", {location:location}); + var $anchor = scope.get('$anchor'); + expect($anchor.book).toBeUndefined(); + expect(onUrlChange).toBeUndefined(); + scope.init(); + expect($anchor.book).toEqual('moby'); + expect(onUrlChange).toBeDefined(); + + url = "http://server/#book=none"; + onUrlChange(); + expect($anchor.book).toEqual('none'); + }); +}); diff --git a/test/ScopeTest.js b/test/ScopeTest.js index e1c5c8ce..b066f0cb 100644 --- a/test/ScopeTest.js +++ b/test/ScopeTest.js @@ -38,7 +38,7 @@ ScopeTest.prototype.testScopeFromPrototype = function(){ ScopeTest.prototype.testSetScopeGet = function(){ var scope = new Scope(); - scope.set('a', 987); + assertEquals(987, scope.set('a', 987)); assertEquals(scope.get('a'), 987); assertEquals(scope.eval('a'), 987); }; diff --git a/test/formsTest.js b/test/formsTest.js deleted file mode 100644 index ccade915..00000000 --- a/test/formsTest.js +++ /dev/null @@ -1,18 +0,0 @@ -nglrTest = TestCase('nglrTest'); - -nglrTest.prototype.testShiftBind = function(){ - expectAsserts(3); - shiftBind('this', function(target, arg) { - assertEquals(this, 'this'); - assertEquals(target, 'target'); - assertEquals(arg, 'arg'); - }).apply('target', ['arg']); -}; - -nglrTest.prototype.testBind = function(){ - expectAsserts(2); - bind('this', function(arg) { - assertEquals(this, 'this'); - assertEquals(arg, 'arg'); - }).apply('XXX', ['arg']); -}; diff --git a/test/testabilityPatch.js b/test/testabilityPatch.js index 78ffd380..8fac7598 100644 --- a/test/testabilityPatch.js +++ b/test/testabilityPatch.js @@ -35,13 +35,13 @@ function report(reportTest){ }); } -MockUrlWatcher = function() { +MockLocation = function() { this.url = "http://server"; }; -MockUrlWatcher.prototype.getUrl = function(){ +MockLocation.prototype.get = function(){ return this.url; }; -MockUrlWatcher.prototype.setUrl = function(url){ +MockLocation.prototype.set = function(url){ this.url = url; }; @@ -96,7 +96,7 @@ function decode64(base64){ return fromJson(Base64.decode(base64)); } -Angular.prototype.configureJQueryPlugins(); +configureJQueryPlugins(); function assertHidden(node) { var display = node.css('display'); -- cgit v1.2.3