From c9c176a53b1632ca2b1c6ed27382ab72ac21d45d Mon Sep 17 00:00:00 2001 From: Adam Abrons Date: Tue, 5 Jan 2010 16:36:58 -0800 Subject: angular.js --- LICENSE | 2 + README.md | 3 + Rakefile | 74 + angular.js | 128 + jsTestDriver.conf | 16 + lib/compiler-closure/COPYING | 202 ++ lib/compiler-closure/README | 193 ++ lib/compiler-closure/compiler.jar | Bin 0 -> 4237729 bytes lib/jquery/jquery-1.3.2.js | 4376 ++++++++++++++++++++++++++++++ lib/jquery/jquery-ui-1.7.1.custom.min.js | 77 + lib/jsl/jsl | Bin 0 -> 519636 bytes lib/jsl/jsl.default.conf | 128 + lib/jstestdriver/JsTestDriver.jar | Bin 0 -> 3065205 bytes lib/shrinksafe/js.jar | Bin 0 -> 756883 bytes lib/shrinksafe/shrinksafe.jar | Bin 0 -> 17685 bytes lib/swfobject/swfobject.js | 4 + lib/underscore/underscore.js | 609 +++++ lib/webtoolkit/webtoolkit.base64.js | 142 + src/API.js | 318 +++ src/Binder.js | 341 +++ src/ControlBar.js | 71 + src/DataStore.js | 332 +++ src/Filters.js | 290 ++ src/JSON.js | 92 + src/Loader.js | 389 +++ src/Model.js | 65 + src/Parser.js | 741 +++++ src/Scope.js | 198 ++ src/Server.js | 69 + src/Users.js | 36 + src/Validators.js | 80 + src/Widgets.js | 774 ++++++ src/Widgets.js.orig | 764 ++++++ src/XSitePost.js | 100 + src/angular-bootstrap.js | 100 + src/test/Runner.js | 160 ++ src/test/Steps.js | 57 + src/test/_namespace.js | 5 + test/ApiTest.js | 252 ++ test/Base64Test.js | 5 + test/BinderTest.js | 1001 +++++++ test/ConsoleTest.js | 13 + test/ControlBarTest.js | 2 + test/DataStoreTest.js | 617 +++++ test/EntityDeclarationTest.js | 46 + test/FileControllerTest.js | 98 + test/FiltersTest.js | 153 ++ test/JsonTest.js | 69 + test/LoaderTest.js | 70 + test/ModelTest.js | 84 + test/ParserTest.js | 462 ++++ test/ScopeTest.js | 144 + test/ServerTest.js | 42 + test/UsersTest.js | 26 + test/ValidatorsTest.js | 65 + test/WidgetsTest.js | 269 ++ test/XSitePostTest.js | 47 + test/formsTest.js | 22 + test/test/StepsTest.js | 7 + test/testabilityPatch.js | 129 + 60 files changed, 14559 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 Rakefile create mode 100644 angular.js create mode 100644 jsTestDriver.conf create mode 100644 lib/compiler-closure/COPYING create mode 100644 lib/compiler-closure/README create mode 100644 lib/compiler-closure/compiler.jar create mode 100644 lib/jquery/jquery-1.3.2.js create mode 100644 lib/jquery/jquery-ui-1.7.1.custom.min.js create mode 100755 lib/jsl/jsl create mode 100755 lib/jsl/jsl.default.conf create mode 100644 lib/jstestdriver/JsTestDriver.jar create mode 100644 lib/shrinksafe/js.jar create mode 100644 lib/shrinksafe/shrinksafe.jar create mode 100644 lib/swfobject/swfobject.js create mode 100644 lib/underscore/underscore.js create mode 100644 lib/webtoolkit/webtoolkit.base64.js create mode 100644 src/API.js create mode 100644 src/Binder.js create mode 100644 src/ControlBar.js create mode 100644 src/DataStore.js create mode 100644 src/Filters.js create mode 100644 src/JSON.js create mode 100644 src/Loader.js create mode 100644 src/Model.js create mode 100644 src/Parser.js create mode 100644 src/Scope.js create mode 100644 src/Server.js create mode 100644 src/Users.js create mode 100644 src/Validators.js create mode 100644 src/Widgets.js create mode 100644 src/Widgets.js.orig create mode 100644 src/XSitePost.js create mode 100644 src/angular-bootstrap.js create mode 100644 src/test/Runner.js create mode 100644 src/test/Steps.js create mode 100644 src/test/_namespace.js create mode 100644 test/ApiTest.js create mode 100644 test/Base64Test.js create mode 100644 test/BinderTest.js create mode 100644 test/ConsoleTest.js create mode 100644 test/ControlBarTest.js create mode 100644 test/DataStoreTest.js create mode 100644 test/EntityDeclarationTest.js create mode 100644 test/FileControllerTest.js create mode 100644 test/FiltersTest.js create mode 100644 test/JsonTest.js create mode 100644 test/LoaderTest.js create mode 100644 test/ModelTest.js create mode 100644 test/ParserTest.js create mode 100644 test/ScopeTest.js create mode 100644 test/ServerTest.js create mode 100644 test/UsersTest.js create mode 100644 test/ValidatorsTest.js create mode 100644 test/WidgetsTest.js create mode 100644 test/XSitePostTest.js create mode 100644 test/formsTest.js create mode 100644 test/test/StepsTest.js create mode 100644 test/testabilityPatch.js diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..5be7e4b9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,2 @@ +TODO +apache or MIT probably diff --git a/README.md b/README.md new file mode 100644 index 00000000..41fa9038 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +Angular +====== +TODO diff --git a/Rakefile b/Rakefile new file mode 100644 index 00000000..9fb7f173 --- /dev/null +++ b/Rakefile @@ -0,0 +1,74 @@ +include FileUtils + +desc 'Compile JavaScript' +task :compile do + compiled = %x(java -jar lib/shrinksafe/shrinksafe.jar \ + lib/webtoolkit/webtoolkit.base64.js \ + lib/underscore/underscore.js \ + src/Loader.js \ + src/API.js \ + src/Binder.js \ + src/ControlBar.js \ + src/DataStore.js \ + src/Filters.js \ + src/JSON.js \ + src/Model.js \ + src/Parser.js \ + src/Scope.js \ + src/Server.js \ + src/Users.js \ + src/Validators.js \ + src/Widgets.js \ + src/angular-bootstrap.js \ + ) + f = File.new("angular.js", 'w') + f.write(compiled) + f.close +end + +desc 'Compile JavaScript with Google Closure Compiler' +task :compileclosure do + %x(java -jar lib/compiler-closure/compiler.jar \ + --compilation_level ADVANCED_OPTIMIZATIONS \ + --js lib/webtoolkit/webtoolkit.base64.js \ + --js lib/underscore/underscore.js \ + --js src/Loader.js \ + --js src/API.js \ + --js src/Binder.js \ + --js src/ControlBar.js \ + --js src/DataStore.js \ + --js src/Filters.js \ + --js src/JSON.js \ + --js src/Model.js \ + --js src/Parser.js \ + --js src/Scope.js \ + --js src/Server.js \ + --js src/Users.js \ + --js src/Validators.js \ + --js src/Widgets.js \ + --js src/angular-bootstrap.js \ + --js_output_file angular.js) +end + +namespace :server do + desc 'Run JsTestDriver Server' + task :start do + sh %x(java -jar lib/jstestdriver/JsTestDriver.jar --browser open --port 9876) + end + + desc "Run JavaScript tests against the server" + task :test do + sh %(java -jar lib/jstestdriver/JsTestDriver.jar --tests all) + end +end + +desc "Run JavaScript tests" +task :test do + sh %(java -jar lib/jstestdriver/JsTestDriver.jar --tests all --browser open --port 9876) +end + +desc 'Lint' +task :lint do + out = %x(lib/jsl/jsl -conf lib/jsl/jsl.default.conf) + print out +end diff --git a/angular.js b/angular.js new file mode 100644 index 00000000..0c19175d --- /dev/null +++ b/angular.js @@ -0,0 +1,128 @@ +function r(){return function(a){return a}}function v(){return function(){}}function x(a){return function(b){this[a]=b}}function z(a){return function(){return a}}var A; +function aa(a){var b="",c,e,d,f,i,j,n=0;a=a;a=a.replace(/\r\n/g,"\n");e="";for(d=0;d127&&f<2048)e+=String.fromCharCode(f>>6|192);else{e+=String.fromCharCode(f>>12|224);e+=String.fromCharCode(f>>6&63|128)}e+=String.fromCharCode(f&63|128)}}for(a=e;n>2;c=(c&3)<<4|e>>4;i=(e&15)<<2|d>>6;j=d&63;if(isNaN(e))i=j=64;else if(isNaN(d))j=64;b=b+this.eb.charAt(f)+ +this.eb.charAt(c)+this.eb.charAt(i)+this.eb.charAt(j)}return b};(function(){var a=this,b=a.cb;function c(g){this.da=g}var e=typeof StopIteration!=="undefined"?StopIteration:"__break__",d=a.cb=function(g){return new c(g)};if(typeof exports!=="undefined")exports.cb=d;var f=Array.prototype.slice,i=Array.prototype.unshift,j=Object.prototype.toString,n=Object.prototype.hasOwnProperty,m=Object.prototype.propertyIsEnumerable;d.oe="0.5.1";d.a=function(g,h,k){try{if(g.forEach)g.forEach(h,k);else if(d.z(g)||d.dc(g))for(var l=0,p=g.length;l=l.ga&&(l={value:p,ga:s})});return l.value};d.min=function(g,h,k){if(!h&&d.z(g))return Math.min.apply(Math,g);var l={ga:Infinity};d.a(g,function(p,s,u){s=h?h.call(k,p,s,u):p;sp?1:0}),"value")};d.yf=function(g,h,k){k=k||d.ka;for(var l=0,p=g.length;l>1;k(g[s])=0})})};d.Pf=function(){for(var g=d.D(arguments),h=d.max(d.vb(g,"length")),k=new Array(h),l=0;l0?p-h:h-p)>=0)return l;l[s++]=p}};d.u=function(g,h){var k=d.Q(arguments,2);return function(){return g.apply(h||a,k.concat(d.D(arguments)))}};d.we=function(g){var h= +d.Q(arguments);if(h.length==0)h=d.nb(g);d.a(h,function(k){g[k]=d.u(g[k],g)});return g};d.lb=function(g,h){var k=d.Q(arguments,2);return setTimeout(function(){return g.apply(g,k)},h)};d.defer=function(g){return d.lb.apply(d,[g,1].concat(d.Q(arguments)))};d.Of=function(g,h){return function(){var k=[g].concat(d.D(arguments));return h.apply(h,k)}};d.Ie=function(){var g=d.D(arguments);return function(){for(var h=d.D(arguments),k=g.length-1;k>=0;k--)h=[g[k].apply(this,h)];return h[0]}};d.la=function(g){if(d.z(g))return d.Md(0, +g.length);var h=[];for(var k in g)n.call(g,k)&&h.push(k);return h};d.Ab=function(g){return d.map(g,d.ka)};d.nb=function(g){return d.select(d.la(g),function(h){return d.P(g[h])}).sort()};d.extend=function(g,h){for(var k in h)g[k]=h[k];return g};d.fa=function(g){if(d.z(g))return g.slice(0);return d.extend({},g)};d.isEqual=function(g,h){if(g===h)return true;var k=typeof g;if(k!=typeof h)return false;if(g==h)return true;if(!g&&h||g&&!h)return false;if(g.isEqual)return g.isEqual(h);if(d.pd(g)&&d.pd(h))return g.getTime()=== +h.getTime();if(d.fc(g)&&d.fc(h))return true;if(d.qd(g)&&d.qd(h))return g.source===h.source&&g.global===h.global&&g.ignoreCase===h.ignoreCase&&g.multiline===h.multiline;if(k!=="object")return false;if(g.length&&g.length!==h.length)return false;k=d.la(g);var l=d.la(h);if(k.length!=l.length)return false;for(var p in g)if(!d.isEqual(g[p],h[p]))return false;return true};d.bf=function(g){return d.la(g).length==0};d.af=function(g){return!!(g&&g.nodeType==1)};d.dc=function(g){return g&&"0"<=g.length&&g.length<= +"9"&&!d.z(g)&&!m.call(g,"length")};d.fc=function(g){return"0"<=g&&g<="9"&&isNaN(g)};d.cf=function(g){return g===null};d.K=function(g){return typeof g=="undefined"};for(var o=["Array","Date","Function","Number","RegExp","String"],q=0,w=o.length;q)[^\t]*)'/g,"$1\r").replace(/\t=(.*?)%>/g,"',$1,'").split("\t").join("');").split("%>").join("p.push('").split("\r").join("\\'")+"');}return p.join('');");return h?g(h):g};d.forEach=d.a;d.Se=d.We=d.reduce;d.Te=d.reduceRight;d.filter=d.select;d.every=d.all;d.some=d.Uc;d.ac=d.jd;d.Cf=d.Q;d.hf=d.nb;function y(g,h){return h?d(g).Ob():g}d.a(d.nb(d),function(g){var h= +d[g];c.prototype[g]=function(){i.call(arguments,this.da);return y(h.apply(d,arguments),this.db)}});d.a(["pop","push","reverse","shift","sort","splice","unshift"],function(g){var h=Array.prototype[g];c.prototype[g]=function(){h.apply(this.da,arguments);return y(this.da,this.db)}});d.a(["concat","join","slice"],function(g){var h=Array.prototype[g];c.prototype[g]=function(){return y(h.apply(this.da,arguments),this.db)}});c.prototype.Ob=function(){this.db=true;return this};c.prototype.value=function(){return this.da}})();if(typeof document.getAttribute=="undefined")document.getAttribute=v();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};if(_.K(window.jf))nglr={};if(_.K(window.Lb))angular={};if(_.K(angular.k))angular.k={};if(_.K(angular.filter))angular.filter={}; +if(_.K(window.console))window.console={log:v(),error:v()};if(_.K(nglr.alert))nglr.alert=function(){console.log(arguments);window.alert.apply(window,arguments)};nglr.Qb=function(a,b){var c=document.createElement("div");c.className=a;for(var e=a="",d=0;d/g,">")};nglr.hd=function(a){if(!a||!a.replace)return a;return a.replace(//g,">").replace(/\"/g,""")}; +nglr.u=function(a,b){if(!a)throw"Missing this";if(!_.P(b))throw"Missing function";return function(){return b.apply(a,arguments)}};nglr.Qd=function(a,b){return function(){for(var c=[this],e=0;e=0&&a.splice(c,1);return b},find:function(a,b,c){if(b){var e=angular.$.compile(b);_.Ub(a,function(d){if(e(d)){c=d;return true}});return c}},Pe:function(a,b){return angular.La.find(a,function(c){return c.R==b},null)},filter:function(a,b){var c= +[];c.Zc=function(m){for(var o=0;o-1;case "object":for(var q in m)if(q.charAt(0)!=="$"&&d(m[q],o))return true;return false;case "array":for(q=0;q=0&&a.splice(e,1);a.unshift((c?"-":"+")+b);return a},mf:function(a,b,c,e){c=c||"ng-ascend";e=e||"ng-descend";a=a[0]||"";var d=true;if(a.charAt(0)=="-"){a=a.substring(1);d=false}else if(a.charAt(0)=="+")a=a.substring(1);return a==b?d?c: +e:""},Ea:function(a,b,c){var e=a[b];if(!e){e={};a[b]=e}nglr.Ea(c,e);return a}}; +angular.U={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")+'"'},tc:function(a){a=angular.U.quote(a);for(var b=[],c=0;c-1;){c1||nglr.g.ea(a[0])!==null};nglr.g.ea=function(a){return(a=a.replace(/\n/gm," ").match(/^\{\{(.*)\}\}$/))?a[1]:null}; +function ga(a,b){var c={};b.replace(/(?:^|&)([^&=]*)=?([^&]*)/g,function(e,d,f){if(d)c[decodeURIComponent(d)]=decodeURIComponent(f)});return c}function D(a,b){b=b||window.location.href;var c=b.indexOf("#");if(!(c<0)){b=b.substring(c+1);b=ga(a,b);jQuery.a(a.anchor,function(e){delete a.anchor[e]});jQuery.a(b,function(e,d){a.anchor[e]=d})}}nglr.g.prototype.Ld=function(a){console.log("URL change detected",a);D(this,a);this.c()}; +function ha(a){var b=window.location.href,c=b.indexOf("#");if(c>-1)b=b.substring(0,c);b+="#";c="";for(var e in a.anchor){var d=a.anchor[e];if(typeof d==="undefined"||d===null)delete a.anchor[e];else{b+=c+encodeURIComponent(e);if(d!==true)b+="="+encodeURIComponent(d);c="&"}}a=b;e=window.location.href;e.match(/#/)||(e+="#");if(e!=a)window.location.href=a;self.Oe=a;return b} +nglr.g.prototype.c=function(){(new Date).getTime();var a=jQuery(this.F).scope();E(a,"$invalidWidgets",[]);a.c();(new Date).getTime();ha(this);_.a(this.Cc,function(b){b()})};function fa(a){jQuery("[ng-init]",a.F).a(function(){var b=jQuery(this),c=b.scope();try{c.eval(b.e("ng-init"))}catch(e){nglr.alert("EVAL ERROR:\n"+b.e("ng-init")+"\n"+nglr.m(e,true))}})} +nglr.g.prototype.ha=function(a){jQuery("[ng-entity]",this.F).e("ng-watch",function(){try{var b=jQuery(this);return a.ha(b.e("ng-entity"))+(b.e("ng-watch")||"")}catch(c){nglr.alert(c)}})}; +nglr.g.prototype.compile=function(){var a=jQuery(this.F),b=this;if(this.w.Xc){var c=jQuery(":submit",this.F).rc("[ng-action]");c.e("ng-action","$save()");c.rc(":disabled").rc("ng-bind-attr").e("ng-bind-attr",'{disabled:"{{$invalidWidgets}}"}')}F(this,this.F)(this.F,a.scope(),"");jQuery("a[ng-action]",this.F).vd("click",function(){var e=jQuery(this);try{e.scope().eval(e.e("ng-action"));e.vc("ng-error");e.wc("ng-exception")}catch(d){e.fb("ng-exception");e.e("ng-error",nglr.m(d,true))}b.c();return false})}; +function ia(a,b,c,e){a=c.concat();c=a.pop();var d=nglr.g.Ha(b.nodeValue);if(d.length>1||nglr.g.ea(d[0])){var f=b.parentNode;if(nglr.ec(f)){f.setAttribute("ng-bind-template",b.nodeValue);e.push({path:a,b:function(o){return new nglr.S(o,o.getAttribute("ng-bind-template"))}})}else for(var i=0;i2&&b.setAttribute("ng-bind-attr",d)}b.getAttribute|| +console.log(b);var o=b.getAttribute("ng-repeat");if(o){b.removeAttribute("ng-repeat");var q=F(a,b);d=document.createComment("ng-repeat: "+o);f=b.parentNode;f.insertBefore(d,b);f.removeChild(b);var w=function(t,y,g){var h=jQuery(b).fa();h.kb("display","");h.e("ng-repeat-index",""+g);h.data("scope",t);q(h[0],t,y+g+":");return h};e.push({path:c,b:function(t,y,g){return new nglr.Xa(jQuery(t),o,w,g)}})}else{b.getAttribute("ng-eval")&&e.push({path:c,b:a.Dd});b.getAttribute("ng-bind")&&e.push({path:c,b:a.qc}); +b.getAttribute("ng-bind-attr")&&e.push({path:c,b:a.zd});b.getAttribute("ng-hide")&&e.push({path:c,b:a.Ed});b.getAttribute("ng-show")&&e.push({path:c,b:a.Fd});b.getAttribute("ng-class")&&e.push({path:c,b:a.Ad});b.getAttribute("ng-class-odd")&&e.push({path:c,b:a.Cd});b.getAttribute("ng-class-even")&&e.push({path:c,b:a.Bd});b.getAttribute("ng-style")&&e.push({path:c,b:a.Gd});b.getAttribute("ng-watch")&&e.push({path:c,b:a.Hd});d=b.nodeName;if(d=="INPUT"||d=="TEXTAREA"||d=="SELECT"||d=="BUTTON")e.push({path:c, +b:function(t,y,g){t.name=g+t.name.split(":").pop();return ja(a.$d,jQuery(t),y)}});if(d=="OPTION")if(!jQuery("')};nglr.T.prototype.j=function(a){var b=this.view.find("input").e("checked")?this.value:null;if(this.d===b)return false;else{E(a,this.xc,b);return true}}; +nglr.T.prototype.c=function(a){if((a=a.i(this.xc))&&this.value!==a){this.value=a;this.view.find("a").e("href",this.value.url).text(this.value.text);this.view.find("span").text(angular.filter.gb(this.value.size))}this.view.find("input").e("checked",!!a)};nglr.ba=x("view");nglr.ba.prototype.j=z(true);nglr.ba.prototype.c=v();nglr.ba.od=new nglr.ba;nglr.Na=x("view");nglr.Na.prototype.j=z(true);nglr.Na.prototype.c=v(); +nglr.bb=function(a,b){this.view=a;this.exp=b;this.k=a.getAttribute("ng-validate");this.Nd=typeof a.attributes["ng-required"]!="undefined";this.hc=null;this.d=undefined;this.H=a.value;a.getAttribute("ng-widget")==="datepicker"&&jQuery(a).Ne()};nglr.bb.prototype.j=function(a){var b=this.view.value;if(this.d===b)return false;else{$(a,this.exp,b);this.d=b;return true}}; +nglr.bb.prototype.c=function(a){var b=this.view,c=a.i(this.exp);if(typeof c==="undefined"){c=this.H;$(a,this.exp,c)}c=c?c:"";if(this.d!=c)this.d=b.value=c;var e=false;b.removeAttribute("ng-error");if(this.Nd)e=!(c&&c.length>0);var d=e?"Required Value":null;if(!e&&this.k&&c){e=this.k;c=c;d=nglr.h.ya[e];if(!d){d=(new nglr.q(e)).k();nglr.h.ya[e]=d}e={scope:a};d=d(e)(e,c);e=!!d}if(this.hc!==d){this.hc=e;if(d!==null){b.setAttribute("ng-error",d);a.I.he.push(this)}jQuery(b).zb("ng-validation-error",e)}}; +nglr.Oa=function(a,b){this.view=a;this.exp=b;this.d=undefined;this.H=a.checked?a.value:""};nglr.Oa.prototype.j=function(a){var b=this.view;b=b.checked?b.value:"";if(this.d===b)return false;else{$(a,this.exp,b);this.d=b;return true}};nglr.Oa.prototype.c=function(a){var b=this.view,c=a.eval(this.exp);if(typeof c==="undefined"){c=this.H;$(a,this.exp,c)}b.checked=b.value==""+c};nglr.Ya=function(a,b){this.view=a;this.exp=b;this.d=undefined;this.H=a.value}; +nglr.Ya.prototype.j=function(a){if(this.view.selectedIndex<0)$(a,this.exp,null);else{var b=this.view.value;if(this.d===b)return false;else{$(a,this.exp,b);this.d=b;return true}}};nglr.Ya.prototype.c=function(a){var b=this.view,c=a.i(this.exp);if(typeof c==="undefined"){c=this.H;$(a,this.exp,c)}if(c!==this.d){b.value=c?c:"";this.d=c}};nglr.ua=function(a,b){this.view=a;this.exp=b;this.d=undefined;this.H=this.selected()}; +nglr.ua.prototype.selected=function(){for(var a=[],b=this.view.options,c=0;ce;--b)this.xa.pop().element.removeNode();if(j&&j.element[0].nodeName==="OPTION")if(e=jQuery(j.element[0].parentNode).data("controller")){e.d=undefined;e.c(a)}})};nglr.A=x("F");nglr.A.Fb="mouseleave mouseout click dblclick keypress keyup";nglr.A.prototype.u=function(){this.F.find(".ng-validation-error,.ng-exception").vd("mouseover",nglr.A.Kd)}; +nglr.A.Kd=function(){nglr.A.ub();var a=jQuery(this);a.u(nglr.A.Fb,nglr.A.ub);var b=a.position(),c=document.documentElement,e=(self.innerWidth||c&&c.clientWidth||document.body.clientWidth)-b.left;c=a.Ue("ng-exception")?"EXCEPTION:":"Validation error...";a=a.e("ng-error");e=e>375?"left":"right";c=jQuery("
"+c+"
"+a+"
");jQuery("body").append(c);if(e==="left")a=b.left+this.offsetWidth+ +11;else{a=b.left-315;c.find(".ng-arrow-right").kb({left:301})}c.kb({left:a+"px",top:b.top-3+"px"});return true};nglr.A.ub=function(){jQuery("#ng-callout").Gf(nglr.A.Fb,nglr.A.ub).remove();return true};nglr.$a=function(a){this.lc=a.append(nglr.$a.Ic).find("#ng-loading");this.Ia=0};nglr.$a.Ic='
loading....
';function Da(a){a.Ia===0&&a.lc.wb();a.Ia++};(function(a){for(var b=/(.*)\/angular-(.*).js(#(.*))?/,c=document.getElementsByTagName("script"),e={Xc:true,Wc:true,Nb:false},d=0;d<\/script>')}if(e.Nb){m("/javascripts/webtoolkit.base64.js");m("/javascripts/swfobject.js");m("/javascripts/jQuery/jquery-1.3.2.js");m("/javascripts/jQuery/jquery-ui-1.7.1.custom.min.js");m("/javascripts/underscore/underscore.js");m("/javascripts/nglr/Loader.js");m("/javascripts/nglr/API.js");m("/javascripts/nglr/Binder.js");m("/javascripts/nglr/ControlBar.js");m("/javascripts/nglr/DataStore.js");m("/javascripts/nglr/Filters.js");m("/javascripts/nglr/JSON.js");m("/javascripts/nglr/Model.js"); +m("/javascripts/nglr/Parser.js");m("/javascripts/nglr/Scope.js");m("/javascripts/nglr/Server.js");m("/javascripts/nglr/Users.js");m("/javascripts/nglr/Validators.js");m("/javascripts/nglr/Widgets.js")}else{m("/ajax/libs/swfobject/2.2/swfobject.js","http://ajax.googleapis.com");m("/ajax/libs/jquery/1.3.2/jquery.min.js","http://ajax.googleapis.com");m("/ajax/libs/jqueryui/1.7.2/jquery-ui.min.js","http://ajax.googleapis.com")}window.onload=function(){window.Lb.nd=function(h,k){var l=_.fa(e||{});_.extend(l, +k);(new nglr.Va(h,jQuery("head"),l)).load()};var o=window.document;if(e.Yc){o=null;for(var q=e.Yc.split("|"),w=0;w1)if(!window.document.getElementById(t[1]))continue;o=window.document.getElementById(y)}}e.Wc&&o&&window.Lb.nd(o);if(typeof a==="function")try{a.apply(this,arguments)}catch(g){}}})(window.onload); diff --git a/jsTestDriver.conf b/jsTestDriver.conf new file mode 100644 index 00000000..77a5f0bf --- /dev/null +++ b/jsTestDriver.conf @@ -0,0 +1,16 @@ +server: http://localhost:9876 + +load: + - lib/swfobject/swfobject.js + - lib/webtoolkit/webtoolkit.base64.js + - lib/jquery/jquery-1.3.2.js + - lib/jquery/jquery-ui-1.7.1.custom.min.js + - lib/underscore/underscore.js + - src/Loader.js + - src/*.js + - src/test/_namespace.js + - src/test/*.js + - test/testabilityPatch.js + - test/test/*.js + - test/*.js + \ No newline at end of file diff --git a/lib/compiler-closure/COPYING b/lib/compiler-closure/COPYING new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/lib/compiler-closure/COPYING @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/lib/compiler-closure/README b/lib/compiler-closure/README new file mode 100644 index 00000000..af4e6106 --- /dev/null +++ b/lib/compiler-closure/README @@ -0,0 +1,193 @@ +/* + * Copyright 2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// +// Contents +// + +The Closure Compiler performs checking, instrumentation, and +optimizations on JavaScript code. The purpose of this README is to +explain how to build and run the Closure Compiler. + +The Closure Compiler requires Java 6 or higher. +http://www.java.com/ + + +// +// Building The Closure Compiler +// + +There are three ways to get a Closure Compiler executable. + +1) Use one we built for you. + +Pre-built Closure binaries can be found at +http://code.google.com/p/closure-compiler/downloads/list + + +2) Check out the source and build it with Apache Ant. + +First, check out the full source tree of the Closure Compiler. There +are instructions on how to do this at the project site. +http://code.google.com/p/closure-compiler/source/checkout + +Apache Ant is a cross-platform build tool. +http://ant.apache.org/ + +At the root of the source tree, there is an Ant file named +build.xml. To use it, navigate to the same directory and type the +command + +ant jar + +This will produce a jar file called "build/compiler.jar". + + +3) Check out the source and build it with Eclipse. + +Eclipse is a cross-platform IDE. +http://www.eclipse.org/ + +Under Eclipse's File menu, click "New > Project ..." and create a +"Java Project." You will see an options screen. Give the project a +name, select "Create project from existing source," and choose the +root of the checked-out source tree as the existing directory. Verify +that you are using JRE version 6 or higher. + +Eclipse can use the build.xml file to discover rules. When you +navigate to the build.xml file, you will see all the build rules in +the "Outline" pane. Run the "jar" rule to build the compiler in +build/compiler.jar. + + +// +// Running The Closure Compiler +// + +Once you have the jar binary, running the Closure Compiler is straightforward. + +On the command line, type + +java -jar compiler.jar + +This starts the compiler in interactive mode. Type + +var x = 17 + 25; + +then hit "Enter", then hit "Ctrl-Z" (on Windows) or "Ctrl-D" (on Mac or Linux) +and "Enter" again. The Compiler will respond: + +var x=42; + +The Closure Compiler has many options for reading input from a file, +writing output to a file, checking your code, and running +optimizations. To learn more, type + +java -jar compiler.jar --help + +You can read more detailed documentation about the many flags at +http://code.google.com/closure/compiler/docs/gettingstarted_app.html + + +// +// Compiling Multiple Scripts +// + +If you have multiple scripts, you should compile them all together with +one compile command. + +java -jar compiler.jar --js=in1.js --js=in2.js ... --js_output_file=out.js + +The Closure Compiler will concatenate the files in the order they're +passed at the command line. + +If you need to compile many, many scripts together, you may start to +run into problems with managing dependencies between scripts. You +should check out the Closure Library. It contains functions for +enforcing dependencies between scripts, and a tool called calcdeps.py +that knows how to give scripts to the Closure Compiler in the right +order. + +http://code.google.com/p/closure-library/ + +// +// Licensing +// + +Unless otherwise stated, all source files are licensed under +the Apache License, Version 2.0. + + +----- +Code under: +src/com/google/javascript/rhino +test/com/google/javascript/rhino + +URL: http://www.mozilla.org/rhino +Version: 1.5R3, with heavy modifications +License: Netscape Public License and MPL / GPL dual license + +Description: A partial copy of Mozilla Rhino. Mozilla Rhino is an +implementation of JavaScript for the JVM. The JavaScript parser and +the parse tree data structures were extracted and modified +significantly for use by Google's JavaScript compiler. + +Local Modifications: The packages have been renamespaced. All code not +relavant to parsing has been removed. A JSDoc parser and static typing +system have been added. + + +----- +Code in: +lib/libtrunk_rhino_parser_jarjared.jar + +URL: http://www.mozilla.org/rhino +Version: Trunk +License: Netscape Public License and MPL / GPL dual license + +Description: Mozilla Rhino is an implementation of JavaScript for the JVM. + +Local Modifications: None. We've used JarJar to renamespace the code +post-compilation. See: +http://code.google.com/p/jarjar/ + + +----- +Code in: +lib/google_common.jar + +URL: http://code.google.com/p/guava-libraries/ +Version: Trunk +License: Apache License 2.0 + +Description: Google's core Java libraries. + +Local Modifications: None. + + +---- +Code in: +lib/junit.jar + +URL: http://sourceforge.net/projects/junit/ +Version: 3.8.1 +License: Common Public License 1.0 + +Description: A framework for writing and running automated tests in Java. + +Local Modifications: None. + + diff --git a/lib/compiler-closure/compiler.jar b/lib/compiler-closure/compiler.jar new file mode 100644 index 00000000..da053a7d Binary files /dev/null and b/lib/compiler-closure/compiler.jar differ diff --git a/lib/jquery/jquery-1.3.2.js b/lib/jquery/jquery-1.3.2.js new file mode 100644 index 00000000..d7093948 --- /dev/null +++ b/lib/jquery/jquery-1.3.2.js @@ -0,0 +1,4376 @@ +/*! + * jQuery JavaScript Library v1.3.2 + * http://jquery.com/ + * + * Copyright (c) 2009 John Resig + * Dual licensed under the MIT and GPL licenses. + * http://docs.jquery.com/License + * + * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009) + * Revision: 6246 + */ +(function(){ + +var + // Will speed up references to window, and allows munging its name. + window = this, + // Will speed up references to undefined, and allows munging its name. + undefined, + // Map over jQuery in case of overwrite + _jQuery = window.jQuery, + // Map over the $ in case of overwrite + _$ = window.$, + + jQuery = window.jQuery = window.$ = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context ); + }, + + // A simple way to check for HTML strings or ID strings + // (both of which we optimize for) + quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/, + // Is it a simple selector + isSimple = /^.[^:#\[\.,]*$/; + +jQuery.fn = jQuery.prototype = { + init: function( selector, context ) { + // Make sure that a selection was provided + selector = selector || document; + + // Handle $(DOMElement) + if ( selector.nodeType ) { + this[0] = selector; + this.length = 1; + this.context = selector; + return this; + } + // Handle HTML strings + if ( typeof selector === "string" ) { + // Are we dealing with HTML string or an ID? + var match = quickExpr.exec( selector ); + + // Verify a match, and that no context was specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) + selector = jQuery.clean( [ match[1] ], context ); + + // HANDLE: $("#id") + else { + var elem = document.getElementById( match[3] ); + + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem && elem.id != match[3] ) + return jQuery().find( selector ); + + // Otherwise, we inject the element directly into the jQuery object + var ret = jQuery( elem || [] ); + ret.context = document; + ret.selector = selector; + return ret; + } + + // HANDLE: $(expr, [context]) + // (which is just equivalent to: $(content).find(expr) + } else + return jQuery( context ).find( selector ); + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) + return jQuery( document ).ready( selector ); + + // Make sure that old selector state is passed along + if ( selector.selector && selector.context ) { + this.selector = selector.selector; + this.context = selector.context; + } + + return this.setArray(jQuery.isArray( selector ) ? + selector : + jQuery.makeArray(selector)); + }, + + // Start with an empty selector + selector: "", + + // The current version of jQuery being used + jquery: "1.3.2", + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num === undefined ? + + // Return a 'clean' array + Array.prototype.slice.call( this ) : + + // Return just the object + this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems, name, selector ) { + // Build a new jQuery matched element set + var ret = jQuery( elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + ret.context = this.context; + + if ( name === "find" ) + ret.selector = this.selector + (this.selector ? " " : "") + selector; + else if ( name ) + ret.selector = this.selector + "." + name + "(" + selector + ")"; + + // Return the newly-formed element set + return ret; + }, + + // Force the current matched set of elements to become + // the specified array of elements (destroying the stack in the process) + // You should use pushStack() in order to do this, but maintain the stack + setArray: function( elems ) { + // Resetting the length to 0, then using the native Array push + // is a super-fast way to populate an object with array-like properties + this.length = 0; + Array.prototype.push.apply( this, elems ); + + return this; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem && elem.jquery ? elem[0] : elem + , this ); + }, + + attr: function( name, value, type ) { + var options = name; + + // Look for the case where we're accessing a style value + if ( typeof name === "string" ) + if ( value === undefined ) + return this[0] && jQuery[ type || "attr" ]( this[0], name ); + + else { + options = {}; + options[ name ] = value; + } + + // Check to see if we're setting style values + return this.each(function(i){ + // Set all the styles + for ( name in options ) + jQuery.attr( + type ? + this.style : + this, + name, jQuery.prop( this, options[ name ], type, i, name ) + ); + }); + }, + + css: function( key, value ) { + // ignore negative width and height values + if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 ) + value = undefined; + return this.attr( key, value, "curCSS" ); + }, + + text: function( text ) { + if ( typeof text !== "object" && text != null ) + return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) ); + + var ret = ""; + + jQuery.each( text || this, function(){ + jQuery.each( this.childNodes, function(){ + if ( this.nodeType != 8 ) + ret += this.nodeType != 1 ? + this.nodeValue : + jQuery.fn.text( [ this ] ); + }); + }); + + return ret; + }, + + wrapAll: function( html ) { + if ( this[0] ) { + // The elements to wrap the target around + var wrap = jQuery( html, this[0].ownerDocument ).clone(); + + if ( this[0].parentNode ) + wrap.insertBefore( this[0] ); + + wrap.map(function(){ + var elem = this; + + while ( elem.firstChild ) + elem = elem.firstChild; + + return elem; + }).append(this); + } + + return this; + }, + + wrapInner: function( html ) { + return this.each(function(){ + jQuery( this ).contents().wrapAll( html ); + }); + }, + + wrap: function( html ) { + return this.each(function(){ + jQuery( this ).wrapAll( html ); + }); + }, + + append: function() { + return this.domManip(arguments, true, function(elem){ + if (this.nodeType == 1) + this.appendChild( elem ); + }); + }, + + prepend: function() { + return this.domManip(arguments, true, function(elem){ + if (this.nodeType == 1) + this.insertBefore( elem, this.firstChild ); + }); + }, + + before: function() { + return this.domManip(arguments, false, function(elem){ + this.parentNode.insertBefore( elem, this ); + }); + }, + + after: function() { + return this.domManip(arguments, false, function(elem){ + this.parentNode.insertBefore( elem, this.nextSibling ); + }); + }, + + end: function() { + return this.prevObject || jQuery( [] ); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: [].push, + sort: [].sort, + splice: [].splice, + + find: function( selector ) { + if ( this.length === 1 ) { + var ret = this.pushStack( [], "find", selector ); + ret.length = 0; + jQuery.find( selector, this[0], ret ); + return ret; + } else { + return this.pushStack( jQuery.unique(jQuery.map(this, function(elem){ + return jQuery.find( selector, elem ); + })), "find", selector ); + } + }, + + clone: function( events ) { + // Do the clone + var ret = this.map(function(){ + if ( !jQuery.support.noCloneEvent && !jQuery.isXMLDoc(this) ) { + // IE copies events bound via attachEvent when + // using cloneNode. Calling detachEvent on the + // clone will also remove the events from the orignal + // In order to get around this, we use innerHTML. + // Unfortunately, this means some modifications to + // attributes in IE that are actually only stored + // as properties will not be copied (such as the + // the name attribute on an input). + var html = this.outerHTML; + if ( !html ) { + var div = this.ownerDocument.createElement("div"); + div.appendChild( this.cloneNode(true) ); + html = div.innerHTML; + } + + return jQuery.clean([html.replace(/ jQuery\d+="(?:\d+|null)"/g, "").replace(/^\s*/, "")])[0]; + } else + return this.cloneNode(true); + }); + + // Copy the events from the original to the clone + if ( events === true ) { + var orig = this.find("*").andSelf(), i = 0; + + ret.find("*").andSelf().each(function(){ + if ( this.nodeName !== orig[i].nodeName ) + return; + + var events = jQuery.data( orig[i], "events" ); + + for ( var type in events ) { + for ( var handler in events[ type ] ) { + jQuery.event.add( this, type, events[ type ][ handler ], events[ type ][ handler ].data ); + } + } + + i++; + }); + } + + // Return the cloned set + return ret; + }, + + filter: function( selector ) { + return this.pushStack( + jQuery.isFunction( selector ) && + jQuery.grep(this, function(elem, i){ + return selector.call( elem, i ); + }) || + + jQuery.multiFilter( selector, jQuery.grep(this, function(elem){ + return elem.nodeType === 1; + }) ), "filter", selector ); + }, + + closest: function( selector ) { + var pos = jQuery.expr.match.POS.test( selector ) ? jQuery(selector) : null, + closer = 0; + + return this.map(function(){ + var cur = this; + while ( cur && cur.ownerDocument ) { + if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selector) ) { + jQuery.data(cur, "closest", closer); + return cur; + } + cur = cur.parentNode; + closer++; + } + }); + }, + + not: function( selector ) { + if ( typeof selector === "string" ) + // test special case where just one selector is passed in + if ( isSimple.test( selector ) ) + return this.pushStack( jQuery.multiFilter( selector, this, true ), "not", selector ); + else + selector = jQuery.multiFilter( selector, this ); + + var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType; + return this.filter(function() { + return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector; + }); + }, + + add: function( selector ) { + return this.pushStack( jQuery.unique( jQuery.merge( + this.get(), + typeof selector === "string" ? + jQuery( selector ) : + jQuery.makeArray( selector ) + ))); + }, + + is: function( selector ) { + return !!selector && jQuery.multiFilter( selector, this ).length > 0; + }, + + hasClass: function( selector ) { + return !!selector && this.is( "." + selector ); + }, + + val: function( value ) { + if ( value === undefined ) { + var elem = this[0]; + + if ( elem ) { + if( jQuery.nodeName( elem, 'option' ) ) + return (elem.attributes.value || {}).specified ? elem.value : elem.text; + + // We need to handle select boxes special + if ( jQuery.nodeName( elem, "select" ) ) { + var index = elem.selectedIndex, + values = [], + options = elem.options, + one = elem.type == "select-one"; + + // Nothing was selected + if ( index < 0 ) + return null; + + // Loop through all the selected options + for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { + var option = options[ i ]; + + if ( option.selected ) { + // Get the specifc value for the option + value = jQuery(option).val(); + + // We don't need an array for one selects + if ( one ) + return value; + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + } + + // Everything else, we just grab the value + return (elem.value || "").replace(/\r/g, ""); + + } + + return undefined; + } + + if ( typeof value === "number" ) + value += ''; + + return this.each(function(){ + if ( this.nodeType != 1 ) + return; + + if ( jQuery.isArray(value) && /radio|checkbox/.test( this.type ) ) + this.checked = (jQuery.inArray(this.value, value) >= 0 || + jQuery.inArray(this.name, value) >= 0); + + else if ( jQuery.nodeName( this, "select" ) ) { + var values = jQuery.makeArray(value); + + jQuery( "option", this ).each(function(){ + this.selected = (jQuery.inArray( this.value, values ) >= 0 || + jQuery.inArray( this.text, values ) >= 0); + }); + + if ( !values.length ) + this.selectedIndex = -1; + + } else + this.value = value; + }); + }, + + html: function( value ) { + return value === undefined ? + (this[0] ? + this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g, "") : + null) : + this.empty().append( value ); + }, + + replaceWith: function( value ) { + return this.after( value ).remove(); + }, + + eq: function( i ) { + return this.slice( i, +i + 1 ); + }, + + slice: function() { + return this.pushStack( Array.prototype.slice.apply( this, arguments ), + "slice", Array.prototype.slice.call(arguments).join(",") ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function(elem, i){ + return callback.call( elem, i, elem ); + })); + }, + + andSelf: function() { + return this.add( this.prevObject ); + }, + + domManip: function( args, table, callback ) { + if ( this[0] ) { + var fragment = (this[0].ownerDocument || this[0]).createDocumentFragment(), + scripts = jQuery.clean( args, (this[0].ownerDocument || this[0]), fragment ), + first = fragment.firstChild; + + if ( first ) + for ( var i = 0, l = this.length; i < l; i++ ) + callback.call( root(this[i], first), this.length > 1 || i > 0 ? + fragment.cloneNode(true) : fragment ); + + if ( scripts ) + jQuery.each( scripts, evalScript ); + } + + return this; + + function root( elem, cur ) { + return table && jQuery.nodeName(elem, "table") && jQuery.nodeName(cur, "tr") ? + (elem.getElementsByTagName("tbody")[0] || + elem.appendChild(elem.ownerDocument.createElement("tbody"))) : + elem; + } + } +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +function evalScript( i, elem ) { + if ( elem.src ) + jQuery.ajax({ + url: elem.src, + async: false, + dataType: "script" + }); + + else + jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); + + if ( elem.parentNode ) + elem.parentNode.removeChild( elem ); +} + +function now(){ + return +new Date; +} + +jQuery.extend = jQuery.fn.extend = function() { + // copy reference to target object + var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) + target = {}; + + // extend jQuery itself if only one argument is passed + if ( length == i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) + // Extend the base object + for ( var name in options ) { + var src = target[ name ], copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) + continue; + + // Recurse if we're merging object values + if ( deep && copy && typeof copy === "object" && !copy.nodeType ) + target[ name ] = jQuery.extend( deep, + // Never move original objects, clone them + src || ( copy.length != null ? [ ] : { } ) + , copy ); + + // Don't bring in undefined values + else if ( copy !== undefined ) + target[ name ] = copy; + + } + + // Return the modified object + return target; +}; + +// exclude the following css properties to add px +var exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i, + // cache defaultView + defaultView = document.defaultView || {}, + toString = Object.prototype.toString; + +jQuery.extend({ + noConflict: function( deep ) { + window.$ = _$; + + if ( deep ) + window.jQuery = _jQuery; + + return jQuery; + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return toString.call(obj) === "[object Function]"; + }, + + isArray: function( obj ) { + return toString.call(obj) === "[object Array]"; + }, + + // check if an element is in a (or is an) XML document + isXMLDoc: function( elem ) { + return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || + !!elem.ownerDocument && jQuery.isXMLDoc( elem.ownerDocument ); + }, + + // Evalulates a script in a global context + globalEval: function( data ) { + if ( data && /\S/.test(data) ) { + // Inspired by code by Andrea Giammarchi + // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html + var head = document.getElementsByTagName("head")[0] || document.documentElement, + script = document.createElement("script"); + + script.type = "text/javascript"; + if ( jQuery.support.scriptEval ) + script.appendChild( document.createTextNode( data ) ); + else + script.text = data; + + // Use insertBefore instead of appendChild to circumvent an IE6 bug. + // This arises when a base node is used (#2709). + head.insertBefore( script, head.firstChild ); + head.removeChild( script ); + } + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase(); + }, + + // args is for internal usage only + each: function( object, callback, args ) { + var name, i = 0, length = object.length; + + if ( args ) { + if ( length === undefined ) { + for ( name in object ) + if ( callback.apply( object[ name ], args ) === false ) + break; + } else + for ( ; i < length; ) + if ( callback.apply( object[ i++ ], args ) === false ) + break; + + // A special, fast, case for the most common use of each + } else { + if ( length === undefined ) { + for ( name in object ) + if ( callback.call( object[ name ], name, object[ name ] ) === false ) + break; + } else + for ( var value = object[0]; + i < length && callback.call( value, i, value ) !== false; value = object[++i] ){} + } + + return object; + }, + + prop: function( elem, value, type, i, name ) { + // Handle executable functions + if ( jQuery.isFunction( value ) ) + value = value.call( elem, i ); + + // Handle passing in a number to a CSS property + return typeof value === "number" && type == "curCSS" && !exclude.test( name ) ? + value + "px" : + value; + }, + + className: { + // internal only, use addClass("class") + add: function( elem, classNames ) { + jQuery.each((classNames || "").split(/\s+/), function(i, className){ + if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) ) + elem.className += (elem.className ? " " : "") + className; + }); + }, + + // internal only, use removeClass("class") + remove: function( elem, classNames ) { + if (elem.nodeType == 1) + elem.className = classNames !== undefined ? + jQuery.grep(elem.className.split(/\s+/), function(className){ + return !jQuery.className.has( classNames, className ); + }).join(" ") : + ""; + }, + + // internal only, use hasClass("class") + has: function( elem, className ) { + return elem && jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1; + } + }, + + // A method for quickly swapping in/out CSS properties to get correct calculations + swap: function( elem, options, callback ) { + var old = {}; + // Remember the old values, and insert the new ones + for ( var name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + callback.call( elem ); + + // Revert the old values + for ( var name in options ) + elem.style[ name ] = old[ name ]; + }, + + css: function( elem, name, force, extra ) { + if ( name == "width" || name == "height" ) { + var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ]; + + function getWH() { + val = name == "width" ? elem.offsetWidth : elem.offsetHeight; + + if ( extra === "border" ) + return; + + jQuery.each( which, function() { + if ( !extra ) + val -= parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0; + if ( extra === "margin" ) + val += parseFloat(jQuery.curCSS( elem, "margin" + this, true)) || 0; + else + val -= parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0; + }); + } + + if ( elem.offsetWidth !== 0 ) + getWH(); + else + jQuery.swap( elem, props, getWH ); + + return Math.max(0, Math.round(val)); + } + + return jQuery.curCSS( elem, name, force ); + }, + + curCSS: function( elem, name, force ) { + var ret, style = elem.style; + + // We need to handle opacity special in IE + if ( name == "opacity" && !jQuery.support.opacity ) { + ret = jQuery.attr( style, "opacity" ); + + return ret == "" ? + "1" : + ret; + } + + // Make sure we're using the right name for getting the float value + if ( name.match( /float/i ) ) + name = styleFloat; + + if ( !force && style && style[ name ] ) + ret = style[ name ]; + + else if ( defaultView.getComputedStyle ) { + + // Only "float" is needed here + if ( name.match( /float/i ) ) + name = "float"; + + name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase(); + + var computedStyle = defaultView.getComputedStyle( elem, null ); + + if ( computedStyle ) + ret = computedStyle.getPropertyValue( name ); + + // We should always get a number back from opacity + if ( name == "opacity" && ret == "" ) + ret = "1"; + + } else if ( elem.currentStyle ) { + var camelCase = name.replace(/\-(\w)/g, function(all, letter){ + return letter.toUpperCase(); + }); + + ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ]; + + // From the awesome hack by Dean Edwards + // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + + // If we're not dealing with a regular pixel number + // but a number that has a weird ending, we need to convert it to pixels + if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) { + // Remember the original values + var left = style.left, rsLeft = elem.runtimeStyle.left; + + // Put in the new values to get a computed value out + elem.runtimeStyle.left = elem.currentStyle.left; + style.left = ret || 0; + ret = style.pixelLeft + "px"; + + // Revert the changed values + style.left = left; + elem.runtimeStyle.left = rsLeft; + } + } + + return ret; + }, + + clean: function( elems, context, fragment ) { + context = context || document; + + // !context.createElement fails in IE with an error but returns typeof 'object' + if ( typeof context.createElement === "undefined" ) + context = context.ownerDocument || context[0] && context[0].ownerDocument || document; + + // If a single string is passed in and it's a single tag + // just do a createElement and skip the rest + if ( !fragment && elems.length === 1 && typeof elems[0] === "string" ) { + var match = /^<(\w+)\s*\/?>$/.exec(elems[0]); + if ( match ) + return [ context.createElement( match[1] ) ]; + } + + var ret = [], scripts = [], div = context.createElement("div"); + + jQuery.each(elems, function(i, elem){ + if ( typeof elem === "number" ) + elem += ''; + + if ( !elem ) + return; + + // Convert html string into DOM nodes + if ( typeof elem === "string" ) { + // Fix "XHTML"-style tags in all browsers + elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){ + return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ? + all : + front + ">"; + }); + + // Trim whitespace, otherwise indexOf won't work as expected + var tags = elem.replace(/^\s+/, "").substring(0, 10).toLowerCase(); + + var wrap = + // option or optgroup + !tags.indexOf("", "" ] || + + !tags.indexOf("", "" ] || + + tags.match(/^<(thead|tbody|tfoot|colg|cap)/) && + [ 1, "", "
" ] || + + !tags.indexOf("", "" ] || + + // matched above + (!tags.indexOf("", "" ] || + + !tags.indexOf("", "" ] || + + // IE can't serialize and '); + }; + + if (scriptConfig.autoLoadDependencies) { + addScript("/javascripts/webtoolkit.base64.js"); + addScript("/javascripts/swfobject.js"); + addScript("/javascripts/jQuery/jquery-1.3.2.js"); + addScript("/javascripts/jQuery/jquery-ui-1.7.1.custom.min.js"); + addScript("/javascripts/underscore/underscore.js"); + addScript("/javascripts/nglr/Loader.js"); + addScript("/javascripts/nglr/API.js"); + addScript("/javascripts/nglr/Binder.js"); + addScript("/javascripts/nglr/ControlBar.js"); + addScript("/javascripts/nglr/DataStore.js"); + addScript("/javascripts/nglr/Filters.js"); + addScript("/javascripts/nglr/JSON.js"); + addScript("/javascripts/nglr/Model.js"); + addScript("/javascripts/nglr/Parser.js"); + addScript("/javascripts/nglr/Scope.js"); + addScript("/javascripts/nglr/Server.js"); + addScript("/javascripts/nglr/Users.js"); + addScript("/javascripts/nglr/Validators.js"); + addScript("/javascripts/nglr/Widgets.js"); + } else { + addScript("/ajax/libs/swfobject/2.2/swfobject.js", "http://ajax.googleapis.com"); + addScript("/ajax/libs/jquery/1.3.2/jquery.min.js", "http://ajax.googleapis.com"); + addScript("/ajax/libs/jqueryui/1.7.2/jquery-ui.min.js", "http://ajax.googleapis.com"); + } + + window.onload = function() { + window.angular.init = function(root, config){ + var cnfgMerged = _.clone(scriptConfig||{}); + _.extend(cnfgMerged, config); + new nglr.Loader(root, jQuery("head"), cnfgMerged).load(); + }; + + var doc = window.document; + if (scriptConfig.bindRootId) { + doc = null; + var ids = scriptConfig.bindRootId.split('|'); + for ( var i = 0; i < ids.length && !doc; i++) { + var idCond = ids[i].split('?'); + var id = idCond[0]; + if (idCond.length > 1) { + if (!window.document.getElementById(idCond[1])) { + continue; + } + } + doc = window.document.getElementById(id); + } + } + if (scriptConfig.autoBind && doc) { + window.angular.init(doc); + } + if (typeof previousOnLoad === 'function') { + try { + previousOnLoad.apply(this, arguments); + } catch (e) {} + } + }; +})(window.onload); + + diff --git a/src/test/Runner.js b/src/test/Runner.js new file mode 100644 index 00000000..478ef73e --- /dev/null +++ b/src/test/Runner.js @@ -0,0 +1,160 @@ +nglr.test.ScenarioRunner = function(scenarios, body) { + this.scenarios = scenarios; + this.body = body; +}; + +nglr.test.ScenarioRunner.prototype = { + run:function(){ + this.setUpUI(); + this.runScenarios(); + }, + setUpUI:function(){ + this.body.html( + '
' + + '
' + + '
' + + '
' + + '' + + '
'); + this.console = this.body.find(".console"); + this.testFrame = this.body.find("iframe"); + this.console.find(".run").live("click", function(){ + jQuery(this).parent().find('.log').toggle(); + }); + }, + runScenarios:function(){ + var runner = new nglr.test.Runner(this.console, this.testFrame); + _.stepper(this.scenarios, function(next, scenario, name){ + new nglr.test.Scenario(name, scenario).run(runner, next); + }, function(){ + } + ); + } +}; + +nglr.test.Runner = function(console, frame){ + this.console = console; + this.current = null; + this.tests = []; + this.frame = frame; +}; +nglr.test.Runner.prototype = { + start:function(name){ + var current = this.current = { + name:name, + start:new Date().getTime(), + scenario:jQuery('
') + }; + current.run = current.scenario.append( + '
' + + '.' + + '.' + + '.' + + '').find(".run"); + current.log = current.scenario.append('
').find(".log"); + current.run.find(".name").text(name); + this.tests.push(current); + this.console.append(current.scenario); + }, + end:function(name){ + var current = this.current; + var run = current.run; + this.current = null; + current.end = new Date().getTime(); + current.time = current.end - current.start; + run.find(".time").text(current.time); + run.find(".state").text(current.error ? "FAIL" : "PASS"); + run.addClass(current.error ? "fail" : "pass"); + if (current.error) + run.find(".run").append('').text(current.error); + current.scenario.find(".log").hide(); + }, + log:function(level) { + var buf = []; + for ( var i = 1; i < arguments.length; i++) { + var arg = arguments[i]; + buf.push(typeof arg == "string" ?arg:nglr.toJson(arg)); + } + var log = jQuery('
'); + log.text(buf.join(" ")); + this.current.log.append(log); + this.console.scrollTop(this.console[0].scrollHeight); + if (level == "error") + this.current.error = buf.join(" "); + } +}; + +nglr.test.Scenario = function(name, scenario){ + this.name = name; + this.scenario = scenario; +}; +nglr.test.Scenario.prototype = { + run:function(runner, callback) { + var self = this; + _.stepper(this.scenario, function(next, steps, name){ + if (name.charAt(0) == '$') { + next(); + } else { + runner.start(self.name + "::" + name); + var allSteps = (self.scenario.$before||[]).concat(steps); + _.stepper(allSteps, function(next, step){ + self.executeStep(runner, step, next); + }, function(){ + runner.end(); + next(); + }); + } + }, callback); + }, + verb:function(step){ + var fn = null; + if (!step) fn = function (){ throw "Step is null!"; } + else if (step.Given) fn = angular.test.GIVEN[step.Given]; + else if (step.When) fn = angular.test.WHEN[step.When]; + else if (step.Then) fn = angular.test.THEN[step.Then]; + return fn || function (){ + throw "ERROR: Need Given/When/Then got: " + nglr.toJson(step); + }; + }, + context: function(runner) { + var frame = runner.frame; + var window = frame[0].contentWindow; + var document; + if (window.jQuery) + document = window.jQuery(window.document); + var context = { + frame:frame, + window:window, + log:_.bind(runner.log, runner, "info"), + document:document, + assert:function(element, path){ + if (element.size() != 1) { + throw "Expected to find '1' found '"+ + element.size()+"' for '"+path+"'."; + } + return element; + }, + element:function(path){ + var exp = path.replace("{{","[ng-bind=").replace("}}", "]"); + var element = document.find(exp); + return context.assert(element, path); + } + }; + return context; + }, + executeStep:function(runner, step, callback) { + if (!step) { + callback(); + return; + } + runner.log("info", nglr.toJson(step)); + var fn = this.verb(step); + var context = this.context(runner); + _.extend(context, step); + try { + (fn.call(context)||function(c){c();})(callback); + } catch (e) { + runner.log("error", "ERROR: " + nglr.toJson(e)); + } + } +}; diff --git a/src/test/Steps.js b/src/test/Steps.js new file mode 100644 index 00000000..af4b84d6 --- /dev/null +++ b/src/test/Steps.js @@ -0,0 +1,57 @@ +angular.test.GIVEN = { + browser:function(){ + var self = this; + if (jQuery.browser.safari && this.frame.attr('src') == this.at) { + this.window.location.reload(); + } else { + this.frame.attr('src', this.at); + } + return function(done){ + self.frame.load(function(){ + self.frame.unbind(); + done(); + }); + }; + }, + dataset:function(){ + this.frame.name="$DATASET:" + nglr.toJson({dataset:this.dataset}); + } +}; +angular.test.WHEN = { + enter:function(){ + var element = this.element(this.at); + element.attr('value', this.text); + element.change(); + }, + click:function(){ + var element = this.element(this.at); + var input = element[0]; + // emulate the browser behavior which causes it + // to be overridden at the end. + var checked = input.checked = !input.checked; + element.click(); + input.checked = checked; + }, + select:function(){ + var element = this.element(this.at); + var path = "option[value=" + this.option + "]"; + var option = this.assert(element.find(path)); + option[0].selected = !option[0].selected; + element.change(); + } +}; +angular.test.THEN = { + text:function(){ + var element = this.element(this.at); + if (typeof this.should_be != undefined ) { + var should_be = this.should_be; + if (_.isArray(this.should_be)) + should_be = JSON.stringify(should_be); + if (element.text() != should_be) + throw "Expected " + should_be + + " but was " + element.text() + "."; + } + }, + drainRequestQueue:function(){ + } +}; diff --git a/src/test/_namespace.js b/src/test/_namespace.js new file mode 100644 index 00000000..78f430f1 --- /dev/null +++ b/src/test/_namespace.js @@ -0,0 +1,5 @@ +if (!angular) angular = {}; +if (!angular.test) angular.test = {}; +if (!angular.test.GIVEN) angular.test.GIVEN = {}; +if (!angular.test.WHEN) angular.test.WHEN = {}; +if (!angular.test.THEN) angular.test.THEN = {}; diff --git a/test/ApiTest.js b/test/ApiTest.js new file mode 100644 index 00000000..250a27b1 --- /dev/null +++ b/test/ApiTest.js @@ -0,0 +1,252 @@ +ApiTest = TestCase("ApiTest"); + +ApiTest.prototype.testItShouldReturnTypeOf = function (){ + assertEquals("undefined", angular.Object.typeOf(undefined)); + assertEquals("null", angular.Object.typeOf(null)); + assertEquals("object", angular.Collection.typeOf({})); + assertEquals("array", angular.Array.typeOf([])); + assertEquals("string", angular.Object.typeOf("")); + assertEquals("date", angular.Object.typeOf(new Date())); + assertEquals("element", angular.Object.typeOf(document.body)); + assertEquals("function", angular.Object.typeOf(function(){})); +}; + +ApiTest.prototype.testItShouldReturnSize = function(){ + assertEquals(0, angular.Collection.size({})); + assertEquals(1, angular.Collection.size({a:"b"})); + assertEquals(0, angular.Object.size({})); + assertEquals(1, angular.Array.size([0])); +}; + +ApiTest.prototype.testIncludeIf = function() { + var array = []; + var obj = {}; + + angular.Array.includeIf(array, obj, true); + angular.Array.includeIf(array, obj, true); + assertTrue(_.include(array, obj)); + assertEquals(1, array.length); + + angular.Array.includeIf(array, obj, false); + assertFalse(_.include(array, obj)); + assertEquals(0, array.length); + + angular.Array.includeIf(array, obj, 'x'); + assertTrue(_.include(array, obj)); + assertEquals(1, array.length); + angular.Array.includeIf(array, obj, ''); + assertFalse(_.include(array, obj)); + assertEquals(0, array.length); +}; + +ApiTest.prototype.testSum = function(){ + assertEquals(3, angular.Array.sum([{a:"1"}, {a:"2"}], 'a')); +}; + +ApiTest.prototype.testSumContainingNaN = function(){ + assertEquals(1, angular.Array.sum([{a:1}, {a:Number.NaN}], 'a')); + assertEquals(1, angular.Array.sum([{a:1}, {a:Number.NaN}], function($){return $.a;})); +}; + +ApiTest.prototype.testInclude = function(){ + assertTrue(angular.Array.include(['a'], 'a')); + assertTrue(angular.Array.include(['a', 'b'], 'a')); + assertTrue(!angular.Array.include(['c'], 'a')); + assertTrue(!angular.Array.include(['c', 'b'], 'a')); +}; + +ApiTest.prototype.testIndex = function(){ + assertEquals(angular.Array.indexOf(['a'], 'a'), 0); + assertEquals(angular.Array.indexOf(['a', 'b'], 'a'), 0); + assertEquals(angular.Array.indexOf(['b', 'a'], 'a'), 1); + assertEquals(angular.Array.indexOf(['b', 'b'],'x'), -1); +}; + +ApiTest.prototype.testRemove = function(){ + var items = ['a', 'b', 'c']; + assertEquals(angular.Array.remove(items, 'q'), 'q'); + assertEquals(items.length, 3); + + assertEquals(angular.Array.remove(items, 'b'), 'b'); + assertEquals(items.length, 2); + + assertEquals(angular.Array.remove(items, 'a'), 'a'); + assertEquals(items.length, 1); + + assertEquals(angular.Array.remove(items, 'c'), 'c'); + assertEquals(items.length, 0); + + assertEquals(angular.Array.remove(items, 'q'), 'q'); + assertEquals(items.length, 0); +}; + +ApiTest.prototype.testFindById = function() { + var items = [{$id:1}, {$id:2}, {$id:3}]; + assertNull(angular.Array.findById(items, 0)); + assertEquals(items[0], angular.Array.findById(items, 1)); + assertEquals(items[1], angular.Array.findById(items, 2)); + assertEquals(items[2], angular.Array.findById(items, 3)); +}; + +ApiTest.prototype.testFilter = function() { + var items = ["MIsKO", {name:"shyam"}, ["adam"], 1234]; + assertEquals(4, angular.Array.filter(items, "").length); + assertEquals(4, angular.Array.filter(items, undefined).length); + + assertEquals(1, angular.Array.filter(items, 'iSk').length); + assertEquals("MIsKO", angular.Array.filter(items, 'isk')[0]); + + assertEquals(1, angular.Array.filter(items, 'yam').length); + assertEquals(items[1], angular.Array.filter(items, 'yam')[0]); + + assertEquals(1, angular.Array.filter(items, 'da').length); + assertEquals(items[2], angular.Array.filter(items, 'da')[0]); + + assertEquals(1, angular.Array.filter(items, '34').length); + assertEquals(1234, angular.Array.filter(items, '34')[0]); + + assertEquals(0, angular.Array.filter(items, "I don't exist").length); +}; + +ApiTest.prototype.testShouldNotFilterOnSystemData = function() { + assertEquals("", "".charAt(0)); // assumption + var items = [{$name:"misko"}]; + assertEquals(0, angular.Array.filter(items, "misko").length); +}; + +ApiTest.prototype.testFilterOnSpecificProperty = function() { + var items = [{ignore:"a", name:"a"}, {ignore:"a", name:"abc"}]; + assertEquals(2, angular.Array.filter(items, {}).length); + + assertEquals(2, angular.Array.filter(items, {name:'a'}).length); + + assertEquals(1, angular.Array.filter(items, {name:'b'}).length); + assertEquals("abc", angular.Array.filter(items, {name:'b'})[0].name); +}; + +ApiTest.prototype.testFilterOnFunction = function() { + var items = [{name:"a"}, {name:"abc", done:true}]; + assertEquals(1, angular.Array.filter(items, function(i){return i.done;}).length); +}; + +ApiTest.prototype.testFilterIsAndFunction = function() { + var items = [{first:"misko", last:"hevery"}, + {first:"adam", last:"abrons"}]; + + assertEquals(2, angular.Array.filter(items, {first:'', last:''}).length); + assertEquals(1, angular.Array.filter(items, {first:'', last:'hevery'}).length); + assertEquals(0, angular.Array.filter(items, {first:'adam', last:'hevery'}).length); + assertEquals(1, angular.Array.filter(items, {first:'misko', last:'hevery'}).length); + assertEquals(items[0], angular.Array.filter(items, {first:'misko', last:'hevery'})[0]); +}; + +ApiTest.prototype.testFilterNot = function() { + var items = ["misko", "adam"]; + + assertEquals(1, angular.Array.filter(items, '!isk').length); + assertEquals(items[1], angular.Array.filter(items, '!isk')[0]); +}; + +ApiTest.prototype.testAdd = function() { + var add = angular.Array.add; + assertJsonEquals([{}, "a"], add(add([]),"a")); +}; + +ApiTest.prototype.testCount = function() { + var array = [{name:'a'},{name:'b'},{name:''}]; + var obj = {}; + + assertEquals(3, angular.Array.count(array)); + assertEquals(2, angular.Array.count(array, 'name')); + assertEquals(1, angular.Array.count(array, 'name=="a"')); +}; + +ApiTest.prototype.testFind = function() { + var array = [{name:'a'},{name:'b'},{name:''}]; + var obj = {}; + + assertEquals(undefined, angular.Array.find(array, 'false')); + assertEquals('default', angular.Array.find(array, 'false', 'default')); + assertEquals('a', angular.Array.find(array, 'name == "a"').name); + assertEquals('', angular.Array.find(array, 'name == ""').name); +}; + +ApiTest.prototype.testItShouldSortArray = function() { + assertEquals([2,15], angular.Array.orderBy([15,2])); + assertEquals(["a","B", "c"], angular.Array.orderBy(["c","B", "a"])); + assertEquals([15,"2"], angular.Array.orderBy([15,"2"])); + assertEquals(["15","2"], angular.Array.orderBy(["15","2"])); + assertJsonEquals([{a:2},{a:15}], angular.Array.orderBy([{a:15},{a:2}], 'a')); + assertJsonEquals([{a:2},{a:15}], angular.Array.orderBy([{a:15},{a:2}], 'a', "F")); +}; + +ApiTest.prototype.testItShouldSortArrayInReverse = function() { + assertJsonEquals([{a:15},{a:2}], angular.Array.orderBy([{a:15},{a:2}], 'a', true)); + assertJsonEquals([{a:15},{a:2}], angular.Array.orderBy([{a:15},{a:2}], 'a', "T")); + assertJsonEquals([{a:15},{a:2}], angular.Array.orderBy([{a:15},{a:2}], 'a', "reverse")); +}; + +ApiTest.prototype.testItShouldSortArrayByPredicate = function() { + assertJsonEquals([{a:2, b:1},{a:15, b:1}], + angular.Array.orderBy([{a:15, b:1},{a:2, b:1}], ['a', 'b'])); + assertJsonEquals([{a:2, b:1},{a:15, b:1}], + angular.Array.orderBy([{a:15, b:1},{a:2, b:1}], ['b', 'a'])); + assertJsonEquals([{a:15, b:1},{a:2, b:1}], + angular.Array.orderBy([{a:15, b:1},{a:2, b:1}], ['+b', '-a'])); +}; + +ApiTest.prototype.testQuoteString = function(){ + assertEquals(angular.String.quote('a'), '"a"'); + assertEquals(angular.String.quote('\\'), '"\\\\"'); + assertEquals(angular.String.quote("'a'"), '"\'a\'"'); + assertEquals(angular.String.quote('"a"'), '"\\"a\\""'); + assertEquals(angular.String.quote('\n\f\r\t'), '"\\n\\f\\r\\t"'); +}; + +ApiTest.prototype.testQuoteStringBug = function(){ + assertEquals(angular.String.quote('"7\\\\\\\"7"', "7\\\"7")); +}; + +ApiTest.prototype.testQuoteUnicode = function(){ + assertEquals('"abc\\u00a0def"', angular.String.quoteUnicode('abc\u00A0def')); +}; + +ApiTest.prototype.testMerge = function() { + var array = [{name:"misko"}]; + angular.Array.merge(array, 0, {name:"", email:"email1"}); + angular.Array.merge(array, 1, {name:"adam", email:"email2"}); + assertJsonEquals([{"email":"email1","name":"misko"},{"email":"email2","name":"adam"}], array); +}; + +ApiTest.prototype.testOrderByToggle = function() { + var orderByToggle = angular.Array.orderByToggle; + var predicate = []; + assertEquals(['+a'], orderByToggle(predicate, 'a')); + assertEquals(['-a'], orderByToggle(predicate, 'a')); + + assertEquals(['-a', '-b'], orderByToggle(['-b', 'a'], 'a')); +}; + +ApiTest.prototype.testOrderByToggle = function() { + var orderByDirection = angular.Array.orderByDirection; + assertEquals("", orderByDirection(['+a','b'], 'x')); + assertEquals("", orderByDirection(['+a','b'], 'b')); + assertEquals('ng-ascend', orderByDirection(['a','b'], 'a')); + assertEquals('ng-ascend', orderByDirection(['+a','b'], 'a')); + assertEquals('ng-descend', orderByDirection(['-a','b'], 'a')); + assertEquals('up', orderByDirection(['+a','b'], 'a', 'up', 'down')); + assertEquals('down', orderByDirection(['-a','b'], 'a', 'up', 'down')); +}; + +ApiTest.prototype.testDateToUTC = function(){ + var date = new Date("Sep 10 2003 13:02:03 GMT"); + assertEquals("date", angular.Object.typeOf(date)); + assertEquals("2003-09-10T13:02:03Z", angular.Date.toString(date)); +}; + +ApiTest.prototype.testStringFromUTC = function(){ + var date = angular.String.toDate("2003-09-10T13:02:03Z"); + assertEquals("date", angular.Object.typeOf(date)); + assertEquals("2003-09-10T13:02:03Z", angular.Date.toString(date)); + assertEquals("str", angular.String.toDate("str")); +}; diff --git a/test/Base64Test.js b/test/Base64Test.js new file mode 100644 index 00000000..a9353186 --- /dev/null +++ b/test/Base64Test.js @@ -0,0 +1,5 @@ +Base64Test = TestCase('Base64Test'); + +Base64Test.prototype.testEncodeDecode = function(){ + assertEquals(Base64.decode(Base64.encode('hello')), 'hello'); +}; diff --git a/test/BinderTest.js b/test/BinderTest.js new file mode 100644 index 00000000..d033996d --- /dev/null +++ b/test/BinderTest.js @@ -0,0 +1,1001 @@ +BinderTest = TestCase('BinderTest'); + +function compile(content, initialScope, config) { + var h = html(content); + config = config || {autoSubmit:true}; + var scope = new nglr.Scope(initialScope, "ROOT"); + h.data('scope', scope); + var binder = new nglr.Binder(h[0], new nglr.WidgetFactory(), new MockUrlWatcher(), config); + var datastore = new nglr.DataStore(); + scope.set("$datastore", datastore); + scope.set("$binder", binder); + scope.set("$anchor", binder.anchor); + binder.entity(scope); + binder.compile(); + return {node:h, binder:binder, scope:scope}; +} + +function compileToHtml(content) { + return compile(content).node.sortedHtml(); +} + + +BinderTest.prototype.testParseTextWithNoBindings = function(){ + var parts = nglr.Binder.parseBindings("a"); + assertEquals(parts.length, 1); + assertEquals(parts[0], "a"); + assertTrue(!nglr.Binder.binding(parts[0])); +}; + +BinderTest.prototype.testParseEmptyText = function(){ + var parts = nglr.Binder.parseBindings(""); + assertEquals(parts.length, 1); + assertEquals(parts[0], ""); + assertTrue(!nglr.Binder.binding(parts[0])); +}; + +BinderTest.prototype.testParseInnerBinding = function(){ + var parts = nglr.Binder.parseBindings("a{{b}}c"); + assertEquals(parts.length, 3); + assertEquals(parts[0], "a"); + assertTrue(!nglr.Binder.binding(parts[0])); + assertEquals(parts[1], "{{b}}"); + assertEquals(nglr.Binder.binding(parts[1]), "b"); + assertEquals(parts[2], "c"); + assertTrue(!nglr.Binder.binding(parts[2])); +}; + +BinderTest.prototype.testParseEndingBinding = function(){ + var parts = nglr.Binder.parseBindings("a{{b}}"); + assertEquals(parts.length, 2); + assertEquals(parts[0], "a"); + assertTrue(!nglr.Binder.binding(parts[0])); + assertEquals(parts[1], "{{b}}"); + assertEquals(nglr.Binder.binding(parts[1]), "b"); +}; + +BinderTest.prototype.testParseBeggingBinding = function(){ + var parts = nglr.Binder.parseBindings("{{b}}c"); + assertEquals(parts.length, 2); + assertEquals(parts[0], "{{b}}"); + assertEquals(nglr.Binder.binding(parts[0]), "b"); + assertEquals(parts[1], "c"); + assertTrue(!nglr.Binder.binding(parts[1])); +}; + +BinderTest.prototype.testParseLoanBinding = function(){ + var parts = nglr.Binder.parseBindings("{{b}}"); + assertEquals(parts.length, 1); + assertEquals(parts[0], "{{b}}"); + assertEquals(nglr.Binder.binding(parts[0]), "b"); +}; + +BinderTest.prototype.testParseTwoBindings = function(){ + var parts = nglr.Binder.parseBindings("{{b}}{{c}}"); + assertEquals(parts.length, 2); + assertEquals(parts[0], "{{b}}"); + assertEquals(nglr.Binder.binding(parts[0]), "b"); + assertEquals(parts[1], "{{c}}"); + assertEquals(nglr.Binder.binding(parts[1]), "c"); +}; + +BinderTest.prototype.testParseTwoBindingsWithTextInMiddle = function(){ + var parts = nglr.Binder.parseBindings("{{b}}x{{c}}"); + assertEquals(parts.length, 3); + assertEquals(parts[0], "{{b}}"); + assertEquals(nglr.Binder.binding(parts[0]), "b"); + assertEquals(parts[1], "x"); + assertTrue(!nglr.Binder.binding(parts[1])); + assertEquals(parts[2], "{{c}}"); + assertEquals(nglr.Binder.binding(parts[2]), "c"); +}; + +BinderTest.prototype.testParseMultiline = function(){ + var parts = nglr.Binder.parseBindings('"X\nY{{A\nB}}C\nD"'); + assertTrue(!!nglr.Binder.binding('{{A\nB}}')); + assertEquals(parts.length, 3); + assertEquals(parts[0], '"X\nY'); + assertEquals(parts[1], '{{A\nB}}'); + assertEquals(parts[2], 'C\nD"'); +}; + +BinderTest.prototype.testHasBinding = function(){ + assertTrue(nglr.Binder.hasBinding("{{a}}")); + assertTrue(!nglr.Binder.hasBinding("a")); + assertTrue(nglr.Binder.hasBinding("{{b}}x{{c}}")); +}; + + +BinderTest.prototype.tearDown = function(){ + jQuery("*", document).die(); + jQuery(document).unbind(); +}; + +BinderTest.prototype.testChangingTextfieldUpdatesModel = function(){ + var state = compile('', {model:{}}); + state.binder.updateView(); + assertEquals('abc', state.scope.get('model').price); +}; + +BinderTest.prototype.testChangingTextareaUpdatesModel = function(){ + var form = html(''); + var scope = new nglr.Scope({model:{}}); + form.data('scope', scope); + var binder = new nglr.Binder(form.get(0), new nglr.WidgetFactory(), new MockUrlWatcher()); + binder.compile(); + binder.updateView(); + assertEquals(scope.get('model').note, 'abc'); +}; + +BinderTest.prototype.testChangingRadioUpdatesModel = function(){ + var form = html('' + + ''); + var scope = new nglr.Scope({model:{}}); + form.data('scope', scope); + var binder = new nglr.Binder(form.get(0), new nglr.WidgetFactory(), new MockUrlWatcher()); + binder.compile(); + binder.updateView(); + assertEquals(scope.get('model').price, 'A'); +}; + +BinderTest.prototype.testChangingCheckboxUpdatesModel = function(){ + var form = html(''); + var scope = new nglr.Scope({model:{}}); + form.data('scope', scope); + var binder = new nglr.Binder(form.get(0), new nglr.WidgetFactory(), new MockUrlWatcher()); + binder.compile(); + binder.updateView(); + assertEquals('A', scope.get('model').price); +}; + +BinderTest.prototype.testBindUpdate = function() { + var c = compile('
'); + c.binder.updateView(); + assertEquals(123, c.scope.get('a')); +}; + +BinderTest.prototype.testChangingSelectNonSelectedUpdatesModel = function(){ + var form = html(''); + var scope = new nglr.Scope({model:{}}); + form.data('scope', scope); + var binder = new nglr.Binder(form.get(0), new nglr.WidgetFactory(), new MockUrlWatcher()); + binder.compile(); + binder.updateView(); + assertEquals('A', scope.get('model').price); +}; + +BinderTest.prototype.testChangingMultiselectUpdatesModel = function(){ + var form = html(''); + var scope = new nglr.Scope({Invoice:{}}); + form.data('scope', scope); + var binder = new nglr.Binder(form.get(0), new nglr.WidgetFactory(), new MockUrlWatcher()); + binder.compile(); + binder.updateView(); + assertJsonEquals(["A", "B"], scope.get('Invoice').options); +}; + +BinderTest.prototype.testChangingSelectSelectedUpdatesModel = function(){ + var form = html(''); + var scope = new nglr.Scope({model:{}}); + form.data('scope', scope); + var binder = new nglr.Binder(form.get(0), new nglr.WidgetFactory(), new MockUrlWatcher()); + binder.compile(); + binder.updateView(); + assertEquals(scope.get('model').price, 'b'); +}; + +BinderTest.prototype.testExecuteInitialization = function() { + var form = html('
'); + var scope = new nglr.Scope(); + form.data('scope', scope); + var binder = new nglr.Binder(form.get(0)); + binder.executeInit(); + assertEquals(scope.get('a'), 123); +}; + +BinderTest.prototype.testExecuteInitializationStatements = function() { + var form = html('
'); + var scope = new nglr.Scope(); + form.data('scope', scope); + var binder = new nglr.Binder(form.get(0)); + binder.executeInit(); + assertEquals(scope.get('a'), 123); + assertEquals(scope.get('b'), 345); +}; + +BinderTest.prototype.testApplyTextBindings = function(){ + var form = html('
x
'); + var scope = new nglr.Scope({model:{a:123}}); + form.data('scope', scope); + var binder = new nglr.Binder(form.get(0), null, new MockUrlWatcher()); + binder.compile(); + binder.updateView(); + assertEquals('123', form.text()); +}; + +BinderTest.prototype.testReplaceBindingInTextWithSpan = function() { + assertEquals(compileToHtml("a{{b}}c"), 'ac'); + assertEquals(compileToHtml("{{b}}"), ''); +}; + +BinderTest.prototype.testReplaceBindingCreatesCorrectNumberOfWidgets = function() { + var h = html("space{{a}}{{a}}a{{a}}{{a}}"); + h.data('scope', new nglr.Scope()); + var binder = new nglr.Binder(h.get(0), new nglr.WidgetFactory()); + binder.compile(); + + assertEquals(4, h.scope().widgets.length); +}; + +BinderTest.prototype.testBindingSpaceConfusesIE = function() { + if (!nglr.msie) return; + var span = document.createElement("span"); + span.innerHTML = ' '; + var nbsp = span.firstChild.nodeValue; + assertEquals( + ''+nbsp+'', + compileToHtml("{{a}} {{b}}")); + assertEquals( + ''+nbsp+'x '+nbsp+'(', + compileToHtml("{{A}} x {{B}} ({{C}})")); +}; + +BinderTest.prototype.testBindingOfAttributes = function() { + var form = html(""); + form.data('scope', new nglr.Scope()); + var binder = new nglr.Binder(form.get(0)); + binder.compile(); + var attrbinding = form.find("a").attr("ng-bind-attr"); + var bindings = nglr.fromJson(attrbinding); + assertEquals("http://s/a{{b}}c", decodeURI(bindings.href)); + assertTrue(!bindings.foo); +}; + +BinderTest.prototype.testMarkMultipleAttributes = function() { + var form = html(""); + form.data('scope', new nglr.Scope()); + var binder = new nglr.Binder(form.get(0)); + binder.compile(); + var attrbinding = form.find("a").attr("ng-bind-attr"); + var bindings = nglr.fromJson(attrbinding); + assertEquals(decodeURI(bindings.href), "http://s/a{{b}}c"); + assertEquals(bindings.foo, "{{d}}"); +}; + +BinderTest.prototype.testAttributesNoneBound = function() { + var form = html(""); + form.data('scope', new nglr.Scope()); + var binder = new nglr.Binder(form.get(0)); + binder.compile(); + var a = form.find("a"); + assertEquals(a.get(0).nodeName, "A"); + assertTrue(!a.attr("ng-bind-attr")); +}; + +BinderTest.prototype.testExistingAttrbindingIsAppended = function() { + var form = html(""); + form.data('scope', new nglr.Scope()); + var binder = new nglr.Binder(form.get(0)); + binder.compile(); + var a = form.find("a"); + assertEquals('{"b":"{{def}}","href":"http://s/{{abc}}"}', a.attr('ng-bind-attr')); +}; + +BinderTest.prototype.testAttributesAreEvaluated = function(){ + var form = html(''); + form.data('scope', new nglr.Scope({a:1, b:2})); + var binder = new nglr.Binder(form.get(0), null, new MockUrlWatcher()); + binder.compile(); + binder.updateView(); + var a = form.find("a"); + assertEquals(a.attr('a'), 'a'); + assertEquals(a.attr('b'), 'a+b=3'); +}; + +BinderTest.prototype.testInputsAreUpdated = function(){ + var form = + html('' + + ''); - var scope = new nglr.Scope({model:{}}); + var scope = new Scope({model:{}}); form.data('scope', scope); - var binder = new nglr.Binder(form.get(0), new nglr.WidgetFactory(), new MockUrlWatcher()); + var binder = new Binder(form.get(0), new WidgetFactory(), new MockUrlWatcher()); binder.compile(); binder.updateView(); assertEquals(scope.get('model').note, 'abc'); @@ -130,9 +129,9 @@ BinderTest.prototype.testChangingTextareaUpdatesModel = function(){ BinderTest.prototype.testChangingRadioUpdatesModel = function(){ var form = html('' + ''); - var scope = new nglr.Scope({model:{}}); + var scope = new Scope({model:{}}); form.data('scope', scope); - var binder = new nglr.Binder(form.get(0), new nglr.WidgetFactory(), new MockUrlWatcher()); + var binder = new Binder(form.get(0), new WidgetFactory(), new MockUrlWatcher()); binder.compile(); binder.updateView(); assertEquals(scope.get('model').price, 'A'); @@ -140,9 +139,9 @@ BinderTest.prototype.testChangingRadioUpdatesModel = function(){ BinderTest.prototype.testChangingCheckboxUpdatesModel = function(){ var form = html(''); - var scope = new nglr.Scope({model:{}}); + var scope = new Scope({model:{}}); form.data('scope', scope); - var binder = new nglr.Binder(form.get(0), new nglr.WidgetFactory(), new MockUrlWatcher()); + var binder = new Binder(form.get(0), new WidgetFactory(), new MockUrlWatcher()); binder.compile(); binder.updateView(); assertEquals('A', scope.get('model').price); @@ -156,9 +155,9 @@ BinderTest.prototype.testBindUpdate = function() { BinderTest.prototype.testChangingSelectNonSelectedUpdatesModel = function(){ var form = html(''); - var scope = new nglr.Scope({model:{}}); + var scope = new Scope({model:{}}); form.data('scope', scope); - var binder = new nglr.Binder(form.get(0), new nglr.WidgetFactory(), new MockUrlWatcher()); + var binder = new Binder(form.get(0), new WidgetFactory(), new MockUrlWatcher()); binder.compile(); binder.updateView(); assertEquals('A', scope.get('model').price); @@ -170,9 +169,9 @@ BinderTest.prototype.testChangingMultiselectUpdatesModel = function(){ '' + '' + ''); - var scope = new nglr.Scope({Invoice:{}}); + var scope = new Scope({Invoice:{}}); form.data('scope', scope); - var binder = new nglr.Binder(form.get(0), new nglr.WidgetFactory(), new MockUrlWatcher()); + var binder = new Binder(form.get(0), new WidgetFactory(), new MockUrlWatcher()); binder.compile(); binder.updateView(); assertJsonEquals(["A", "B"], scope.get('Invoice').options); @@ -180,9 +179,9 @@ BinderTest.prototype.testChangingMultiselectUpdatesModel = function(){ BinderTest.prototype.testChangingSelectSelectedUpdatesModel = function(){ var form = html(''); - var scope = new nglr.Scope({model:{}}); + var scope = new Scope({model:{}}); form.data('scope', scope); - var binder = new nglr.Binder(form.get(0), new nglr.WidgetFactory(), new MockUrlWatcher()); + var binder = new Binder(form.get(0), new WidgetFactory(), new MockUrlWatcher()); binder.compile(); binder.updateView(); assertEquals(scope.get('model').price, 'b'); @@ -190,18 +189,18 @@ BinderTest.prototype.testChangingSelectSelectedUpdatesModel = function(){ BinderTest.prototype.testExecuteInitialization = function() { var form = html('
'); - var scope = new nglr.Scope(); + var scope = new Scope(); form.data('scope', scope); - var binder = new nglr.Binder(form.get(0)); + var binder = new Binder(form.get(0)); binder.executeInit(); assertEquals(scope.get('a'), 123); }; BinderTest.prototype.testExecuteInitializationStatements = function() { var form = html('
'); - var scope = new nglr.Scope(); + var scope = new Scope(); form.data('scope', scope); - var binder = new nglr.Binder(form.get(0)); + var binder = new Binder(form.get(0)); binder.executeInit(); assertEquals(scope.get('a'), 123); assertEquals(scope.get('b'), 345); @@ -209,9 +208,9 @@ BinderTest.prototype.testExecuteInitializationStatements = function() { BinderTest.prototype.testApplyTextBindings = function(){ var form = html('
x
'); - var scope = new nglr.Scope({model:{a:123}}); + var scope = new Scope({model:{a:123}}); form.data('scope', scope); - var binder = new nglr.Binder(form.get(0), null, new MockUrlWatcher()); + var binder = new Binder(form.get(0), null, new MockUrlWatcher()); binder.compile(); binder.updateView(); assertEquals('123', form.text()); @@ -224,15 +223,15 @@ BinderTest.prototype.testReplaceBindingInTextWithSpan = function() { BinderTest.prototype.testReplaceBindingCreatesCorrectNumberOfWidgets = function() { var h = html("space{{a}}{{a}}a{{a}}{{a}}"); - h.data('scope', new nglr.Scope()); - var binder = new nglr.Binder(h.get(0), new nglr.WidgetFactory()); + h.data('scope', new Scope()); + var binder = new Binder(h.get(0), new WidgetFactory()); binder.compile(); assertEquals(4, h.scope().widgets.length); }; BinderTest.prototype.testBindingSpaceConfusesIE = function() { - if (!nglr.msie) return; + if (!msie) return; var span = document.createElement("span"); span.innerHTML = ' '; var nbsp = span.firstChild.nodeValue; @@ -246,30 +245,30 @@ BinderTest.prototype.testBindingSpaceConfusesIE = function() { BinderTest.prototype.testBindingOfAttributes = function() { var form = html(""); - form.data('scope', new nglr.Scope()); - var binder = new nglr.Binder(form.get(0)); + form.data('scope', new Scope()); + var binder = new Binder(form.get(0)); binder.compile(); var attrbinding = form.find("a").attr("ng-bind-attr"); - var bindings = nglr.fromJson(attrbinding); + var bindings = fromJson(attrbinding); assertEquals("http://s/a{{b}}c", decodeURI(bindings.href)); assertTrue(!bindings.foo); }; BinderTest.prototype.testMarkMultipleAttributes = function() { var form = html(""); - form.data('scope', new nglr.Scope()); - var binder = new nglr.Binder(form.get(0)); + form.data('scope', new Scope()); + var binder = new Binder(form.get(0)); binder.compile(); var attrbinding = form.find("a").attr("ng-bind-attr"); - var bindings = nglr.fromJson(attrbinding); + var bindings = fromJson(attrbinding); assertEquals(decodeURI(bindings.href), "http://s/a{{b}}c"); assertEquals(bindings.foo, "{{d}}"); }; BinderTest.prototype.testAttributesNoneBound = function() { var form = html(""); - form.data('scope', new nglr.Scope()); - var binder = new nglr.Binder(form.get(0)); + form.data('scope', new Scope()); + var binder = new Binder(form.get(0)); binder.compile(); var a = form.find("a"); assertEquals(a.get(0).nodeName, "A"); @@ -278,8 +277,8 @@ BinderTest.prototype.testAttributesNoneBound = function() { BinderTest.prototype.testExistingAttrbindingIsAppended = function() { var form = html(""); - form.data('scope', new nglr.Scope()); - var binder = new nglr.Binder(form.get(0)); + form.data('scope', new Scope()); + var binder = new Binder(form.get(0)); binder.compile(); var a = form.find("a"); assertEquals('{"b":"{{def}}","href":"http://s/{{abc}}"}', a.attr('ng-bind-attr')); @@ -287,8 +286,8 @@ BinderTest.prototype.testExistingAttrbindingIsAppended = function() { BinderTest.prototype.testAttributesAreEvaluated = function(){ var form = html(''); - form.data('scope', new nglr.Scope({a:1, b:2})); - var binder = new nglr.Binder(form.get(0), null, new MockUrlWatcher()); + form.data('scope', new Scope({a:1, b:2})); + var binder = new Binder(form.get(0), null, new MockUrlWatcher()); binder.compile(); binder.updateView(); var a = form.find("a"); @@ -305,8 +304,8 @@ BinderTest.prototype.testInputsAreUpdated = function(){ '' + '' + ''); - var binder = new nglr.Binder(form.get(0), new nglr.WidgetFactory(), new MockUrlWatcher()); - form.data('scope', new nglr.Scope({A:{text:"t1", textarea:"t2", radio:"r", checkbox:"c", select:"S"}})); + var binder = new Binder(form.get(0), new WidgetFactory(), new MockUrlWatcher()); + form.data('scope', new Scope({A:{text:"t1", textarea:"t2", radio:"r", checkbox:"c", select:"S"}})); binder.compile(); binder.updateView(); assertEquals(form.find("input[type=text]").attr('value'), 't1'); @@ -349,7 +348,7 @@ BinderTest.prototype.testButtonElementActionExecutesInScope = function(){ }; BinderTest.prototype.testParseEmptyAnchor = function(){ - var binder = new nglr.Binder(null, null, new MockUrlWatcher()); + var binder = new Binder(null, null, new MockUrlWatcher()); var anchor = binder.anchor; binder.parseAnchor("a#x=1"); assertEquals(1, binder.anchor.x); @@ -360,7 +359,7 @@ BinderTest.prototype.testParseEmptyAnchor = function(){ }; BinderTest.prototype.testParseAnchor = function(){ - var binder = new nglr.Binder(null, null, new MockUrlWatcher()); + var binder = new Binder(null, null, new MockUrlWatcher()); binder.parseAnchor("a#x=1"); assertEquals(binder.anchor.x, "1"); binder.parseAnchor("a#a=b&c=%20&d"); @@ -371,7 +370,7 @@ BinderTest.prototype.testParseAnchor = function(){ }; BinderTest.prototype.testWriteAnchor = function(){ - var binder = new nglr.Binder(null, null, new MockUrlWatcher()); + var binder = new Binder(null, null, new MockUrlWatcher()); binder.urlWatcher.setUrl('a'); binder.anchor.a = 'b'; binder.anchor.c = ' '; @@ -381,9 +380,9 @@ BinderTest.prototype.testWriteAnchor = function(){ }; BinderTest.prototype.testWriteAnchorAsPartOfTheUpdateView = function(){ - var binder = new nglr.Binder(html("
")[0], null, new MockUrlWatcher()); + var binder = new Binder(html("
")[0], null, new MockUrlWatcher()); binder.urlWatcher.setUrl('a'); - $(binder.doc).data('scope', new nglr.Scope()); + $(binder.doc).data('scope', new Scope()); binder.anchor.a = 'b'; binder.updateView(); assertEquals(binder.urlWatcher.getUrl(), "a#a=b"); @@ -391,9 +390,9 @@ BinderTest.prototype.testWriteAnchorAsPartOfTheUpdateView = function(){ BinderTest.prototype.testRepeaterUpdateBindings = function(){ var form = html('
'); - var binder = new nglr.Binder(form.get(0), null, new MockUrlWatcher()); + var binder = new Binder(form.get(0), null, new MockUrlWatcher()); var items = [{a:"A"}, {a:"B"}]; - form.data('scope', new nglr.Scope({model:{items:items}})); + form.data('scope', new Scope({model:{items:items}})); binder.compile(); binder.updateView(); @@ -423,8 +422,8 @@ BinderTest.prototype.testRepeaterUpdateBindings = function(){ BinderTest.prototype.testRepeaterContentDoesNotBind = function(){ var form = html('
'); - form.data('scope', new nglr.Scope({model:{items:[{a:"A"}]}})); - var binder = new nglr.Binder(form.get(0), null, new MockUrlWatcher()); + form.data('scope', new Scope({model:{items:[{a:"A"}]}})); + var binder = new Binder(form.get(0), null, new MockUrlWatcher()); binder.compile(); binder.updateView(); assertEquals('
    ' + @@ -450,9 +449,9 @@ BinderTest.prototype.testRepeaterInputContentDoesNotBind = function(){ var form = html('
    • ' + '
    '); - var binder = new nglr.Binder(form.get(0), null, new MockUrlWatcher()); + var binder = new Binder(form.get(0), null, new MockUrlWatcher()); var items = [{a:"A"}]; - form.data('scope', new nglr.Scope({model:{items:items}})); + form.data('scope', new Scope({model:{items:items}})); assertEquals(form.find(":input").attr("value"), "OLD"); }; @@ -492,9 +491,9 @@ BinderTest.prototype.testDoNotOverwriteCustomAction = function(){ BinderTest.prototype.testReplaceFileUploadWithSwf = function(){ expectAsserts(1); var form = jQuery("body").append('
    '); - form.data('scope', new nglr.Scope()); + form.data('scope', new Scope()); var factory = {}; - var binder = new nglr.Binder(form.get(0), factory, new MockUrlWatcher()); + var binder = new Binder(form.get(0), factory, new MockUrlWatcher()); factory.createController = function(node){ assertEquals(node.attr('type'), 'file'); return {updateModel:function(){}}; @@ -505,8 +504,8 @@ BinderTest.prototype.testReplaceFileUploadWithSwf = function(){ BinderTest.prototype.testRepeaterAdd = function(){ var doc = $('
    '); - var binder = new nglr.Binder(doc[0], new nglr.WidgetFactory(), new MockUrlWatcher()); - doc.data('scope', new nglr.Scope({items:[{x:'a'}, {x:'b'}], $binder:binder})); + var binder = new Binder(doc[0], new WidgetFactory(), new MockUrlWatcher()); + doc.data('scope', new Scope({items:[{x:'a'}, {x:'b'}], $binder:binder})); binder.compile(); binder.updateView(); assertEquals('a', doc.find(':input')[0].value); @@ -520,16 +519,16 @@ BinderTest.prototype.testRepeaterAdd = function(){ BinderTest.prototype.testIfTextBindingThrowsErrorDecorateTheSpan = function(){ var doc = $('
    {{error.throw()}}
    '); - var scope = new nglr.Scope(); + var scope = new Scope(); doc.data('scope', scope); - var binder = new nglr.Binder(doc[0], new nglr.WidgetFactory(), new MockUrlWatcher()); + var binder = new Binder(doc[0], new WidgetFactory(), new MockUrlWatcher()); binder.compile(); scope.set('error.throw', function(){throw "ErrorMsg1";}); binder.updateView(); var span = doc.find('span'); assertTrue(span.hasClass('ng-exception')); - assertEquals('ErrorMsg1', nglr.fromJson(span.text())); + assertEquals('ErrorMsg1', fromJson(span.text())); assertEquals('"ErrorMsg1"', span.attr('ng-error')); scope.set('error.throw', function(){throw "MyError";}); @@ -548,9 +547,9 @@ BinderTest.prototype.testIfTextBindingThrowsErrorDecorateTheSpan = function(){ BinderTest.prototype.testIfAttrBindingThrowsErrorDecorateTheSpan = function(){ var doc = $('
    '); - var scope = new nglr.Scope(); + var scope = new Scope(); doc.data('scope', scope); - var binder = new nglr.Binder(doc[0], new nglr.WidgetFactory(), new MockUrlWatcher()); + var binder = new Binder(doc[0], new WidgetFactory(), new MockUrlWatcher()); binder.compile(); scope.set('error.throw', function(){throw "ErrorMsg";}); @@ -570,9 +569,9 @@ BinderTest.prototype.testNestedRepeater = function() { var doc = html('
    ' + '
      ' + '
      '); - var scope = new nglr.Scope(); + var scope = new Scope(); doc.data('scope', scope); - var binder = new nglr.Binder(doc[0], new nglr.WidgetFactory(), new MockUrlWatcher()); + var binder = new Binder(doc[0], new WidgetFactory(), new MockUrlWatcher()); binder.compile(); scope.set('model', [{name:'a', item:['a1', 'a2']}, {name:'b', item:['b1', 'b2']}]); @@ -594,9 +593,9 @@ BinderTest.prototype.testNestedRepeater = function() { BinderTest.prototype.testRadioButtonGetsPrefixed = function () { var doc = html(''); - var scope = new nglr.Scope(); + var scope = new Scope(); doc.data('scope', scope); - var binder = new nglr.Binder(doc[0], new nglr.WidgetFactory(), new MockUrlWatcher()); + var binder = new Binder(doc[0], new WidgetFactory(), new MockUrlWatcher()); binder.compile(); scope.set('model', ['a1', 'a2']); @@ -611,9 +610,9 @@ BinderTest.prototype.testRadioButtonGetsPrefixed = function () { BinderTest.prototype.testHideBindingExpression = function() { var doc = html('
      '); - var scope = new nglr.Scope(); + var scope = new Scope(); doc.data('scope', scope); - var binder = new nglr.Binder(doc[0], new nglr.WidgetFactory(), new MockUrlWatcher()); + var binder = new Binder(doc[0], new WidgetFactory(), new MockUrlWatcher()); binder.compile(); scope.set('hidden', 3); @@ -629,9 +628,9 @@ BinderTest.prototype.testHideBindingExpression = function() { BinderTest.prototype.testHideBinding = function() { var doc = html('
      '); - var scope = new nglr.Scope(); + var scope = new Scope(); doc.data('scope', scope); - var binder = new nglr.Binder(doc[0], new nglr.WidgetFactory(), new MockUrlWatcher()); + var binder = new Binder(doc[0], new WidgetFactory(), new MockUrlWatcher()); binder.compile(); scope.set('hidden', 'true'); @@ -652,9 +651,9 @@ BinderTest.prototype.testHideBinding = function() { BinderTest.prototype.testShowBinding = function() { var doc = html('
      '); - var scope = new nglr.Scope(); + var scope = new Scope(); doc.data('scope', scope); - var binder = new nglr.Binder(doc[0], new nglr.WidgetFactory(), new MockUrlWatcher()); + var binder = new Binder(doc[0], new WidgetFactory(), new MockUrlWatcher()); binder.compile(); scope.set('show', 'true'); @@ -684,9 +683,9 @@ BinderTest.prototype.testBindClassUndefined = function() { BinderTest.prototype.testBindClass = function() { var doc = html('
      '); - var scope = new nglr.Scope(); + var scope = new Scope(); doc.data('scope', scope); - var binder = new nglr.Binder(doc[0], new nglr.WidgetFactory(), new MockUrlWatcher()); + var binder = new Binder(doc[0], new WidgetFactory(), new MockUrlWatcher()); binder.compile(); scope.set('class', 'testClass'); @@ -713,9 +712,9 @@ BinderTest.prototype.testBindClassEvenOdd = function() { BinderTest.prototype.testBindStyle = function() { var doc = html('
      '); - var scope = new nglr.Scope(); + var scope = new Scope(); doc.data('scope', scope); - var binder = new nglr.Binder(doc[0], new nglr.WidgetFactory(), new MockUrlWatcher()); + var binder = new Binder(doc[0], new WidgetFactory(), new MockUrlWatcher()); binder.compile(); scope.eval('style={color:"red"}'); @@ -734,7 +733,7 @@ BinderTest.prototype.testActionOnAHrefThrowsError = function(){ var state = compile('Add Phone', model); var input = state.node.find('a'); input.click(); - assertEquals('abc', nglr.fromJson(input.attr('ng-error')).a); + assertEquals('abc', fromJson(input.attr('ng-error')).a); assertNotNull(input.data('qtip')); assertTrue("should have an error class", input.hasClass('ng-exception')); @@ -890,7 +889,7 @@ BinderTest.prototype.testItShouldCallListenersWhenAnchorChanges = function() { }; BinderTest.prototype.testParseQueryString = function(){ - var binder = new nglr.Binder(); + var binder = new Binder(); assertJsonEquals({"a":"1"}, binder.parseQueryString("a=1")); assertJsonEquals({"a":"1", "b":"two"}, binder.parseQueryString("a=1&b=two")); assertJsonEquals({}, binder.parseQueryString("")); @@ -905,8 +904,8 @@ BinderTest.prototype.testParseQueryString = function(){ BinderTest.prototype.testSetBinderAnchorTriggersListeners = function(){ expectAsserts(2); var doc = html("
      ")[0]; - var binder = new nglr.Binder(doc, null, new MockUrlWatcher()); - var scope = new nglr.Scope({$binder:binder, $anchor:binder.anchor}); + var binder = new Binder(doc, null, new MockUrlWatcher()); + var scope = new Scope({$binder:binder, $anchor:binder.anchor}); jQuery(doc).data('scope', scope); scope.addWatchListener("$anchor.name", function(newVal, oldVal) { diff --git a/test/ConsoleTest.js b/test/ConsoleTest.js index 56e223bd..f659752f 100644 --- a/test/ConsoleTest.js +++ b/test/ConsoleTest.js @@ -1,13 +1,12 @@ ConsoleTest = TestCase('ConsoleTest'); ConsoleTest.prototype.testConsoleWrite = function(){ - var consoleNode = $("
      ")[0]; - nglr.consoleNode = consoleNode; - nglr.consoleLog("error", ["Hello", "world"]); + consoleNode = $("
      ")[0]; + consoleLog("error", ["Hello", "world"]); assertEquals($(consoleNode)[0].nodeName, 'DIV'); assertEquals($(consoleNode).text(), 'Hello world'); assertEquals($('div', consoleNode)[0].className, 'error'); - nglr.consoleLog("error",["Bye"]); + consoleLog("error",["Bye"]); assertEquals($(consoleNode).text(), 'Hello worldBye'); - nglr.consoleNode = null; + consoleNode = null; }; \ No newline at end of file diff --git a/test/DataStoreTest.js b/test/DataStoreTest.js index 9fe6c3df..2dd4a582 100644 --- a/test/DataStoreTest.js +++ b/test/DataStoreTest.js @@ -11,14 +11,14 @@ DataStoreTest.prototype.testSavePostsToServer = function(){ assertEquals("123", posted.$id); assertEquals("1", posted.$version); assertFalse('function' == typeof posted.save); - response = nglr.fromJson(nglr.toJson(posted)); + response = fromJson(toJson(posted)); response.$entity = "abc"; response.$id = "123"; response.$version = "2"; callback(200, [response]); }; var model; - var datastore = new nglr.DataStore(post); + var datastore = new DataStore(post); model = datastore.entity('abc', {name: "value"})(); model.$id = "123"; model.$version = "1"; @@ -44,7 +44,7 @@ DataStoreTest.prototype.testLoadGetsFromServer = function(){ response = [{$entity:'abc', $id:'1', $version:'2', key:"value"}]; callback(200, response); }; - var datastore = new nglr.DataStore(post); + var datastore = new DataStore(post); var model = datastore.entity("abc", {merge:true})(); assertEquals(datastore.load(model, '1', function(obj){ @@ -72,14 +72,14 @@ DataStoreTest.prototype.testRemove = function(){ assertEquals("123", posted.$id); assertEquals("1", posted.$version); assertFalse('function' == typeof posted.save); - response = nglr.fromJson(nglr.toJson(posted)); + response = fromJson(toJson(posted)); response.$entity = "abc"; response.$id = "123"; response.$version = "2"; callback(200, [response]); }; var model; - var datastore = new nglr.DataStore(post); + var datastore = new DataStore(post); model = datastore.entity('abc', {name: "value"})(); model.$id = "123"; model.$version = "1"; @@ -101,7 +101,7 @@ DataStoreTest.prototype.test401ResponseDoesNotCallCallback = function(){ callback(200, {$status_code: 401}); }; - var datastore = new nglr.DataStore(post, {login:function(){ + var datastore = new DataStore(post, {login:function(){ assertTrue(true); }}); @@ -119,7 +119,7 @@ DataStoreTest.prototype.test403ResponseDoesNotCallCallback = function(){ callback(200, [{$status_code: 403}]); }; - var datastore = new nglr.DataStore(post, {notAuthorized:function(){ + var datastore = new DataStore(post, {notAuthorized:function(){ assertTrue(true); }}); @@ -136,14 +136,14 @@ DataStoreTest.prototype.testLoadCalledWithoutIdShouldBeNoop = function(){ var post = function(url, callback){ assertTrue(false); }; - var datastore = new nglr.DataStore(post); + var datastore = new DataStore(post); var model = datastore.entity("abc")(); assertEquals(datastore.load(model, undefined), model); assertEquals(model.$entity, "abc"); }; DataStoreTest.prototype.testEntityFactory = function(){ - var ds = new nglr.DataStore(); + var ds = new DataStore(); var Recipe = ds.entity("Recipe", {a:1, b:2}); assertEquals(Recipe.title, "Recipe"); assertEquals(Recipe.defaults.a, 1); @@ -161,7 +161,7 @@ DataStoreTest.prototype.testEntityFactory = function(){ }; DataStoreTest.prototype.testEntityFactoryNoDefaults = function(){ - var ds = new nglr.DataStore(); + var ds = new DataStore(); var Recipe = ds.entity("Recipe"); assertEquals(Recipe.title, "Recipe"); @@ -170,7 +170,7 @@ DataStoreTest.prototype.testEntityFactoryNoDefaults = function(){ }; DataStoreTest.prototype.testEntityFactoryWithInitialValues = function(){ - var ds = new nglr.DataStore(); + var ds = new DataStore(); var Recipe = ds.entity("Recipe"); var recipe = Recipe({name: "name"}); @@ -178,7 +178,7 @@ DataStoreTest.prototype.testEntityFactoryWithInitialValues = function(){ }; DataStoreTest.prototype.testEntityLoad = function(){ - var ds = new nglr.DataStore(); + var ds = new DataStore(); var Recipe = ds.entity("Recipe", {a:1, b:2}); ds.load = function(instance, id, callback){ callback.apply(instance); @@ -192,7 +192,7 @@ DataStoreTest.prototype.testEntityLoad = function(){ }; DataStoreTest.prototype.testSaveScope = function(){ - var ds = new nglr.DataStore(); + var ds = new DataStore(); var log = ""; var Person = ds.entity("Person"); var person1 = Person({name:"A", $entity:"Person", $id:"1", $version:"1"}, ds); @@ -215,7 +215,7 @@ DataStoreTest.prototype.testSaveScope = function(){ }; DataStoreTest.prototype.testEntityLoadAllRows = function(){ - var ds = new nglr.DataStore(); + var ds = new DataStore(); var Recipe = ds.entity("Recipe"); var list = []; ds.loadAll = function(entity, callback){ @@ -236,7 +236,7 @@ DataStoreTest.prototype.testLoadAll = function(){ assertEquals("A", data[0][1]); callback(200, [[{$entity:'A', $id:'1'},{$entity:'A', $id:'2'}]]); }; - var datastore = new nglr.DataStore(post); + var datastore = new DataStore(post); var list = datastore.entity("A").all(function(){ assertTrue(true); }); @@ -256,7 +256,7 @@ DataStoreTest.prototype.testQuery = function(){ callback(200, [[{$entity:"Employee", $id: "456", managerId: "123ABC"}]]); }; - var datastore = new nglr.DataStore(post); + var datastore = new DataStore(post); var Employee = datastore.entity("Employee"); var list = Employee.query('managerId', "123abc", function(){ assertTrue(true); @@ -269,7 +269,7 @@ DataStoreTest.prototype.testQuery = function(){ DataStoreTest.prototype.testLoadingDocumentRefreshesExistingArrays = function() { expectAsserts(12); var post; - var datastore = new nglr.DataStore(function(r, c){post(r,c);}); + var datastore = new DataStore(function(r, c){post(r,c);}); var Book = datastore.entity('Book'); post = function(req, callback) { callback(200, [[{$id:1, $entity:"Book", name:"Moby"}, @@ -285,7 +285,7 @@ DataStoreTest.prototype.testLoadingDocumentRefreshesExistingArrays = function() assertEquals("Dick", queryBooks[1].name); post = function(req, callback) { - assertEquals('[["GET","Book/1"]]', nglr.toJson(req)); + assertEquals('[["GET","Book/1"]]', toJson(req)); callback(200, [{$id:1, $entity:"Book", name:"Moby Dick"}]); }; var book = Book.load(1); @@ -307,7 +307,7 @@ DataStoreTest.prototype.testLoadingDocumentRefreshesExistingArrays = function() DataStoreTest.prototype.testEntityProperties = function() { expectAsserts(2); - var datastore = new nglr.DataStore(); + var datastore = new DataStore(); var callback = {}; datastore._jsonRequest = function(request, callbackFn) { @@ -322,11 +322,11 @@ DataStoreTest.prototype.testEntityProperties = function() { DataStoreTest.prototype.testLoadInstanceIsNotFromCache = function() { var post; - var datastore = new nglr.DataStore(function(r, c){post(r,c);}); + var datastore = new DataStore(function(r, c){post(r,c);}); var Book = datastore.entity('Book'); post = function(req, callback) { - assertEquals('[["GET","Book/1"]]', nglr.toJson(req)); + assertEquals('[["GET","Book/1"]]', toJson(req)); callback(200, [{$id:1, $entity:"Book", name:"Moby Dick"}]); }; var book = Book.load(1); @@ -336,14 +336,14 @@ DataStoreTest.prototype.testLoadInstanceIsNotFromCache = function() { }; DataStoreTest.prototype.testLoadStarsIsNewDocument = function() { - var datastore = new nglr.DataStore(); + var datastore = new DataStore(); var Book = datastore.entity('Book'); var book = Book.load('*'); assertEquals('Book', book.$entity); }; DataStoreTest.prototype.testUndefinedEntityReturnsNullValueObject = function() { - var datastore = new nglr.DataStore(); + var datastore = new DataStore(); var Entity = datastore.entity(undefined); var all = Entity.all(); assertEquals(0, all.length); @@ -355,7 +355,7 @@ DataStoreTest.prototype.testFetchEntities = function(){ assertJsonEquals(["GET", "$entities"], data[0]); callback(200, [{A:0, B:0}]); }; - var datastore = new nglr.DataStore(post); + var datastore = new DataStore(post); var entities = datastore.entities(function(){ assertTrue(true); }); @@ -367,20 +367,20 @@ DataStoreTest.prototype.testFetchEntities = function(){ }; DataStoreTest.prototype.testItShouldMigrateSchema = function() { - var datastore = new nglr.DataStore(); + var datastore = new DataStore(); var Entity = datastore.entity("Entity", {a:[], user:{name:"Misko", email:""}}); var doc = Entity().$loadFrom({b:'abc', user:{email:"misko@hevery.com"}}); assertFalse( - nglr.toJson({a:[], b:'abc', user:{name:"Misko", email:"misko@hevery.com"}}) == - nglr.toJson(doc)); + toJson({a:[], b:'abc', user:{name:"Misko", email:"misko@hevery.com"}}) == + toJson(doc)); doc.$migrate(); assertEquals( - nglr.toJson({a:[], b:'abc', user:{name:"Misko", email:"misko@hevery.com"}}), - nglr.toJson(doc)); + toJson({a:[], b:'abc', user:{name:"Misko", email:"misko@hevery.com"}}), + toJson(doc)); }; DataStoreTest.prototype.testItShouldCollectRequestsForBulk = function() { - var ds = new nglr.DataStore(); + var ds = new DataStore(); var Book = ds.entity("Book"); var Library = ds.entity("Library"); Book.all(); @@ -391,7 +391,7 @@ DataStoreTest.prototype.testItShouldCollectRequestsForBulk = function() { }; DataStoreTest.prototype.testEmptyFlushShouldDoNothing = function () { - var ds = new nglr.DataStore(function(){ + var ds = new DataStore(function(){ fail("expecting noop"); }); ds.flush(); @@ -400,17 +400,17 @@ DataStoreTest.prototype.testEmptyFlushShouldDoNothing = function () { DataStoreTest.prototype.testFlushShouldCallAllCallbacks = function() { var log = ""; function post(request, callback){ - log += 'BulkRequest:' + nglr.toJson(request) + ';'; + log += 'BulkRequest:' + toJson(request) + ';'; callback(200, [[{$id:'ABC'}], {$id:'XYZ'}]); } - var ds = new nglr.DataStore(post); + var ds = new DataStore(post); var Book = ds.entity("Book"); var Library = ds.entity("Library"); Book.all(function(instance){ - log += nglr.toJson(instance) + ';'; + log += toJson(instance) + ';'; }); Library.load("123", function(instance){ - log += nglr.toJson(instance) + ';'; + log += toJson(instance) + ';'; }); assertEquals("", log); ds.flush(); @@ -421,7 +421,7 @@ DataStoreTest.prototype.testFlushShouldCallAllCallbacks = function() { DataStoreTest.prototype.testSaveOnNotLoggedInRetriesAfterLoggin = function(){ var log = ""; var book; - var ds = new nglr.DataStore(null, {login:function(c){c();}}); + var ds = new DataStore(null, {login:function(c){c();}}); ds.post = function (request, callback){ assertJsonEquals([["POST", "", book]], request); ds.post = function(request, callback){ @@ -439,7 +439,7 @@ DataStoreTest.prototype.testSaveOnNotLoggedInRetriesAfterLoggin = function(){ DataStoreTest.prototype.testItShouldRemoveItemFromCollectionWhenDeleted = function() { expectAsserts(6); - var ds = new nglr.DataStore(); + var ds = new DataStore(); ds.post = function(request, callback){ assertJsonEquals([["GET", "Book"]], request); callback(200, [[{name:"Moby Dick", $id:123, $entity:'Book'}]]); @@ -462,7 +462,7 @@ DataStoreTest.prototype.testItShouldRemoveItemFromCollectionWhenDeleted = functi DataStoreTest.prototype.testItShouldAddToAll = function() { expectAsserts(8); - var ds = new nglr.DataStore(); + var ds = new DataStore(); ds.post = function(request, callback){ assertJsonEquals([["GET", "Book"]], request); callback(200, [[]]); @@ -490,7 +490,7 @@ DataStoreTest.prototype.testItShouldAddToAll = function() { DataStoreTest.prototype.testItShouldReturnCreatedDocumentCountByUser = function(){ expectAsserts(2); - var datastore = new nglr.DataStore( + var datastore = new DataStore( function(request, callback){ assertJsonEquals([["GET", "$users"]], request); callback(200, [{misko:1, adam:1}]); @@ -502,7 +502,7 @@ DataStoreTest.prototype.testItShouldReturnCreatedDocumentCountByUser = function( DataStoreTest.prototype.testItShouldReturnDocumentIdsForUeserByEntity = function(){ expectAsserts(2); - var datastore = new nglr.DataStore( + var datastore = new DataStore( function(request, callback){ assertJsonEquals([["GET", "$users/misko@hevery.com"]], request); callback(200, [{Book:["1"], Library:["2"]}]); @@ -514,7 +514,7 @@ DataStoreTest.prototype.testItShouldReturnDocumentIdsForUeserByEntity = function DataStoreTest.prototype.testItShouldReturnNewInstanceOn404 = function(){ expectAsserts(7); var log = ""; - var datastore = new nglr.DataStore( + var datastore = new DataStore( function(request, callback){ assertJsonEquals([["GET", "User/misko"]], request); callback(200, [{$status_code:404}]); @@ -532,13 +532,13 @@ DataStoreTest.prototype.testItShouldReturnNewInstanceOn404 = function(){ DataStoreTest.prototype.testItShouldReturnNewInstanceOn404 = function(){ var log = ""; - var datastore = new nglr.DataStore( + var datastore = new DataStore( function(request, callback){ assertJsonEquals([["GET", "User/misko"],["GET", "User/adam"]], request); callback(200, [{$id:'misko'},{$id:'adam'}]); }); var User = datastore.entity("User"); - var users = User.loadMany(['misko', 'adam'], function(i){log+="cb "+nglr.toJson(i)+";";}); + var users = User.loadMany(['misko', 'adam'], function(i){log+="cb "+toJson(i)+";";}); datastore.flush(); assertEquals("misko", users[0].$id); assertEquals("adam", users[1].$id); @@ -546,7 +546,7 @@ DataStoreTest.prototype.testItShouldReturnNewInstanceOn404 = function(){ }; DataStoreTest.prototype.testItShouldCreateJoinAndQuery = function() { - var datastore = new nglr.DataStore(); + var datastore = new DataStore(); var Invoice = datastore.entity("Invoice"); var Customer = datastore.entity("Customer"); var InvoiceWithCustomer = datastore.join({ @@ -568,7 +568,7 @@ DataStoreTest.prototype.testItShouldCreateJoinAndQuery = function() { }; DataStoreTest.prototype.testItShouldThrowIfMoreThanOneEntityIsPrimary = function() { - var datastore = new nglr.DataStore(); + var datastore = new DataStore(); var Invoice = datastore.entity("Invoice"); var Customer = datastore.entity("Customer"); assertThrows("Exactly one entity needs to be primary.", function(){ @@ -580,7 +580,7 @@ DataStoreTest.prototype.testItShouldThrowIfMoreThanOneEntityIsPrimary = function }; DataStoreTest.prototype.testItShouldThrowIfLoopInReferences = function() { - var datastore = new nglr.DataStore(); + var datastore = new DataStore(); var Invoice = datastore.entity("Invoice"); var Customer = datastore.entity("Customer"); assertThrows("Infinite loop in join: invoice -> customer", function(){ @@ -592,7 +592,7 @@ DataStoreTest.prototype.testItShouldThrowIfLoopInReferences = function() { }; DataStoreTest.prototype.testItShouldThrowIfReferenceToNonExistantJoin = function() { - var datastore = new nglr.DataStore(); + var datastore = new DataStore(); var Invoice = datastore.entity("Invoice"); var Customer = datastore.entity("Customer"); assertThrows("Named entity 'x' is undefined.", function(){ @@ -604,7 +604,7 @@ DataStoreTest.prototype.testItShouldThrowIfReferenceToNonExistantJoin = function }; DataStoreTest.prototype.testItShouldThrowIfQueryOnNonPrimary = function() { - var datastore = new nglr.DataStore(); + var datastore = new DataStore(); var Invoice = datastore.entity("Invoice"); var Customer = datastore.entity("Customer"); var InvoiceWithCustomer = datastore.join({ diff --git a/test/EntityDeclarationTest.js b/test/EntityDeclarationTest.js index 5cab90f4..d64dd775 100644 --- a/test/EntityDeclarationTest.js +++ b/test/EntityDeclarationTest.js @@ -2,7 +2,7 @@ EntityDeclarationTest = TestCase('EntityDeclarationTest'); EntityDeclarationTest.prototype.testEntityTypeOnly = function(){ expectAsserts(2); - var scope = new nglr.Scope({$datastore:{entity:function(name){ + var scope = new Scope({$datastore:{entity:function(name){ assertEquals("Person", name); }}}); var init = scope.entity("Person"); @@ -11,7 +11,7 @@ EntityDeclarationTest.prototype.testEntityTypeOnly = function(){ EntityDeclarationTest.prototype.testWithDefaults = function(){ expectAsserts(4); - var scope = new nglr.Scope({$datastore:{entity:function(name, init){ + var scope = new Scope({$datastore:{entity:function(name, init){ assertEquals("Person", name); assertEquals("=a:", init.a); assertEquals(0, init.b.length); @@ -22,7 +22,7 @@ EntityDeclarationTest.prototype.testWithDefaults = function(){ EntityDeclarationTest.prototype.testWithName = function(){ expectAsserts(2); - var scope = new nglr.Scope({$datastore:{entity:function(name, init){ + var scope = new Scope({$datastore:{entity:function(name, init){ assertEquals("Person", name); return function (){ return {}; }; }}}); @@ -34,7 +34,7 @@ EntityDeclarationTest.prototype.testMultipleEntities = function(){ expectAsserts(3); var expect = ['Person', 'Book']; var i=0; - var scope = new nglr.Scope({$datastore:{entity:function(name, init){ + var scope = new Scope({$datastore:{entity:function(name, init){ assertEquals(expect[i], name); i++; return function (){ return {}; }; diff --git a/test/FileControllerTest.js b/test/FileControllerTest.js index ca5925e4..09eb6fc5 100644 --- a/test/FileControllerTest.js +++ b/test/FileControllerTest.js @@ -3,7 +3,7 @@ FileControllerTest = TestCase('FileControllerTest'); FileControllerTest.prototype.testOnSelectUpdateView = function(){ var view = jQuery(''); var swf = {}; - var controller = new nglr.FileController(view, null, swf); + var controller = new FileController(view, null, swf); swf.uploadFile = function(path){}; controller._on_select('A', 9, '9 bytes'); assertEquals(view.find('a').text(), "A"); @@ -11,14 +11,14 @@ FileControllerTest.prototype.testOnSelectUpdateView = function(){ }; FileControllerTest.prototype.testUpdateModelView = function(){ - var view = nglr.FileController.template(''); + var view = FileController.template(''); var input = $(''); var controller; - var scope = new nglr.Scope({value:{}, $binder:{updateView:function(){ + var scope = new Scope({value:{}, $binder:{updateView:function(){ controller.updateView(scope); }}}); view.data('scope', scope); - controller = new nglr.FileController(view, 'value.input', null, "http://server_base"); + controller = new FileController(view, 'value.input', null, "http://server_base"); var value = '{"text":"A", "size":123, "id":"890"}'; controller._on_uploadCompleteData(value); controller.updateView(scope); @@ -34,7 +34,7 @@ FileControllerTest.prototype.testUpdateModelView = function(){ FileControllerTest.prototype.testFileUpload = function(){ expectAsserts(1); var swf = {}; - var controller = new nglr.FileController(null, null, swf, "http://server_base"); + var controller = new FileController(null, null, swf, "http://server_base"); swf.uploadFile = function(path){ assertEquals("http://server_base/_attachments", path); }; @@ -47,16 +47,16 @@ FileControllerTest.prototype.testFileUploadNoFileIsNoop = function(){ var swf = {uploadFile:function(path){ fail(); }}; - var controller = new nglr.FileController(null, swf); + var controller = new FileController(null, swf); controller.upload("basePath", null); }; FileControllerTest.prototype.testRemoveAttachment = function(){ - var doc = nglr.FileController.template(); + var doc = FileController.template(); var input = $(''); - var scope = new nglr.Scope(); + var scope = new Scope(); input.data('scope', scope); - var controller = new nglr.FileController(doc, 'file', null, null); + var controller = new FileController(doc, 'file', null, null); controller.updateView(scope); assertEquals(false, doc.find('input').attr('checked')); @@ -75,10 +75,10 @@ FileControllerTest.prototype.testRemoveAttachment = function(){ }; FileControllerTest.prototype.testShouldEmptyOutOnUndefined = function () { - var view = nglr.FileController.template('hello'); - var controller = new nglr.FileController(view, 'abc', null, null); + var view = FileController.template('hello'); + var controller = new FileController(view, 'abc', null, null); - var scope = new nglr.Scope(); + var scope = new Scope(); scope.set('abc', {text: 'myname', url: 'myurl', size: 1234}); controller.updateView(scope); diff --git a/test/FiltersTest.js b/test/FiltersTest.js index 8943fdd4..c219f24f 100644 --- a/test/FiltersTest.js +++ b/test/FiltersTest.js @@ -3,7 +3,7 @@ FiltersTest = TestCase('FiltersTest'); FiltersTest.prototype.testCurrency = function(){ var html = $(''); var context = {element:html[0]}; - var currency = nglr.bind(context, angular.filter.currency); + var currency = bind(context, angular.filter.currency); assertEquals(currency(0), '$0.00'); assertEquals(html.hasClass('ng-format-negative'), false); @@ -15,8 +15,8 @@ FiltersTest.prototype.testCurrency = function(){ FiltersTest.prototype.testFilterThisIsContext = function(){ expectAsserts(2); - var scope = new nglr.Scope(); - nglr.Scope.expressionCache = {}; + var scope = new Scope(); + Scope.expressionCache = {}; var context = {element:123}; angular.filter.testFn = function () { assertEquals('Context not equal', this, context); @@ -28,7 +28,7 @@ FiltersTest.prototype.testFilterThisIsContext = function(){ FiltersTest.prototype.testNumberFormat = function(){ var context = {jqElement:$('')}; - var number = nglr.bind(context, angular.filter.number); + var number = bind(context, angular.filter.number); assertEquals('0', number(0, 0)); assertEquals('0.00', number(0)); @@ -40,7 +40,7 @@ FiltersTest.prototype.testNumberFormat = function(){ }; FiltersTest.prototype.testJson = function () { - assertEquals(nglr.toJson({a:"b"}, true), angular.filter.json({a:"b"})); + assertEquals(toJson({a:"b"}, true), angular.filter.json({a:"b"})); }; FiltersTest.prototype.testPackageTracking = function () { @@ -48,9 +48,9 @@ FiltersTest.prototype.testPackageTracking = function () { var val = angular.filter.trackPackage(trackingNo, title); assertNotNull("Did Not Match: " + trackingNo, val); assertEquals(angular.filter.Meta.TAG, val.TAG); - assertEquals(title + ": " + nglr.trim(trackingNo), val.text); + assertEquals(title + ": " + trim(trackingNo), val.text); assertNotNull(val.url); - assertEquals(nglr.trim(trackingNo), val.trackingNo); + assertEquals(trim(trackingNo), val.trackingNo); assertEquals('' + val.text + '', val.html); }; assert('UPS', ' 1Z 999 999 99 9999 999 9 '); @@ -83,7 +83,7 @@ FiltersTest.prototype.testLink = function() { }; FiltersTest.prototype.testBytes = function(){ - var controller = new nglr.FileController(); + var controller = new FileController(); assertEquals(angular.filter.bytes(123), '123 bytes'); assertEquals(angular.filter.bytes(1234), '1.2 KB'); assertEquals(angular.filter.bytes(1234567), '1.1 MB'); diff --git a/test/JsonTest.js b/test/JsonTest.js index 5c3644f5..cf49bec3 100644 --- a/test/JsonTest.js +++ b/test/JsonTest.js @@ -1,69 +1,69 @@ JsonTest = TestCase("JsonTest"); JsonTest.prototype.testPrimitives = function () { - assertEquals("null", nglr.toJson(0/0)); - assertEquals("null", nglr.toJson(null)); - assertEquals("true", nglr.toJson(true)); - assertEquals("false", nglr.toJson(false)); - assertEquals("123.45", nglr.toJson(123.45)); - assertEquals('"abc"', nglr.toJson("abc")); - assertEquals('"a \\t \\n \\r b \\\\"', nglr.toJson("a \t \n \r b \\")); + assertEquals("null", toJson(0/0)); + assertEquals("null", toJson(null)); + assertEquals("true", toJson(true)); + assertEquals("false", toJson(false)); + assertEquals("123.45", toJson(123.45)); + assertEquals('"abc"', toJson("abc")); + assertEquals('"a \\t \\n \\r b \\\\"', toJson("a \t \n \r b \\")); }; JsonTest.prototype.testEscaping = function () { - assertEquals("\"7\\\\\\\"7\"", nglr.toJson("7\\\"7")); + assertEquals("\"7\\\\\\\"7\"", toJson("7\\\"7")); }; JsonTest.prototype.testObjects = function () { - assertEquals('{"a":1,"b":2}', nglr.toJson({a:1,b:2})); - assertEquals('{"a":{"b":2}}', nglr.toJson({a:{b:2}})); - assertEquals('{"a":{"b":{"c":0}}}', nglr.toJson({a:{b:{c:0}}})); - assertEquals('{"a":{"b":null}}', nglr.toJson({a:{b:0/0}})); + assertEquals('{"a":1,"b":2}', toJson({a:1,b:2})); + assertEquals('{"a":{"b":2}}', toJson({a:{b:2}})); + assertEquals('{"a":{"b":{"c":0}}}', toJson({a:{b:{c:0}}})); + assertEquals('{"a":{"b":null}}', toJson({a:{b:0/0}})); }; JsonTest.prototype.testObjectPretty = function () { - assertEquals('{\n "a":1,\n "b":2}', nglr.toJson({a:1,b:2}, true)); - assertEquals('{\n "a":{\n "b":2}}', nglr.toJson({a:{b:2}}, true)); + assertEquals('{\n "a":1,\n "b":2}', toJson({a:1,b:2}, true)); + assertEquals('{\n "a":{\n "b":2}}', toJson({a:{b:2}}, true)); }; JsonTest.prototype.testArray = function () { - assertEquals('[]', nglr.toJson([])); - assertEquals('[1,"b"]', nglr.toJson([1,"b"])); + assertEquals('[]', toJson([])); + assertEquals('[1,"b"]', toJson([1,"b"])); }; JsonTest.prototype.testIgnoreFunctions = function () { - assertEquals('[null,1]', nglr.toJson([function(){},1])); - assertEquals('{}', nglr.toJson({a:function(){}})); + assertEquals('[null,1]', toJson([function(){},1])); + assertEquals('{}', toJson({a:function(){}})); }; JsonTest.prototype.testParseNull = function () { - assertNull(nglr.fromJson("null")); + assertNull(fromJson("null")); }; JsonTest.prototype.testParseBoolean = function () { - assertTrue(nglr.fromJson("true")); - assertFalse(nglr.fromJson("false")); + assertTrue(fromJson("true")); + assertFalse(fromJson("false")); }; JsonTest.prototype.test$$isIgnored = function () { - assertEquals("{}", nglr.toJson({$$:0})); + assertEquals("{}", toJson({$$:0})); }; JsonTest.prototype.testArrayWithEmptyItems = function () { var a = []; a[1] = "X"; - assertEquals('[null,"X"]', nglr.toJson(a)); + assertEquals('[null,"X"]', toJson(a)); }; JsonTest.prototype.testItShouldEscapeUnicode = function () { assertEquals(1, "\u00a0".length); - assertEquals(8, nglr.toJson("\u00a0").length); - assertEquals(1, nglr.fromJson(nglr.toJson("\u00a0")).length); + assertEquals(8, toJson("\u00a0").length); + assertEquals(1, fromJson(toJson("\u00a0")).length); }; JsonTest.prototype.testItShouldUTCDates = function() { var date = angular.String.toDate("2009-10-09T01:02:03Z"); - assertEquals('"2009-10-09T01:02:03Z"', nglr.toJson(date)); + assertEquals('"2009-10-09T01:02:03Z"', toJson(date)); assertEquals(date.getTime(), - nglr.fromJson('"2009-10-09T01:02:03Z"').getTime()); + fromJson('"2009-10-09T01:02:03Z"').getTime()); }; diff --git a/test/LoaderTest.js b/test/LoaderTest.js index 91a804a5..88ae3efa 100644 --- a/test/LoaderTest.js +++ b/test/LoaderTest.js @@ -3,7 +3,7 @@ LoaderTest = TestCase('LoaderTest'); LoaderTest.prototype.testLoadCss = function(){ if ($.browser.safari) return; var head = jQuery('')[0]; - var loader = new nglr.Loader(document, head, {}); + var loader = new Loader(document, head, {}); var log = ''; loader.config.server = 'http://'; loader.loadCss('x'); @@ -11,15 +11,15 @@ LoaderTest.prototype.testLoadCss = function(){ }; LoaderTest.prototype.testDefaultDatabasePathFromSubdomain = function() { - var loader = new nglr.Loader(null, null, {server:"http://account.getangular.com", database:"database"}); + var loader = new Loader(null, null, {server:"http://account.getangular.com", database:"database"}); loader.computeConfiguration(); assertEquals("database", loader.config.database); - loader = new nglr.Loader(null, null, {server:"http://account.getangular.com"}); + loader = new Loader(null, null, {server:"http://account.getangular.com"}); loader.computeConfiguration(); assertEquals("account", loader.config.database); - loader = new nglr.Loader(null, null, {server:"https://account.getangular.com"}); + loader = new Loader(null, null, {server:"https://account.getangular.com"}); loader.computeConfiguration(); assertEquals("account", loader.config.database); }; @@ -31,7 +31,7 @@ UrlWatcherTest = TestCase('UrlWatcherTest'); UrlWatcherTest.prototype.testUrlWatcher = function () { expectAsserts(2); var location = {href:"http://server", hash:""}; - var watcher = new nglr.UrlWatcher(location); + var watcher = new UrlWatcher(location); watcher.delay = 1; watcher.listener = function(url){ assertEquals('http://getangular.test', url); @@ -49,9 +49,9 @@ UrlWatcherTest.prototype.testUrlWatcher = function () { UrlWatcherTest.prototype.testItShouldFireOnUpdateEventWhenSpecialURLSet = function(){ expectAsserts(2); var location = {href:"http://server", hash:"#$iframe_notify=1234"}; - var watcher = new nglr.UrlWatcher(location); - nglr._iframe_notify_1234 = function () { - assertEquals("undefined", typeof nglr._iframe_notify_1234); + var watcher = new UrlWatcher(location); + callbacks._iframe_notify_1234 = function () { + assertEquals("undefined", typeof callbacks._iframe_notify_1234); assertEquals("http://server2#", location.href); }; watcher.delay = 1; @@ -66,5 +66,5 @@ UrlWatcherTest.prototype.testItShouldFireOnUpdateEventWhenSpecialURLSet = functi FunctionTest = TestCase("FunctionTest"); FunctionTest.prototype.testEscapeHtml = function () { - assertEquals("<div>&amp;</div>", nglr.escapeHtml('
      &
      ')); + assertEquals("<div>&amp;</div>", escapeHtml('
      &
      ')); }; \ No newline at end of file diff --git a/test/ModelTest.js b/test/ModelTest.js index 5d9119a1..dbd97778 100644 --- a/test/ModelTest.js +++ b/test/ModelTest.js @@ -1,7 +1,7 @@ ModelTest = TestCase('ModelTest'); ModelTest.prototype.testLoadSaveOperations = function(){ - var m1 = new nglr.DataStore().entity('A')(); + var m1 = new DataStore().entity('A')(); m1.a = 1; var m2 = {b:1}; @@ -13,7 +13,7 @@ ModelTest.prototype.testLoadSaveOperations = function(){ }; ModelTest.prototype.testLoadfromDoesNotClobberFunctions = function(){ - var m1 = new nglr.DataStore().entity('A')(); + var m1 = new DataStore().entity('A')(); m1.id = function(){return 'OK';}; m1.$loadFrom({id:null}); assertEquals(m1.id(), 'OK'); @@ -24,7 +24,7 @@ ModelTest.prototype.testLoadfromDoesNotClobberFunctions = function(){ }; ModelTest.prototype.testDataStoreDoesNotGetClobbered = function(){ - var ds = new nglr.DataStore(); + var ds = new DataStore(); var m = ds.entity('A')(); assertTrue(m.$$entity.datastore === ds); m.$loadFrom({}); @@ -33,7 +33,7 @@ ModelTest.prototype.testDataStoreDoesNotGetClobbered = function(){ ModelTest.prototype.testManagedModelDelegatesMethodsToDataStore = function(){ expectAsserts(7); - var datastore = new nglr.DataStore(); + var datastore = new DataStore(); var model = datastore.entity("A", {a:1})(); var fn = {}; datastore.save = function(instance, callback) { @@ -56,7 +56,7 @@ ModelTest.prototype.testManagedModelDelegatesMethodsToDataStore = function(){ ModelTest.prototype.testManagedModelCanBeForcedToFlush = function(){ expectAsserts(6); - var datastore = new nglr.DataStore(); + var datastore = new DataStore(); var model = datastore.entity("A", {a:1})(); datastore.save = function(instance, callback) { @@ -77,7 +77,7 @@ ModelTest.prototype.testManagedModelCanBeForcedToFlush = function(){ ModelTest.prototype.testItShouldMakeDeepCopyOfInitialValues = function (){ var initial = {a:[]}; - var entity = new nglr.DataStore().entity("A", initial); + var entity = new DataStore().entity("A", initial); var model = entity(); model.a.push(1); assertEquals(0, entity().a.length); diff --git a/test/ParserTest.js b/test/ParserTest.js index 7fe8e6a4..058010f3 100644 --- a/test/ParserTest.js +++ b/test/ParserTest.js @@ -1,7 +1,7 @@ LexerTest = TestCase('LexerTest'); LexerTest.prototype.testTokenizeAString = function(){ - var lexer = new nglr.Lexer("a.bc[22]+1.3|f:'a\\\'c':\"d\\\"e\""); + var lexer = new Lexer("a.bc[22]+1.3|f:'a\\\'c':\"d\\\"e\""); var tokens = lexer.parse(); var i = 0; assertEquals(tokens[i].index, 0); @@ -54,7 +54,7 @@ LexerTest.prototype.testTokenizeAString = function(){ LexerTest.prototype.testTokenizeRegExp = function(){ - var lexer = new nglr.Lexer("/r 1/"); + var lexer = new Lexer("/r 1/"); var tokens = lexer.parse(); var i = 0; assertEquals(tokens[i].index, 0); @@ -64,7 +64,7 @@ LexerTest.prototype.testTokenizeRegExp = function(){ LexerTest.prototype.testQuotedString = function(){ var str = "['\\'', \"\\\"\"]"; - var lexer = new nglr.Lexer(str); + var lexer = new Lexer(str); var tokens = lexer.parse(); assertEquals(1, tokens[1].index); @@ -77,21 +77,21 @@ LexerTest.prototype.testQuotedString = function(){ LexerTest.prototype.testQuotedStringEscape = function(){ var str = '"\\"\\n\\f\\r\\t\\v\\u00A0"'; - var lexer = new nglr.Lexer(str); + var lexer = new Lexer(str); var tokens = lexer.parse(); assertEquals('"\n\f\r\t\v\u00A0', tokens[0].text); }; LexerTest.prototype.testTokenizeUnicode = function(){ - var lexer = new nglr.Lexer('"\\u00A0"'); + var lexer = new Lexer('"\\u00A0"'); var tokens = lexer.parse(); assertEquals(1, tokens.length); assertEquals('\u00a0', tokens[0].text); }; LexerTest.prototype.testTokenizeRegExpWithOptions = function(){ - var lexer = new nglr.Lexer("/r/g"); + var lexer = new Lexer("/r/g"); var tokens = lexer.parse(); var i = 0; assertEquals(tokens[i].index, 0); @@ -101,7 +101,7 @@ LexerTest.prototype.testTokenizeRegExpWithOptions = function(){ }; LexerTest.prototype.testTokenizeRegExpWithEscape = function(){ - var lexer = new nglr.Lexer("/\\/\\d/"); + var lexer = new Lexer("/\\/\\d/"); var tokens = lexer.parse(); var i = 0; assertEquals(tokens[i].index, 0); @@ -110,14 +110,14 @@ LexerTest.prototype.testTokenizeRegExpWithEscape = function(){ }; LexerTest.prototype.testIgnoreWhitespace = function(){ - var lexer = new nglr.Lexer("a \t \n \r b"); + var lexer = new Lexer("a \t \n \r b"); var tokens = lexer.parse(); assertEquals(tokens[0].text, 'a'); assertEquals(tokens[1].text, 'b'); }; LexerTest.prototype.testRelation = function(){ - var lexer = new nglr.Lexer("! == != < > <= >="); + var lexer = new Lexer("! == != < > <= >="); var tokens = lexer.parse(); assertEquals(tokens[0].text, '!'); assertEquals(tokens[1].text, '=='); @@ -129,7 +129,7 @@ LexerTest.prototype.testRelation = function(){ }; LexerTest.prototype.testStatements = function(){ - var lexer = new nglr.Lexer("a;b;"); + var lexer = new Lexer("a;b;"); var tokens = lexer.parse(); assertEquals(tokens[0].text, 'a'); assertEquals(tokens[1].text, ';'); @@ -140,7 +140,7 @@ LexerTest.prototype.testStatements = function(){ ParserTest = TestCase('ParserTest'); ParserTest.prototype.testExpressions = function(){ - var scope = new nglr.Scope(); + var scope = new Scope(); assertEquals(scope.eval("-1"), -1); assertEquals(scope.eval("1 + 2.5"), 3.5); assertEquals(scope.eval("1 + -2.5"), -1.5); @@ -151,7 +151,7 @@ ParserTest.prototype.testExpressions = function(){ }; ParserTest.prototype.testComparison = function(){ - var scope = new nglr.Scope(); + var scope = new Scope(); assertEquals(scope.eval("false"), false); assertEquals(scope.eval("!true"), false); assertEquals(scope.eval("1==1"), true); @@ -163,14 +163,14 @@ ParserTest.prototype.testComparison = function(){ }; ParserTest.prototype.testLogical = function(){ - var scope = new nglr.Scope(); + var scope = new Scope(); assertEquals(scope.eval("0&&2"), 0&&2); assertEquals(scope.eval("0||2"), 0||2); assertEquals(scope.eval("0||1&&2"), 0||1&&2); }; ParserTest.prototype.testString = function(){ - var scope = new nglr.Scope(); + var scope = new Scope(); assertEquals(scope.eval("'a' + 'b c'"), "ab c"); }; @@ -182,7 +182,7 @@ ParserTest.prototype.testFilters = function(){ angular.filter.upper = {_case:function(input) { return input.toUpperCase(); }}; - var scope = new nglr.Scope(); + var scope = new Scope(); try { scope.eval("1|nonExistant"); fail(); @@ -196,7 +196,7 @@ ParserTest.prototype.testFilters = function(){ }; ParserTest.prototype.testScopeAccess = function(){ - var scope = new nglr.Scope(); + var scope = new Scope(); scope.set('a', 123); scope.set('b.c', 456); assertEquals(scope.eval("a", scope), 123); @@ -205,16 +205,16 @@ ParserTest.prototype.testScopeAccess = function(){ }; ParserTest.prototype.testGrouping = function(){ - var scope = new nglr.Scope(); + var scope = new Scope(); assertEquals(scope.eval("(1+2)*3"), (1+2)*3); }; ParserTest.prototype.testAssignments = function(){ - var scope = new nglr.Scope(); + var scope = new Scope(); assertEquals(scope.eval("a=12"), 12); assertEquals(scope.get("a"), 12); - scope = new nglr.Scope(); + scope = new Scope(); assertEquals(scope.eval("x.y.z=123;"), 123); assertEquals(scope.get("x.y.z"), 123); @@ -224,13 +224,13 @@ ParserTest.prototype.testAssignments = function(){ }; ParserTest.prototype.testFunctionCallsNoArgs = function(){ - var scope = new nglr.Scope(); + var scope = new Scope(); scope.set('const', function(a,b){return 123;}); assertEquals(scope.eval("const()"), 123); }; ParserTest.prototype.testFunctionCalls = function(){ - var scope = new nglr.Scope(); + var scope = new Scope(); scope.set('add', function(a,b){ return a+b; }); @@ -238,7 +238,7 @@ ParserTest.prototype.testFunctionCalls = function(){ }; ParserTest.prototype.testCalculationBug = function(){ - var scope = new nglr.Scope(); + var scope = new Scope(); scope.set('taxRate', 8); scope.set('subTotal', 100); assertEquals(scope.eval("taxRate / 100 * subTotal"), 8); @@ -246,7 +246,7 @@ ParserTest.prototype.testCalculationBug = function(){ }; ParserTest.prototype.testArray = function(){ - var scope = new nglr.Scope(); + var scope = new Scope(); assertEquals(scope.eval("[]").length, 0); assertEquals(scope.eval("[1, 2]").length, 2); assertEquals(scope.eval("[1, 2]")[0], 1); @@ -254,7 +254,7 @@ ParserTest.prototype.testArray = function(){ }; ParserTest.prototype.testArrayAccess = function(){ - var scope = new nglr.Scope(); + var scope = new Scope(); assertEquals(scope.eval("[1][0]"), 1); assertEquals(scope.eval("[[1]][0][0]"), 1); assertEquals(scope.eval("[].length"), 0); @@ -262,33 +262,33 @@ ParserTest.prototype.testArrayAccess = function(){ }; ParserTest.prototype.testObject = function(){ - var scope = new nglr.Scope(); - assertEquals(nglr.toJson(scope.eval("{}")), "{}"); - assertEquals(nglr.toJson(scope.eval("{a:'b'}")), '{"a":"b"}'); - assertEquals(nglr.toJson(scope.eval("{'a':'b'}")), '{"a":"b"}'); - assertEquals(nglr.toJson(scope.eval("{\"a\":'b'}")), '{"a":"b"}'); + var scope = new Scope(); + assertEquals(toJson(scope.eval("{}")), "{}"); + assertEquals(toJson(scope.eval("{a:'b'}")), '{"a":"b"}'); + assertEquals(toJson(scope.eval("{'a':'b'}")), '{"a":"b"}'); + assertEquals(toJson(scope.eval("{\"a\":'b'}")), '{"a":"b"}'); }; ParserTest.prototype.testObjectAccess = function(){ - var scope = new nglr.Scope(); + var scope = new Scope(); assertEquals("WC", scope.eval("{false:'WC', true:'CC'}[false]")); }; ParserTest.prototype.testJSON = function(){ - var scope = new nglr.Scope(); - assertEquals(nglr.toJson(scope.eval("[{}]")), "[{}]"); - assertEquals(nglr.toJson(scope.eval("[{a:[]}, {b:1}]")), '[{"a":[]},{"b":1}]'); + var scope = new Scope(); + assertEquals(toJson(scope.eval("[{}]")), "[{}]"); + assertEquals(toJson(scope.eval("[{a:[]}, {b:1}]")), '[{"a":[]},{"b":1}]'); }; ParserTest.prototype.testMultippleStatements = function(){ - var scope = new nglr.Scope(); + var scope = new Scope(); assertEquals(scope.eval("a=1;b=3;a+b"), 4); assertEquals(scope.eval(";;1;;"), 1); }; ParserTest.prototype.testParseThrow = function(){ expectAsserts(1); - var scope = new nglr.Scope(); + var scope = new Scope(); scope.set('e', 'abc'); try { scope.eval("throw e"); @@ -298,7 +298,7 @@ ParserTest.prototype.testParseThrow = function(){ }; ParserTest.prototype.testMethodsGetDispatchedWithCorrectThis = function(){ - var scope = new nglr.Scope(); + var scope = new Scope(); var C = function (){ this.a=123; }; @@ -310,7 +310,7 @@ ParserTest.prototype.testMethodsGetDispatchedWithCorrectThis = function(){ assertEquals(123, scope.eval("obj.getA()")); }; ParserTest.prototype.testMethodsArgumentsGetCorrectThis = function(){ - var scope = new nglr.Scope(); + var scope = new Scope(); var C = function (){ this.a=123; }; @@ -326,13 +326,13 @@ ParserTest.prototype.testMethodsArgumentsGetCorrectThis = function(){ }; ParserTest.prototype.testObjectPointsToScopeValue = function(){ - var scope = new nglr.Scope(); + var scope = new Scope(); scope.set('a', "abc"); assertEquals("abc", scope.eval("{a:a}").a); }; ParserTest.prototype.testFieldAccess = function(){ - var scope = new nglr.Scope(); + var scope = new Scope(); var fn = function(){ return {name:'misko'}; }; @@ -341,14 +341,14 @@ ParserTest.prototype.testFieldAccess = function(){ }; ParserTest.prototype.testArrayIndexBug = function () { - var scope = new nglr.Scope(); + var scope = new Scope(); scope.set('items', [{}, {name:'misko'}]); assertEquals("misko", scope.eval('items[1].name')); }; ParserTest.prototype.testArrayAssignment = function () { - var scope = new nglr.Scope(); + var scope = new Scope(); scope.set('items', []); assertEquals("abc", scope.eval('items[1] = "abc"')); @@ -359,30 +359,30 @@ ParserTest.prototype.testArrayAssignment = function () { }; ParserTest.prototype.testFiltersCanBeGrouped = function () { - var scope = new nglr.Scope({name:'MISKO'}); + var scope = new Scope({name:'MISKO'}); assertEquals('misko', scope.eval('n = (name|lowercase)')); assertEquals('misko', scope.eval('n')); }; ParserTest.prototype.testFiltersCanBeGrouped = function () { - var scope = new nglr.Scope({name:'MISKO'}); + var scope = new Scope({name:'MISKO'}); assertEquals('misko', scope.eval('n = (name|lowercase)')); assertEquals('misko', scope.eval('n')); }; ParserTest.prototype.testRemainder = function () { - var scope = new nglr.Scope(); + var scope = new Scope(); assertEquals(1, scope.eval('1%2')); }; ParserTest.prototype.testSumOfUndefinedIsNotUndefined = function () { - var scope = new nglr.Scope(); + var scope = new Scope(); assertEquals(1, scope.eval('1+undefined')); assertEquals(1, scope.eval('undefined+1')); }; ParserTest.prototype.testMissingThrowsError = function() { - var scope = new nglr.Scope(); + var scope = new Scope(); try { scope.eval('[].count('); fail(); @@ -392,7 +392,7 @@ ParserTest.prototype.testMissingThrowsError = function() { }; ParserTest.prototype.testItShouldParseOnChangeIntoHashSet = function () { - var scope = new nglr.Scope({count:0}); + var scope = new Scope({count:0}); scope.watch("$anchor.a:count=count+1;$anchor.a:count=count+20;b:count=count+300"); scope.watchListeners["$anchor.a"].listeners[0](); @@ -403,7 +403,7 @@ ParserTest.prototype.testItShouldParseOnChangeIntoHashSet = function () { assertEquals(321, scope.get("count")); }; ParserTest.prototype.testItShouldParseOnChangeBlockIntoHashSet = function () { - var scope = new nglr.Scope({count:0}); + var scope = new Scope({count:0}); var listeners = {a:[], b:[]}; scope.watch("a:{count=count+1;count=count+20;};b:count=count+300", function(n, fn){listeners[n].push(fn);}); @@ -417,12 +417,12 @@ ParserTest.prototype.testItShouldParseOnChangeBlockIntoHashSet = function () { }; ParserTest.prototype.testItShouldParseEmptyOnChangeAsNoop = function () { - var scope = new nglr.Scope(); + var scope = new Scope(); scope.watch("", function(){fail();}); }; ParserTest.prototype.testItShouldCreateClosureFunctionWithNoArguments = function () { - var scope = new nglr.Scope(); + var scope = new Scope(); var fn = scope.eval("{:value}"); scope.set("value", 1); assertEquals(1, fn()); @@ -433,7 +433,7 @@ ParserTest.prototype.testItShouldCreateClosureFunctionWithNoArguments = function }; ParserTest.prototype.testItShouldCreateClosureFunctionWithArguments = function () { - var scope = new nglr.Scope(); + var scope = new Scope(); var fn = scope.eval("{(a):value+a}"); scope.set("value", 1); assertEquals(11, fn(10)); @@ -444,14 +444,14 @@ ParserTest.prototype.testItShouldCreateClosureFunctionWithArguments = function ( }; ParserTest.prototype.testItShouldHaveDefaultArugument = function(){ - var scope = new nglr.Scope(); + var scope = new Scope(); var fn = scope.eval("{:$*2}"); assertEquals(4, fn(2)); }; ParserTest.prototype.testReturnFunctionsAreNotBound = function(){ - var scope = new nglr.Scope(); - scope.set("$datastore", new nglr.DataStore()); + var scope = new Scope(); + scope.set("$datastore", new DataStore()); scope.entity("Group"); var Group = scope.get("Group"); assertEquals("eval Group", "function", typeof scope.eval("Group")); diff --git a/test/ScopeTest.js b/test/ScopeTest.js index c66a2329..e1c5c8ce 100644 --- a/test/ScopeTest.js +++ b/test/ScopeTest.js @@ -23,13 +23,13 @@ ScopeTest.prototype.testNoScopeDoesNotCauseInfiniteRecursion = function(){ }; ScopeTest.prototype.testScopeEval = function(){ - var scope = new nglr.Scope({b:345}); + var scope = new Scope({b:345}); assertEquals(scope.eval('b = 123'), 123); assertEquals(scope.get('b'), 123); }; ScopeTest.prototype.testScopeFromPrototype = function(){ - var scope = new nglr.Scope({b:123}); + var scope = new Scope({b:123}); scope.eval('a = b'); scope.eval('b = 456'); assertEquals(scope.get('a'), 123); @@ -37,32 +37,32 @@ ScopeTest.prototype.testScopeFromPrototype = function(){ }; ScopeTest.prototype.testSetScopeGet = function(){ - var scope = new nglr.Scope(); + var scope = new Scope(); scope.set('a', 987); assertEquals(scope.get('a'), 987); assertEquals(scope.eval('a'), 987); }; ScopeTest.prototype.testGetChain = function(){ - var scope = new nglr.Scope({a:{b:987}}); + var scope = new Scope({a:{b:987}}); assertEquals(scope.get('a.b'), 987); assertEquals(scope.eval('a.b'), 987); }; ScopeTest.prototype.testGetUndefinedChain = function(){ - var scope = new nglr.Scope(); + var scope = new Scope(); assertEquals(typeof scope.get('a.b'), 'undefined'); }; ScopeTest.prototype.testSetChain = function(){ - var scope = new nglr.Scope({a:{}}); + var scope = new Scope({a:{}}); scope.set('a.b', 987); assertEquals(scope.get('a.b'), 987); assertEquals(scope.eval('a.b'), 987); }; ScopeTest.prototype.testSetGetOnChain = function(){ - var scope = new nglr.Scope(); + var scope = new Scope(); scope.set('a.b', 987); assertEquals(scope.get('a.b'), 987); assertEquals(scope.eval('a.b'), 987); @@ -70,7 +70,7 @@ ScopeTest.prototype.testSetGetOnChain = function(){ ScopeTest.prototype.testGlobalFunctionAccess =function(){ window['scopeAddTest'] = function (a, b) {return a+b;}; - var scope = new nglr.Scope({window:window}); + var scope = new Scope({window:window}); assertEquals(scope.eval('window.scopeAddTest(1,2)'), 3); scope.set('add', function (a, b) {return a+b;}); @@ -82,7 +82,7 @@ ScopeTest.prototype.testGlobalFunctionAccess =function(){ ScopeTest.prototype.testValidationEval = function(){ expectAsserts(4); - var scope = new nglr.Scope(); + var scope = new Scope(); angular.validator.testValidator = function(value, expect){ assertEquals(scope, this.scope); return value == expect ? null : "Error text"; @@ -96,7 +96,7 @@ ScopeTest.prototype.testValidationEval = function(){ ScopeTest.prototype.testCallingNonExistantMethodShouldProduceFriendlyException = function() { expectAsserts(1); - var scope = new nglr.Scope({obj:{}}); + var scope = new Scope({obj:{}}); try { scope.eval("obj.iDontExist()"); fail(); @@ -106,7 +106,7 @@ ScopeTest.prototype.testCallingNonExistantMethodShouldProduceFriendlyException = }; ScopeTest.prototype.testAccessingWithInvalidPathShouldThrowError = function() { - var scope = new nglr.Scope(); + var scope = new Scope(); try { scope.get('a.{{b}}'); fail(); @@ -116,25 +116,25 @@ ScopeTest.prototype.testAccessingWithInvalidPathShouldThrowError = function() { }; ScopeTest.prototype.testItShouldHave$parent = function() { - var parent = new nglr.Scope({}, "ROOT"); - var child = new nglr.Scope(parent.state); + var parent = new Scope({}, "ROOT"); + var child = new Scope(parent.state); assertSame("parent", child.state.$parent, parent.state); assertSame("root", child.state.$root, parent.state); }; ScopeTest.prototype.testItShouldHave$root = function() { - var scope = new nglr.Scope({}, "ROOT"); + var scope = new Scope({}, "ROOT"); assertSame(scope.state.$root, scope.state); }; ScopeTest.prototype.testItShouldBuildPathOnUndefined = function(){ - var scope = new nglr.Scope({}, "ROOT"); + var scope = new Scope({}, "ROOT"); scope.setEval("a.$b.c", 1); assertJsonEquals({$b:{c:1}}, scope.get("a")); }; ScopeTest.prototype.testItShouldMapUnderscoreFunctions = function(){ - var scope = new nglr.Scope({}, "ROOT"); + var scope = new Scope({}, "ROOT"); scope.set("a", [1,2,3]); assertEquals('function', typeof scope.get("a.$size")); scope.eval("a.$includeIf(4,true)"); diff --git a/test/ServerTest.js b/test/ServerTest.js index d1f662f9..e367c90a 100644 --- a/test/ServerTest.js +++ b/test/ServerTest.js @@ -1,7 +1,7 @@ ServerTest = TestCase("ServerTest"); ServerTest.prototype.testBreakLargeRequestIntoPackets = function() { var log = ""; - var server = new nglr.Server("http://server", function(url){ + var server = new Server("http://server", function(url){ log += "|" + url; }); server.maxSize = 30; @@ -10,7 +10,7 @@ ServerTest.prototype.testBreakLargeRequestIntoPackets = function() { assertEquals(200, code); assertEquals("response", r); }); - nglr.uuid0("response"); + callbacks.uuid0("response"); assertEquals( "|http://server/$/uuid0/2/1?h=eyJtIjoiUE9TVCIsInAiOnt9LCJ1Ij" + "|http://server/$/uuid0/2/2?h=oiL2RhdGEvZGF0YWJhc2UifQ==", @@ -18,7 +18,7 @@ ServerTest.prototype.testBreakLargeRequestIntoPackets = function() { }; ServerTest.prototype.testItShouldEncodeUsingUrlRules = function() { - var server = new nglr.Server("http://server"); + var server = new Server("http://server"); assertEquals("fn5-fn5-", server.base64url("~~~~~~")); assertEquals("fn5_fn5_", server.base64url("~~\u007f~~\u007f")); }; @@ -28,13 +28,13 @@ FrameServerTest = TestCase("FrameServerTest"); FrameServerTest.prototype = { testRead:function(){ var window = {name:'$DATASET:"MyData"'}; - var server = new nglr.FrameServer(window); + var server = new FrameServer(window); server.read(); assertEquals("MyData", server.data); }, testWrite:function(){ var window = {}; - var server = new nglr.FrameServer(window); + var server = new FrameServer(window); server.data = "TestData" server.write(); assertEquals('$DATASET:"TestData"', window.name); diff --git a/test/UsersTest.js b/test/UsersTest.js index c808885c..f0ff545a 100644 --- a/test/UsersTest.js +++ b/test/UsersTest.js @@ -10,10 +10,10 @@ UsersTest.prototype = { testItShouldFetchCurrentUser:function(){ expectAsserts(5); var user; - var users = new nglr.Users({request:function(method, url, request, callback){ + var users = new Users({request:function(method, url, request, callback){ assertEquals("GET", method); assertEquals("/account.json", url); - assertEquals("{}", nglr.toJson(request)); + assertEquals("{}", toJson(request)); callback(200, {$status_code:200, user:{name:'misko'}}); }}); users.fetchCurrentUser(function(u){ diff --git a/test/WidgetsTest.js b/test/WidgetsTest.js index a245abda..fe20e664 100644 --- a/test/WidgetsTest.js +++ b/test/WidgetsTest.js @@ -2,8 +2,8 @@ WidgetTest = TestCase('WidgetTest'); WidgetTest.prototype.testRequired = function () { var view = $(''); - var scope = new nglr.Scope({$invalidWidgets:[]}); - var cntl = new nglr.TextController(view[0], 'a'); + var scope = new Scope({$invalidWidgets:[]}); + var cntl = new TextController(view[0], 'a'); cntl.updateView(scope); assertTrue(view.hasClass('ng-validation-error')); assertEquals("Required Value", view.attr('ng-error')); @@ -15,8 +15,8 @@ WidgetTest.prototype.testRequired = function () { WidgetTest.prototype.testValidator = function () { var view = $(''); - var scope = new nglr.Scope({$invalidWidgets:[]}); - var cntl = new nglr.TextController(view[0], 'a'); + var scope = new Scope({$invalidWidgets:[]}); + var cntl = new TextController(view[0], 'a'); angular.validator.testValidator = function(value, expect){ return value == expect ? null : "Error text"; }; @@ -43,8 +43,8 @@ WidgetTest.prototype.testValidator = function () { WidgetTest.prototype.testRequiredValidator = function () { var view = $(''); - var scope = new nglr.Scope({$invalidWidgets:[]}); - var cntl = new nglr.TextController(view[0], 'a'); + var scope = new Scope({$invalidWidgets:[]}); + var cntl = new TextController(view[0], 'a'); angular.validator.testValidator = function(value, expect){ return value == expect ? null : "Error text"; }; @@ -67,29 +67,29 @@ WidgetTest.prototype.testRequiredValidator = function () { delete angular.validator['testValidator']; }; -TextController = TestCase("TextController"); +TextControllerTest = TestCase("TextControllerTest"); -TextController.prototype.testDatePicker = function() { +TextControllerTest.prototype.testDatePicker = function() { var input = $(''); - input.data('scope', new nglr.Scope()); + input.data('scope', new Scope()); var body = $(document.body); body.append(input); - var binder = new nglr.Binder(input[0], new nglr.WidgetFactory()); + var binder = new Binder(input[0], new WidgetFactory()); assertTrue('before', input.data('datepicker') === undefined); binder.compile(); assertTrue('after', input.data('datepicker') !== null); assertTrue(body.html(), input.hasClass('hasDatepicker')); }; -RepeaterUpdater = TestCase("RepeaterUpdater"); +RepeaterUpdaterTest = TestCase("RepeaterUpdaterTest"); -RepeaterUpdater.prototype.testRemoveThenAdd = function() { +RepeaterUpdaterTest.prototype.testRemoveThenAdd = function() { var view = $("
      "); var template = function () { return $("
    • "); }; - var repeater = new nglr.RepeaterUpdater(view.find("span"), "a in b", template, ""); - var scope = new nglr.Scope(); + var repeater = new RepeaterUpdater(view.find("span"), "a in b", template, ""); + var scope = new Scope(); scope.set('b', [1,2]); repeater.updateView(scope); @@ -102,14 +102,14 @@ RepeaterUpdater.prototype.testRemoveThenAdd = function() { assertEquals(1, view.find("li").size()); }; -RepeaterUpdater.prototype.testShouldBindWidgetOnRepeaterClone = function(){ +RepeaterUpdaterTest.prototype.testShouldBindWidgetOnRepeaterClone = function(){ //fail(); }; -RepeaterUpdater.prototype.testShouldThrowInformativeSyntaxError= function(){ +RepeaterUpdaterTest.prototype.testShouldThrowInformativeSyntaxError= function(){ expectAsserts(1); try { - var repeater = new nglr.RepeaterUpdater(null, "a=b"); + var repeater = new RepeaterUpdater(null, "a=b"); } catch (e) { assertEquals("Expected ng-repeat in form of 'item in collection' but got 'a=b'.", e); } @@ -118,17 +118,17 @@ RepeaterUpdater.prototype.testShouldThrowInformativeSyntaxError= function(){ SelectControllerTest = TestCase("SelectControllerTest"); SelectControllerTest.prototype.testShouldUpdateModelNullOnNothingSelected = function(){ - var scope = new nglr.Scope(); + var scope = new Scope(); var view = {selectedIndex:-1, options:[]}; - var cntl = new nglr.SelectController(view, 'abc'); + var cntl = new SelectController(view, 'abc'); cntl.updateModel(scope); assertNull(scope.get('abc')); }; SelectControllerTest.prototype.testShouldUpdateModelWhenNothingSelected = function(){ - var scope = new nglr.Scope(); + var scope = new Scope(); var view = {value:'123'}; - var cntl = new nglr.SelectController(view, 'abc'); + var cntl = new SelectController(view, 'abc'); cntl.updateView(scope); assertEquals("123", scope.get('abc')); }; @@ -137,8 +137,8 @@ BindUpdaterTest = TestCase("BindUpdaterTest"); BindUpdaterTest.prototype.testShouldDisplayNothingForUndefined = function () { var view = $(''); - var controller = new nglr.BindUpdater(view[0], "{{a}}"); - var scope = new nglr.Scope(); + var controller = new BindUpdater(view[0], "{{a}}"); + var scope = new Scope(); scope.set('a', undefined); controller.updateView(scope); @@ -151,20 +151,20 @@ BindUpdaterTest.prototype.testShouldDisplayNothingForUndefined = function () { BindUpdaterTest.prototype.testShouldDisplayJsonForNonStrings = function () { var view = $(''); - var controller = new nglr.BindUpdater(view[0], "{{obj}}"); + var controller = new BindUpdater(view[0], "{{obj}}"); - controller.updateView(new nglr.Scope({obj:[]})); + controller.updateView(new Scope({obj:[]})); assertEquals("[]", view.text()); - controller.updateView(new nglr.Scope({obj:{text:'abc'}})); - assertEquals('abc', nglr.fromJson(view.text()).text); + controller.updateView(new Scope({obj:{text:'abc'}})); + assertEquals('abc', fromJson(view.text()).text); }; BindUpdaterTest.prototype.testShouldInsertHtmlNode = function () { var view = $(''); - var controller = new nglr.BindUpdater(view[0], "&{{obj}}"); - var scope = new nglr.Scope(); + var controller = new BindUpdater(view[0], "&{{obj}}"); + var scope = new Scope(); scope.set("obj", $('
      myDiv
      ')[0]); controller.updateView(scope); @@ -174,8 +174,8 @@ BindUpdaterTest.prototype.testShouldInsertHtmlNode = function () { BindUpdaterTest.prototype.testShouldDisplayTextMethod = function () { var view = $('
      '); - var controller = new nglr.BindUpdater(view[0], "{{obj}}"); - var scope = new nglr.Scope(); + var controller = new BindUpdater(view[0], "{{obj}}"); + var scope = new Scope(); scope.set("obj", new angular.filter.Meta({text:function(){return "abc";}})); controller.updateView(scope); @@ -187,13 +187,13 @@ BindUpdaterTest.prototype.testShouldDisplayTextMethod = function () { scope.set("obj", {text:"123"}); controller.updateView(scope); - assertEquals("123", nglr.fromJson(view.text()).text); + assertEquals("123", fromJson(view.text()).text); }; BindUpdaterTest.prototype.testShouldDisplayHtmlMethod = function () { var view = $('
      '); - var controller = new nglr.BindUpdater(view[0], "{{obj}}"); - var scope = new nglr.Scope(); + var controller = new BindUpdater(view[0], "{{obj}}"); + var scope = new Scope(); scope.set("obj", new angular.filter.Meta({html:function(){return "a
      b
      c";}})); controller.updateView(scope); @@ -205,13 +205,13 @@ BindUpdaterTest.prototype.testShouldDisplayHtmlMethod = function () { scope.set("obj", {html:"123"}); controller.updateView(scope); - assertEquals("123", nglr.fromJson(view.text()).html); + assertEquals("123", fromJson(view.text()).html); }; BindUpdaterTest.prototype.testUdateBoolean = function() { var view = $('
      '); - var controller = new nglr.BindUpdater(view[0], "{{true}}, {{false}}"); - controller.updateView(new nglr.Scope()); + var controller = new BindUpdater(view[0], "{{true}}, {{false}}"); + controller.updateView(new Scope()); assertEquals('true, false', view.text()); }; @@ -219,9 +219,9 @@ BindAttrUpdaterTest = TestCase("BindAttrUpdaterTest"); BindAttrUpdaterTest.prototype.testShouldLoadBlankImageWhenBindingIsUndefined = function () { var view = $(''); - var controller = new nglr.BindAttrUpdater(view[0], {src: '{{imageUrl}}'}); + var controller = new BindAttrUpdater(view[0], {src: '{{imageUrl}}'}); - var scope = new nglr.Scope(); + var scope = new Scope(); scope.set('imageUrl', undefined); scope.set('config.server', 'http://server'); @@ -229,16 +229,15 @@ BindAttrUpdaterTest.prototype.testShouldLoadBlankImageWhenBindingIsUndefined = f assertEquals("http://server/images/blank.gif", view.attr('src')); }; -RepeaterUpdaterTest = TestCase("RepeaterUpdaterTest"); RepeaterUpdaterTest.prototype.testShouldNotDieWhenRepeatExpressionIsNull = function() { - var rep = new nglr.RepeaterUpdater(null, "$item in items", null, null); - var scope = new nglr.Scope(); + var rep = new RepeaterUpdater(null, "$item in items", null, null); + var scope = new Scope(); scope.set('items', undefined); rep.updateView(scope); }; RepeaterUpdaterTest.prototype.testShouldIterateOverKeys = function() { - var rep = new nglr.RepeaterUpdater(null, "($k,_v) in items", null, null); + var rep = new RepeaterUpdater(null, "($k,_v) in items", null, null); assertEquals("items", rep.iteratorExp); assertEquals("_v", rep.valueExp); assertEquals("$k", rep.keyExp); @@ -247,14 +246,14 @@ RepeaterUpdaterTest.prototype.testShouldIterateOverKeys = function() { EvalUpdaterTest = TestCase("EvalUpdaterTest"); EvalUpdaterTest.prototype.testEvalThrowsException = function(){ var view = $('
      '); - var eval = new nglr.EvalUpdater(view[0], 'undefined()'); + var eval = new EvalUpdater(view[0], 'undefined()'); - eval.updateView(new nglr.Scope()); + eval.updateView(new Scope()); assertTrue(!!view.attr('ng-error')); assertTrue(view.hasClass('ng-exception')); eval.exp = "1"; - eval.updateView(new nglr.Scope()); + eval.updateView(new Scope()); assertFalse(!!view.attr('ng-error')); assertFalse(view.hasClass('ng-exception')); }; @@ -262,8 +261,8 @@ EvalUpdaterTest.prototype.testEvalThrowsException = function(){ RadioControllerTest = TestCase("RadioController"); RadioControllerTest.prototype.testItShouldTreatTrueStringAsBoolean = function () { var view = $(''); - var radio = new nglr.RadioController(view[0], 'select'); - var scope = new nglr.Scope({select:true}); + var radio = new RadioController(view[0], 'select'); + var scope = new Scope({select:true}); radio.updateView(scope); assertTrue(view[0].checked); }; diff --git a/test/XSitePostTest.js b/test/XSitePostTest.js deleted file mode 100644 index 8a3e4d6f..00000000 --- a/test/XSitePostTest.js +++ /dev/null @@ -1,47 +0,0 @@ -XSitePost = TestCase("XSitePost"); - -var e = function(text){ return Base64.encode(text); }; - -XSitePost.prototype.testMessageReceived = function () { - expectAsserts(4); - var xPost = new nglr.XSitePost(); - xPost.baseUrl = "http://getangular.test"; - xPost.post = function(url, request, callback){ - assertEquals('http://getangular.test/url', url); - assertEquals('abc', request.a); - assertEquals('xyz', request.x); - }; - xPost.incomingFragment('#id;0;1;'+e('/url')+':a:'+e('abc')+':x:'+e('xyz')); - assertEquals('{}', nglr.toJson(xPost.inQueue)); -}; - -XSitePost.prototype.testMessageReceivedInParts = function () { - expectAsserts(5); - var xPost = new nglr.XSitePost(); - xPost.baseUrl = "http://getangular.test"; - xPost.post = function(url, request, callback){ - assertEquals('http://getangular.test/url', url); - assertEquals('abc', request.a); - assertEquals('xyz', request.x); - }; - xPost.incomingFragment('#id;1;2;:x:'+e('xyz')); - assertNotSame('{}', nglr.toJson(xPost.inQueue)); - xPost.incomingFragment('#id;0;2;'+e('/url')+':a:'+e('abc')); - assertEquals('{}', nglr.toJson(xPost.inQueue)); -}; - -XSitePost.prototype.testPostResponsIsEnqueued = function () { - var xPost = new nglr.XSitePost(); - xPost.maxMsgSize = 11; - xPost.response("id", "response", "status"); - - assertEquals('["id:0:2:cmVzcG9uc2U","id:1:2:="]', - nglr.toJson(xPost.outQueue)); -}; - -XSitePost.prototype.testPush = function () { - var window = {}; - var xPost = new nglr.XSitePost(window); - xPost.response("id", "response", "status"); - assertEquals('id:0:1:cmVzcG9uc2U=', xPost.outQueue[0]); -}; diff --git a/test/formsTest.js b/test/formsTest.js index 66c4ec69..ccade915 100644 --- a/test/formsTest.js +++ b/test/formsTest.js @@ -2,7 +2,7 @@ nglrTest = TestCase('nglrTest'); nglrTest.prototype.testShiftBind = function(){ expectAsserts(3); - nglr.shiftBind('this', function(target, arg) { + shiftBind('this', function(target, arg) { assertEquals(this, 'this'); assertEquals(target, 'target'); assertEquals(arg, 'arg'); @@ -11,7 +11,7 @@ nglrTest.prototype.testShiftBind = function(){ nglrTest.prototype.testBind = function(){ expectAsserts(2); - nglr.bind('this', function(arg) { + bind('this', function(arg) { assertEquals(this, 'this'); assertEquals(arg, 'arg'); }).apply('XXX', ['arg']); diff --git a/test/testabilityPatch.js b/test/testabilityPatch.js index 13378d36..dde21846 100644 --- a/test/testabilityPatch.js +++ b/test/testabilityPatch.js @@ -6,8 +6,8 @@ HIDDEN = jQuery.browser.msie ? ' style="display: none; "' : ' style="display: none;"'; -nglr.msie = jQuery.browser.msie; -nglr.alert = function(msg) {jstestdriver.console.log("ALERT: " + msg);}; +msie = jQuery.browser.msie; +alert = function(msg) {jstestdriver.console.log("ALERT: " + msg);}; function noop(){} @@ -50,7 +50,7 @@ jQuery.fn.sortedHtml = function() { var toString = function(index, node) { node = node || this; if (node.nodeName == "#text") { - html += nglr.escapeHtml(node.nodeValue); + html += escapeHtml(node.nodeValue); } else { html += '<' + node.nodeName.toLowerCase(); var attributes = node.attributes || []; @@ -89,14 +89,14 @@ jQuery.fn.sortedHtml = function() { }; function encode64(obj){ - return Base64.encode(nglr.toJson(obj)); + return Base64.encode(toJson(obj)); } function decode64(base64){ - return nglr.fromJson(Base64.decode(base64)); + return fromJson(Base64.decode(base64)); } -nglr.Loader.prototype.configureJQueryPlugins(); +Loader.prototype.configureJQueryPlugins(); function assertHidden(node) { var display = node.css('display'); @@ -110,7 +110,7 @@ function assertVisible(node) { } function assertJsonEquals(expected, actual) { - assertEquals(nglr.toJson(expected), nglr.toJson(actual)); + assertEquals(toJson(expected), toJson(actual)); } function assertUndefined(value) { @@ -118,7 +118,7 @@ function assertUndefined(value) { } function assertDefined(value) { - assertTrue(nglr.toJson(value), !!value); + assertTrue(toJson(value), !!value); } function assertThrows(error, fn){ -- cgit v1.2.3 From 1aba6b53b88c70b61a0cc991b1371739305d117b Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Sun, 10 Jan 2010 08:58:57 -0800 Subject: basic calculator works with minified.js, lots of references still broken --- .gitignore | 2 + Rakefile | 33 +- angular-minified.js | 231 +++++----- angular.js | 789 ++++------------------------------ example/calculator-manual_init.html | 3 +- example/calculator-minified_init.html | 21 + src/API.js | 72 ++-- src/DataStore.js | 2 +- src/Filters.js | 2 +- src/JSON.js | 6 +- src/Loader.js | 47 +- src/Parser.js | 8 +- src/Scope.js | 2 +- src/Widgets.js | 16 +- src/test/Runner.js | 2 +- src/test/_namespace.js | 10 +- 16 files changed, 343 insertions(+), 903 deletions(-) create mode 100644 .gitignore create mode 100644 example/calculator-minified_init.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..90a1eea4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +angular-minified.map +externs.js diff --git a/Rakefile b/Rakefile index a4823809..8f7bc7ec 100644 --- a/Rakefile +++ b/Rakefile @@ -1,12 +1,39 @@ include FileUtils +desc 'Generate Externs' +task :compileexterns do + out = File.new("externs.js", "w") + + out.write("function _(){};\n") + file = File.new("lib/underscore/underscore.js", "r") + while (line = file.gets) + if line =~ /^\s*_\.(\w+)\s*=.*$/ + out.write("_.#{$1}=function(){};\n") + end + end + file.close + + out.write("function jQuery(){};\n") + file = File.new("lib/jquery/jquery-1.3.2.js", "r") + while (line = file.gets) + if line =~ /^\s*(\w+)\s*:\s*function.*$/ + out.write("jQuery.#{$1}=function(){};\n") + end + end + file.close + out.write("jQuery.scope=function(){};\n") + out.write("jQuery.controller=function(){};\n") + + out.close +end + desc 'Compile JavaScript' task :compile do + Rake::Task['compileexterns'].execute + concat = %x(cat \ - lib/underscore/underscore.js \ src/angular.prefix \ lib/webtoolkit/webtoolkit.base64.js \ - lib/swfobject.js/swfobject.js \ src/Loader.js \ src/API.js \ src/Binder.js \ @@ -30,6 +57,8 @@ task :compile do %x(java -jar lib/compiler-closure/compiler.jar \ --compilation_level ADVANCED_OPTIMIZATIONS \ --js angular.js \ + --externs externs.js \ + --create_source_map ./angular-minified.map \ --js_output_file angular-minified.js) end diff --git a/angular-minified.js b/angular-minified.js index a9faa563..ce5155f2 100644 --- a/angular-minified.js +++ b/angular-minified.js @@ -1,122 +1,109 @@ -function u(){return function(s){return s}}function x(){return function(){}}function y(s){return function(v){this[s]=v}}function z(s){return function(){return s}} -(function(){var s=this,v=s.Ma;function w(f){this.$=f}var a=typeof StopIteration!=="undefined"?StopIteration:"__break__",b=s.Ma=function(f){return new w(f)};if(typeof exports!=="undefined")exports.Ma=b;var c=Array.prototype.slice,d=Array.prototype.unshift,e=Object.prototype.toString,g=Object.prototype.hasOwnProperty,i=Object.prototype.propertyIsEnumerable;b.Ce="0.5.1";b.b=function(f,h,j){try{if(f.forEach)f.forEach(h,j);else if(b.q(f)||b.Lb(f))for(var k=0,m=f.length;k=k.ca&&(k={value:m,ca:q})});return k.value};b.min=function(f,h,j){if(!h&&b.q(f))return Math.min.apply(Math,f);var k={ca:Infinity};b.b(f,function(m,q,t){q=h?h.call(j,m,q,t):m;qm?1:0}),"value")};b.Sf=function(f,h,j){j=j||b.ga;for(var k=0,m=f.length;k>1;j(f[q])=0})})};b.gg=function(){for(var f=b.A(arguments),h=b.max(b.bb(f,"length")),j=new Array(h),k=0;k0?m-h:h-m)>=0)return k;k[q++]=m}};b.B=function(f,h){var j=b.L(arguments,2);return function(){return f.apply(h||s,j.concat(b.A(arguments)))}};b.Ne=function(f){var h= -b.L(arguments);if(h.length==0)h=b.Ua(f);b.b(h,function(j){f[j]=b.B(f[j],f)});return f};b.Ta=function(f,h){var j=b.L(arguments,2);return setTimeout(function(){return f.apply(f,j)},h)};b.defer=function(f){return b.Ta.apply(b,[f,1].concat(b.L(arguments)))};b.fg=function(f,h){return function(){var j=[f].concat(b.A(arguments));return h.apply(h,j)}};b.Ze=function(){var f=b.A(arguments);return function(){for(var h=b.A(arguments),j=f.length-1;j>=0;j--)h=[f[j].apply(this,h)];return h[0]}};b.ia=function(f){if(b.q(f))return b.Rd(0, -f.length);var h=[];for(var j in f)g.call(f,j)&&h.push(j);return h};b.gb=function(f){return b.map(f,b.ga)};b.Ua=function(f){return b.select(b.ia(f),function(h){return b.K(f[h])}).sort()};b.extend=function(f,h){for(var j in h)f[j]=h[j];return f};b.ua=function(f){if(b.q(f))return f.slice(0);return b.extend({},f)};b.isEqual=function(f,h){if(f===h)return true;var j=typeof f;if(j!=typeof h)return false;if(f==h)return true;if(!f&&h||f&&!h)return false;if(f.isEqual)return f.isEqual(h);if(b.pd(f)&&b.pd(h))return f.getTime()=== -h.getTime();if(b.Mb(f)&&b.Mb(h))return true;if(b.rd(f)&&b.rd(h))return f.source===h.source&&f.global===h.global&&f.ignoreCase===h.ignoreCase&&f.multiline===h.multiline;if(j!=="object")return false;if(f.length&&f.length!==h.length)return false;j=b.ia(f);var k=b.ia(h);if(j.length!=k.length)return false;for(var m in f)if(!b.isEqual(f[m],h[m]))return false;return true};b.tf=function(f){return b.ia(f).length==0};b.sf=function(f){return!!(f&&f.nodeType==1)};b.Lb=function(f){return f&&b.ha(f.length)&&!b.q(f)&& -!i.call(f,"length")};b.Mb=function(f){return b.ha(f)&&isNaN(f)};b.uf=function(f){return f===null};b.H=function(f){return typeof f=="undefined"};for(var l=["Array","Date","Function","Number","RegExp","String"],n=0,o=l.length;n)[^\t]*)'/g,"$1\r").replace(/\t=(.*?)%>/g,"',$1,'").split("\t").join("');").split("%>").join("p.push('").split("\r").join("\\'")+"');}return p.join('');");return h?f(h):f};b.forEach=b.b;b.kf=b.of=b.reduce;b.lf=b.reduceRight;b.filter=b.select;b.every=b.all;b.some=b.Hc;b.Ib=b.gd;b.Wf=b.L;b.Af=b.Ua;function r(f,h){return h?b(f).rb():f}b.b(b.Ua(b),function(f){var h=b[f];w.prototype[f]=function(){d.call(arguments,this.$);return r(h.apply(b, -arguments),this.Na)}});b.b(["pop","push","reverse","shift","sort","splice","unshift"],function(f){var h=Array.prototype[f];w.prototype[f]=function(){h.apply(this.$,arguments);return r(this.$,this.Na)}});b.b(["concat","join","slice"],function(f){var h=Array.prototype[f];w.prototype[f]=function(){return r(h.apply(this.$,arguments),this.Na)}});w.prototype.rb=function(){this.Na=true;return this};w.prototype.value=function(){return this.$}})(); -(function(s,v){var w={I:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=",wa:function(a){var b="",c,d,e,g,i,l,n=0;for(a=w.Cc(a);n>2;c=(c&3)<<4|d>>4;i=(d&15)<<2|e>>6;l=e&63;if(isNaN(d))i=l=64;else if(isNaN(e))l=64;b=b+this.I.charAt(g)+this.I.charAt(c)+this.I.charAt(i)+this.I.charAt(l)}return b},ef:function(a){var b="",c,d,e,g,i,l=0;for(a=a.replace(/[^A-Za-z0-9\+\/\=]/g,"");l>4;d=(d&15)<<4|g>>2;e=(g&3)<<6|i;b+=String.fromCharCode(c);if(g!=64)b+=String.fromCharCode(d);if(i!=64)b+=String.fromCharCode(e)}return b=w.Bc(b)},Cc: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},Bc: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 v.getAttribute=="undefined")v.getAttribute=x();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};if(_.H(s.Cf))nglr={};if(_.H(s.Fe))angular={};if(_.H(angular.j))angular.j={};if(_.H(angular.filter))angular.filter={};if(_.H(s.console))s.console={log:x(),error:x()};if(_.H(alert))alert=function(){console.log(arguments);s.alert.apply(s,arguments)};consoleLog=function(a,b){var c=v.createElement("div");c.className=a; -for(var d=a="",e=0;e/g,">")};escapeAttr=function(a){if(!a||!a.replace)return a;return a.replace(//g,">").replace(/\"/g,""")};bind=function(a,b){if(!a)throw"Missing this";if(!_.K(b))throw"Missing function";return function(){return b.apply(a,arguments)}};shiftBind=function(a,b){return function(){for(var c=[this],d=0;d=0&&a.splice(c,1);return b},find:function(a,b,c){if(b){var d=angular.X.compile(b); -_.xb(a,function(e){if(d(e)){c=e;return true}});return c}},gf:function(a,b){return angular.Ka.find(a,function(c){return c.N==b},null)},filter:function(a,b){var c=[];c.Nc=function(o){for(var p=0;p-1;case "object":for(var r in o)if(r.charAt(0)!=="$"&&e(o[r],p))return true;return false; -case "array":for(r=0;r=0&&a.splice(d,1);a.unshift((c?"-":"+")+b);return a}, -Gf: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:""},zf:function(a,b,c){var d=a[b];if(!d){d={};a[b]=d}merge(c,d);return a}};angular.O={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")+'"'},cc:function(a){a=angular.O.quote(a);for(var b=[], -c=0;c-1;){c1||Binder.ba(a[0])!==null};Binder.ba=function(a){return(a=a.replace(/\n/gm," ").match(/^\{\{(.*)\}\}$/))?a[1]:null};Binder.prototype.Qd=function(a){var b={};a.replace(/(?:^|&)([^&=]*)=?([^&]*)/g,function(c,d,e){if(d)b[decodeURIComponent(d)]=decodeURIComponent(e)});return b};Binder.prototype.Zb=function(a){var b= -this;a=a||this.fb.Gb();var c=a.indexOf("#");if(!(c<0)){a=this.Qd(a.substring(c+1));jQuery.b(b.anchor,function(d){delete b.anchor[d]});jQuery.b(a,function(d,e){b.anchor[d]=e})}};Binder.prototype.Pd=function(a){console.log("URL change detected",a);this.Zb(a);this.d()};Binder.prototype.ge=function(){var a=this.fb.Gb(),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.fb.Zd(a);return a};Binder.prototype.d=function(){(new Date).getTime();var a=jQuery(this.Q).scope();a.s("$invalidWidgets",[]);a.d();(new Date).getTime();this.ge();_.b(this.lc,function(b){b()})};Binder.prototype.va=function(a){var b=jQuery(this.Q),c=b.find(a);if(b.od(a))c=c.Ee();return c};Binder.prototype.dd=function(){this.va("[ng-init]").b(function(){var a=jQuery(this),b=a.scope();try{b.eval(a.g("ng-init"))}catch(c){alert("EVAL ERROR:\n"+a.g("ng-init")+ -"\n"+toJson(c,true))}})};Binder.prototype.da=function(a){this.va("[ng-entity]").g("ng-watch",function(){try{var b=jQuery(this);return a.da(b.g("ng-entity"))+(b.g("ng-watch")||"")}catch(c){alert(c)}})};Binder.prototype.compile=function(){var a=jQuery(this.Q),b=this;if(this.o.He){var c=this.va(":submit").Yb("[ng-action]");c.g("ng-action","$save()");c.Yb(":disabled").Yb("ng-bind-attr").g("ng-bind-attr",'{disabled:"{{$invalidWidgets}}"}')}this.ac(this.Q)(this.Q,a.scope(),"");this.va("a[ng-action]").xd("click", -function(){var d=jQuery(this);try{d.scope().eval(d.g("ng-action"));d.fc("ng-error");d.gc("ng-exception")}catch(e){d.Oa("ng-exception");d.g("ng-error",toJson(e,true))}b.d();return false})};Binder.prototype.ce=function(a,b,c){b=b.concat();var d=b.pop(),e=Binder.Ea(a.nodeValue);if(e.length>1||Binder.ba(e[0])){var g=a.parentNode;if(isLeafNode(g)){g.setAttribute("ng-bind-template",a.nodeValue);c.push({path:b,c:function(p){return new BindUpdater(p,p.getAttribute("ng-bind-template"))}})}else for(var i=0;i< -e.length;i++){var l=e[i],n=Binder.ba(l),o;if(n){o=v.createElement("span");jQuery(o).g("ng-bind",n);i===0&&c.push({path:b.concat(d+i),c:Binder.prototype.Xb})}else if(msie&&l.charAt(0)==" "){o=v.createElement("span");o.innerHTML=" "+l.substring(1)}else o=v.createTextNode(l);g.insertBefore(o,a)}g.removeChild(a)}};Binder.prototype.ac=function(a){var b=[];this.bc(a,[],b);return function(c,d,e){for(var g=b.length,i=0;i -2&&a.setAttribute("ng-bind-attr",d)}a.getAttribute||console.log(a);var o=a.getAttribute("ng-repeat");if(o){a.removeAttribute("ng-repeat");var p=this.ac(a);d=v.createComment("ng-repeat: "+o);e=a.parentNode;e.insertBefore(d,a);e.removeChild(a);var r=function(h,j,k){var m=jQuery(a).ua();m.Sa("display","");m.g("ng-repeat-index",""+k);m.data("scope",h);p(m[0],h,j+k+":");return m};c.push({path:b,c:function(h,j,k){return new RepeaterUpdater(jQuery(h),o,r,k)}})}else{a.getAttribute("ng-eval")&&c.push({path:b, -c:this.Hd});a.getAttribute("ng-bind")&&c.push({path:b,c:this.Xb});a.getAttribute("ng-bind-attr")&&c.push({path:b,c:this.Dd});a.getAttribute("ng-hide")&&c.push({path:b,c:this.Id});a.getAttribute("ng-show")&&c.push({path:b,c:this.Jd});a.getAttribute("ng-class")&&c.push({path:b,c:this.Ed});a.getAttribute("ng-class-odd")&&c.push({path:b,c:this.Gd});a.getAttribute("ng-class-even")&&c.push({path:b,c:this.Fd});a.getAttribute("ng-style")&&c.push({path:b,c:this.Kd});a.getAttribute("ng-watch")&&c.push({path:b, -c:this.Ld});d=a.nodeName;if(d=="INPUT"||d=="TEXTAREA"||d=="SELECT"||d=="BUTTON"){var f=this;c.push({path:b,c:function(h,j,k){h.name=k+h.name.split(":").pop();return f.me.Sc(jQuery(h),j)}})}if(d=="OPTION")if(!jQuery("')};FileController.prototype.i=function(a){var b=this.view.find("input").g("checked")?this.value:null;if(this.e===b)return false;else{a.s(this.hc,b);return true}};FileController.prototype.d=function(a){if((a=a.h(this.hc))&&this.value!==a){this.value=a;this.view.find("a").g("href", -this.value.url).text(this.value.text);this.view.find("span").text(angular.filter.Pa(this.value.size))}this.view.find("input").g("checked",!!a)};NullController=y("view");NullController.prototype.i=z(true);NullController.prototype.d=x();NullController.nd=new NullController;ButtonController=y("view");ButtonController.prototype.i=z(true);ButtonController.prototype.d=x();TextController=function(a,b){this.view=a;this.exp=b;this.j=a.getAttribute("ng-validate");this.Vd=typeof a.attributes["ng-required"]!= -"undefined";this.Nb=null;this.e=undefined;this.D=a.value;a.getAttribute("ng-widget")==="datepicker"&&jQuery(a).df()};TextController.prototype.i=function(a){var b=this.view.value;if(this.e===b)return false;else{a.z(this.exp,b);this.e=b;return true}};TextController.prototype.d=function(a){var b=this.view,c=a.h(this.exp);if(typeof c==="undefined"){c=this.D;a.z(this.exp,c)}c=c?c:"";if(this.e!=c)this.e=b.value=c;var d=false;b.removeAttribute("ng-error");if(this.Vd)d=!(c&&c.length>0);var e=d?"Required Value": -null;if(!d&&this.j&&c){e=a.je(this.j,c);d=!!e}if(this.Nb!==e){this.Nb=d;if(e!==null){b.setAttribute("ng-error",e);a.zd(this)}jQuery(b).eb("ng-validation-error",d)}};CheckboxController=function(a,b){this.view=a;this.exp=b;this.e=undefined;this.D=a.checked?a.value:""};CheckboxController.prototype.i=function(a){var b=this.view;b=b.checked?b.value:"";if(this.e===b)return false;else{a.z(this.exp,b);this.e=b;return true}};CheckboxController.prototype.d=function(a){var b=this.view,c=a.eval(this.exp);if(typeof c=== -"undefined"){c=this.D;a.z(this.exp,c)}b.checked=b.value==""+c};SelectController=function(a,b){this.view=a;this.exp=b;this.e=undefined;this.D=a.value};SelectController.prototype.i=function(a){if(this.view.selectedIndex<0)a.z(this.exp,null);else{var b=this.view.value;if(this.e===b)return false;else{a.z(this.exp,b);this.e=b;return true}}};SelectController.prototype.d=function(a){var b=this.view,c=a.h(this.exp);if(typeof c==="undefined"){c=this.D;a.z(this.exp,c)}if(c!==this.e){b.value=c?c:"";this.e=c}}; -MultiSelectController=function(a,b){this.view=a;this.exp=b;this.e=undefined;this.D=this.selected()};MultiSelectController.prototype.selected=function(){for(var a=[],b=this.view.options,c=0;cd;--b)this.ta.pop().element.removeNode();if(l&& -l.element[0].nodeName==="OPTION")if(d=jQuery(l.element[0].parentNode).data("controller")){d.e=undefined;d.d(a)}})};PopUp=y("Q");PopUp.nb="mouseleave mouseout click dblclick keypress keyup";PopUp.prototype.B=function(){this.Q.find(".ng-validation-error,.ng-exception").xd("mouseover",PopUp.Od)};PopUp.Od=function(){PopUp.ab();var a=jQuery(this);a.B(PopUp.nb,PopUp.ab);var b=a.position(),c=v.documentElement,d=(self.innerWidth||c&&c.clientWidth||v.body.clientWidth)-b.left;c=a.mf("ng-exception")?"EXCEPTION:": -"Validation error...";a=a.g("ng-error");d=d>375?"left":"right";c=jQuery("
      "+c+"
      "+a+"
      ");jQuery("body").append(c);if(d==="left")a=b.left+this.offsetWidth+11;else{a=b.left-315;c.find(".ng-arrow-right").Sa({left:301})}c.Sa({left:a+"px",top:b.top-3+"px"});return true};PopUp.ab=function(){jQuery("#ng-callout").Zf(PopUp.nb,PopUp.ab).remove();return true};Status=function(a){this.Sb= -a.append(Status.sc).find("#ng-loading");this.Ha=0};Status.sc='
      loading....
      ';Status.prototype.Lc=function(){this.Ha===0&&this.Sb.cb();this.Ha++};Status.prototype.ad=function(){this.Ha--;this.Ha===0&&this.Sb.Jb("fold")}})(window,document); +function m(){return function(){}}function u(n){return function(q){this[n]=q}}function w(n){return function(){return n}} +(function(n,q){var z={z:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=",$:function(a){var b="",c,d,e,f,g,h,i=0;for(a=z.Gb(a);i>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.z.charAt(f)+this.z.charAt(c)+this.z.charAt(g)+this.z.charAt(h)}return b},Gd: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=z.Fb(b)},Gb: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},Fb: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 q.getAttribute=="undefined")q.getAttribute=m();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};var x={},j=n.jQuery,A=j.browser.msie;if(!n.ld){angular={};n.angular=angular}if(!angular.h)angular.h={};if(!angular.filter)angular.filter={};if(!n.console)n.console={log:m(),error:m()};if(!angular.alert)angular.alert=function(){console.log(arguments);n.alert.apply(n,arguments)};var y;consoleLog=function(a,b){var c= +q.createElement("div");c.className=a;for(var d=a="",e=0;e/g,">")};escapeAttr=function(a){if(!a||!a.replace)return a;return a.replace(//g,">").replace(/\"/g,""")};bind=function(a,b){if(!a)throw"Missing this";if(!_.isFunction(b))throw"Missing function";return function(){return b.apply(a,arguments)}};shiftBind=function(a,b){return function(){for(var c=[this],d=0;d=0&&a.splice(c,1);return b},find:function(a,b,c){if(b){var d= +angular.Function.compile(b);_.detect(a,function(e){if(d(e)){c=e;return true}});return c}},findById:function(a,b){return angular.gd.find(a,function(c){return c.C==b},null)},filter:function(a,b){var c=[];c.Pb=function(k){for(var l=0;l-1;case "object":for(var o in k)if(o.charAt(0)!== +"$"&&e(k[o],l))return true;return false;case "array":for(o=0;o=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}merge(c,d);return a}};angular.String={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=angular.String.quote(a);for(var b=[],c=0;c-1;){c1||Binder.N(a[0])!==null};Binder.N=function(a){return(a=a.replace(/\n/gm, +" ").match(/^\{\{(.*)\}\}$/))?a[1]:null};Binder.prototype.Jc=function(a){var b={};a.replace(/(?:^|&)([^&=]*)=?([^&]*)/g,function(c,d,e){if(d)b[decodeURIComponent(d)]=decodeURIComponent(e)});return b};Binder.prototype.ib=function(a){var b=this;a=a||this.ya.Va();var c=a.indexOf("#");if(!(c<0)){a=this.Jc(a.substring(c+1));j.each(b.anchor,function(d){delete b.anchor[d]});j.each(a,function(d,e){b.anchor[d]=e})}};Binder.prototype.Ic=function(a){console.log("URL change detected",a);this.ib(a);this.c()}; +Binder.prototype.Vc=function(){var a=this.ya.Va(),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.ya.Rc(a);return a};Binder.prototype.c=function(){(new Date).getTime();var a=j(this.F).scope();a.l("$invalidWidgets",[]);a.c();(new Date).getTime();this.Vc();_.each(this.rb,function(b){b()})};Binder.prototype.Z= +function(a){var b=j(this.F),c=b.find(a);if(b.is(a))c=c.andSelf();return c};Binder.prototype.fc=function(){this.Z("[ng-init]").each(function(){var a=j(this),b=a.scope();try{b.eval(a.attr("ng-init"))}catch(c){alert("EVAL ERROR:\n"+a.attr("ng-init")+"\n"+toJson(c,true))}})};Binder.prototype.O=function(a){this.Z("[ng-entity]").attr("ng-watch",function(){try{var b=j(this);return a.O(b.attr("ng-entity"))+(b.attr("ng-watch")||"")}catch(c){alert(c)}})};Binder.prototype.compile=function(){var a=j(this.F), +b=this;if(this.k.nd){var c=this.Z(":submit").not("[ng-action]");c.attr("ng-action","$save()");c.not(":disabled").not("ng-bind-attr").attr("ng-bind-attr",'{disabled:"{{$invalidWidgets}}"}')}this.jb(this.F)(this.F,a.scope(),"");this.Z("a[ng-action]").live("click",function(){var d=j(this);try{d.scope().eval(d.attr("ng-action"));d.removeAttr("ng-error");d.removeClass("ng-exception")}catch(e){d.addClass("ng-exception");d.attr("ng-error",toJson(e,true))}b.c();return false})};Binder.prototype.Tc=function(a, +b,c){b=b.concat();var d=b.pop(),e=Binder.ga(a.nodeValue);if(e.length>1||Binder.N(e[0])){var f=a.parentNode;if(isLeafNode(f)){f.setAttribute("ng-bind-template",a.nodeValue);c.push({path:b,b:function(l){return new BindUpdater(l,l.getAttribute("ng-bind-template"))}})}else for(var g=0;g2&&a.setAttribute("ng-bind-attr",d)}a.getAttribute||console.log(a);var k=a.getAttribute("ng-repeat");if(k){a.removeAttribute("ng-repeat");var l=this.jb(a);d=q.createComment("ng-repeat: "+k);e=a.parentNode;e.insertBefore(d,a); +e.removeChild(a);var o=function(p,r,s){var v=j(a).clone();v.css("display","");v.attr("ng-repeat-index",""+s);v.data("scope",p);l(v[0],p,r+s+":");return v};c.push({path:b,b:function(p,r,s){return new RepeaterUpdater(j(p),k,o,s)}})}else{a.getAttribute("ng-eval")&&c.push({path:b,b:this.Ac});a.getAttribute("ng-bind")&&c.push({path:b,b:this.hb});a.getAttribute("ng-bind-attr")&&c.push({path:b,b:this.wc});a.getAttribute("ng-hide")&&c.push({path:b,b:this.Bc});a.getAttribute("ng-show")&&c.push({path:b,b:this.Cc}); +a.getAttribute("ng-class")&&c.push({path:b,b:this.xc});a.getAttribute("ng-class-odd")&&c.push({path:b,b:this.zc});a.getAttribute("ng-class-even")&&c.push({path:b,b:this.yc});a.getAttribute("ng-style")&&c.push({path:b,b:this.Dc});a.getAttribute("ng-watch")&&c.push({path:b,b:this.Ec});d=a.nodeName;if(d=="INPUT"||d=="TEXTAREA"||d=="SELECT"||d=="BUTTON"){var t=this;c.push({path:b,b:function(p,r,s){p.name=s+p.name.split(":").pop();return t.ad.Ub(j(p),r)}})}if(d=="OPTION")if(!j("')};FileController.prototype.e=function(a){var b=this.view.find("input").attr("checked")?this.value:null;if(this.d===b)return false;else{a.l(this.nb,b);return true}};FileController.prototype.c=function(a){if((a=a.get(this.nb))&&this.value!==a){this.value=a;this.view.find("a").attr("href",this.value.url).text(this.value.text); +this.view.find("span").text(angular.filter.bytes(this.value.size))}this.view.find("input").attr("checked",!!a)};NullController=u("view");NullController.prototype.e=w(true);NullController.prototype.c=m();NullController.lc=new NullController;ButtonController=u("view");ButtonController.prototype.e=w(true);ButtonController.prototype.c=m();TextController=function(a,b){this.view=a;this.exp=b;this.h=a.getAttribute("ng-validate");this.Nc=typeof a.attributes["ng-required"]!="undefined";this.Ya=null;this.d= +undefined;this.s=a.value;a.getAttribute("ng-widget")==="datepicker"&&j(a).Fd()};TextController.prototype.e=function(a){var b=this.view.value;if(this.d===b)return false;else{a.p(this.exp,b);this.d=b;return true}};TextController.prototype.c=function(a){var b=this.view,c=a.get(this.exp);if(typeof c==="undefined"){c=this.s;a.p(this.exp,c)}c=c?c:"";if(this.d!=c)this.d=b.value=c;var d=false;b.removeAttribute("ng-error");if(this.Nc)d=!(c&&c.length>0);var e=d?"Required Value":null;if(!d&&this.h&&c){e=a.Yc(this.h, +c);d=!!e}if(this.Ya!==e){this.Ya=d;if(e!==null){b.setAttribute("ng-error",e);a.sc(this)}j(b).toggleClass("ng-validation-error",d)}};CheckboxController=function(a,b){this.view=a;this.exp=b;this.d=undefined;this.s=a.checked?a.value:""};CheckboxController.prototype.e=function(a){var b=this.view;b=b.checked?b.value:"";if(this.d===b)return false;else{a.p(this.exp,b);this.d=b;return true}};CheckboxController.prototype.c=function(a){var b=this.view,c=a.eval(this.exp);if(typeof c==="undefined"){c=this.s; +a.p(this.exp,c)}b.checked=b.value==""+c};SelectController=function(a,b){this.view=a;this.exp=b;this.d=undefined;this.s=a.value};SelectController.prototype.e=function(a){if(this.view.selectedIndex<0)a.p(this.exp,null);else{var b=this.view.value;if(this.d===b)return false;else{a.p(this.exp,b);this.d=b;return true}}};SelectController.prototype.c=function(a){var b=this.view,c=a.get(this.exp);if(typeof c==="undefined"){c=this.s;a.p(this.exp,c)}if(c!==this.d){b.value=c?c:"";this.d=c}};MultiSelectController= +function(a,b){this.view=a;this.exp=b;this.d=undefined;this.s=this.selected()};MultiSelectController.prototype.selected=function(){for(var a=[],b=this.view.options,c=0;cd;--b){var o= +this.children.pop().element[0];o.parentNode.removeChild(o)}if(h&&h.element[0].nodeName==="OPTION")if(d=j(h.element[0].parentNode).data("controller")){d.d=undefined;d.c(a)}})};PopUp=u("F");PopUp.Fa="mouseleave mouseout click dblclick keypress keyup";PopUp.prototype.bind=function(){this.F.find(".ng-validation-error,.ng-exception").live("mouseover",PopUp.Hc)};PopUp.Hc=function(){PopUp.wa();var a=j(this);a.bind(PopUp.Fa,PopUp.wa);var b=a.position(),c=q.documentElement,d=(self.innerWidth||c&&c.clientWidth|| +q.body.clientWidth)-b.left;c=a.hasClass("ng-exception")?"EXCEPTION:":"Validation error...";a=a.attr("ng-error");d=d>375?"left":"right";c=j("
      "+c+"
      "+a+"
      ");j("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};PopUp.wa=function(){j("#ng-callout").unbind(PopUp.Fa, +PopUp.wa).remove();return true};Status=function(a){this.cb=a.append(Status.yb).find("#ng-loading");this.ja=0};Status.yb='
      loading....
      ';Status.prototype.Nb=function(){this.ja===0&&this.cb.show();this.ja++};Status.prototype.cc=function(){this.ja--;this.ja===0&&this.cb.hide("fold")}})(window,document); diff --git a/angular.js b/angular.js index 0e1527f2..c113d56b 100644 --- a/angular.js +++ b/angular.js @@ -1,612 +1,3 @@ -// Underscore.js -// (c) 2009 Jeremy Ashkenas, DocumentCloud Inc. -// Underscore is freely distributable under the terms of the MIT license. -// Portions of Underscore are inspired by or borrowed from Prototype.js, -// Oliver Steele's Functional, and John Resig's Micro-Templating. -// For all details and documentation: -// http://documentcloud.github.com/underscore/ - -(function() { - - /*------------------------- Baseline setup ---------------------------------*/ - - // Establish the root object, "window" in the browser, or "global" on the server. - var root = this; - - // Save the previous value of the "_" variable. - var previousUnderscore = root._; - - // If Underscore is called as a function, it returns a wrapped object that - // can be used OO-style. This wrapper holds altered versions of all the - // underscore functions. Wrapped objects may be chained. - var wrapper = function(obj) { this._wrapped = obj; }; - - // Establish the object that gets thrown to break out of a loop iteration. - var breaker = typeof StopIteration !== 'undefined' ? StopIteration : '__break__'; - - // Create a safe reference to the Underscore object for reference below. - var _ = root._ = function(obj) { return new wrapper(obj); }; - - // Export the Underscore object for CommonJS. - if (typeof exports !== 'undefined') exports._ = _; - - // Create quick reference variables for speed access to core prototypes. - var slice = Array.prototype.slice, - unshift = Array.prototype.unshift, - toString = Object.prototype.toString, - hasOwnProperty = Object.prototype.hasOwnProperty, - propertyIsEnumerable = Object.prototype.propertyIsEnumerable; - - // Current version. - _.VERSION = '0.5.1'; - - /*------------------------ Collection Functions: ---------------------------*/ - - // The cornerstone, an each implementation. - // Handles objects implementing forEach, arrays, and raw objects. - _.each = function(obj, iterator, context) { - var index = 0; - try { - if (obj.forEach) { - obj.forEach(iterator, context); - } else if (_.isArray(obj) || _.isArguments(obj)) { - for (var i=0, l=obj.length; i= result.computed && (result = {value : value, computed : computed}); - }); - return result.value; - }; - - // Return the minimum element (or element-based computation). - _.min = function(obj, iterator, context) { - if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj); - var result = {computed : Infinity}; - _.each(obj, function(value, index, list) { - var computed = iterator ? iterator.call(context, value, index, list) : value; - computed < result.computed && (result = {value : value, computed : computed}); - }); - return result.value; - }; - - // Sort the object's values by a criteria produced by an iterator. - _.sortBy = function(obj, iterator, context) { - return _.pluck(_.map(obj, function(value, index, list) { - return { - value : value, - criteria : iterator.call(context, value, index, list) - }; - }).sort(function(left, right) { - var a = left.criteria, b = right.criteria; - return a < b ? -1 : a > b ? 1 : 0; - }), 'value'); - }; - - // Use a comparator function to figure out at what index an object should - // be inserted so as to maintain order. Uses binary search. - _.sortedIndex = function(array, obj, iterator) { - iterator = iterator || _.identity; - var low = 0, high = array.length; - while (low < high) { - var mid = (low + high) >> 1; - iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid; - } - return low; - }; - - // Convert anything iterable into a real, live array. - _.toArray = function(iterable) { - if (!iterable) return []; - if (iterable.toArray) return iterable.toArray(); - if (_.isArray(iterable)) return iterable; - if (_.isArguments(iterable)) return slice.call(iterable); - return _.map(iterable, function(val){ return val; }); - }; - - // Return the number of elements in an object. - _.size = function(obj) { - return _.toArray(obj).length; - }; - - /*-------------------------- Array Functions: ------------------------------*/ - - // Get the first element of an array. Passing "n" will return the first N - // values in the array. Aliased as "head". The "guard" check allows it to work - // with _.map. - _.first = function(array, n, guard) { - return n && !guard ? slice.call(array, 0, n) : array[0]; - }; - - // Returns everything but the first entry of the array. Aliased as "tail". - // Especially useful on the arguments object. Passing an "index" will return - // the rest of the values in the array from that index onward. The "guard" - //check allows it to work with _.map. - _.rest = function(array, index, guard) { - return slice.call(array, _.isUndefined(index) || guard ? 1 : index); - }; - - // Get the last element of an array. - _.last = function(array) { - return array[array.length - 1]; - }; - - // Trim out all falsy values from an array. - _.compact = function(array) { - return _.select(array, function(value){ return !!value; }); - }; - - // Return a completely flattened version of an array. - _.flatten = function(array) { - return _.reduce(array, [], function(memo, value) { - if (_.isArray(value)) return memo.concat(_.flatten(value)); - memo.push(value); - return memo; - }); - }; - - // Return a version of the array that does not contain the specified value(s). - _.without = function(array) { - var values = _.rest(arguments); - return _.select(array, function(value){ return !_.include(values, value); }); - }; - - // Produce a duplicate-free version of the array. If the array has already - // been sorted, you have the option of using a faster algorithm. - _.uniq = function(array, isSorted) { - return _.reduce(array, [], function(memo, el, i) { - if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) memo.push(el); - return memo; - }); - }; - - // Produce an array that contains every item shared between all the - // passed-in arrays. - _.intersect = function(array) { - var rest = _.rest(arguments); - return _.select(_.uniq(array), function(item) { - return _.all(rest, function(other) { - return _.indexOf(other, item) >= 0; - }); - }); - }; - - // Zip together multiple lists into a single array -- elements that share - // an index go together. - _.zip = function() { - var args = _.toArray(arguments); - var length = _.max(_.pluck(args, 'length')); - var results = new Array(length); - for (var i=0; i 0 ? i - stop : stop - i) >= 0) return range; - range[idx++] = i; - } - }; - - /* ----------------------- Function Functions: -----------------------------*/ - - // Create a function bound to a given object (assigning 'this', and arguments, - // optionally). Binding with arguments is also known as 'curry'. - _.bind = function(func, obj) { - var args = _.rest(arguments, 2); - return function() { - return func.apply(obj || root, args.concat(_.toArray(arguments))); - }; - }; - - // Bind all of an object's methods to that object. Useful for ensuring that - // all callbacks defined on an object belong to it. - _.bindAll = function(obj) { - var funcs = _.rest(arguments); - if (funcs.length == 0) funcs = _.functions(obj); - _.each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); - return obj; - }; - - // Delays a function for the given number of milliseconds, and then calls - // it with the arguments supplied. - _.delay = function(func, wait) { - var args = _.rest(arguments, 2); - return setTimeout(function(){ return func.apply(func, args); }, wait); - }; - - // Defers a function, scheduling it to run after the current call stack has - // cleared. - _.defer = function(func) { - return _.delay.apply(_, [func, 1].concat(_.rest(arguments))); - }; - - // Returns the first function passed as an argument to the second, - // allowing you to adjust arguments, run code before and after, and - // conditionally execute the original function. - _.wrap = function(func, wrapper) { - return function() { - var args = [func].concat(_.toArray(arguments)); - return wrapper.apply(wrapper, args); - }; - }; - - // Returns a function that is the composition of a list of functions, each - // consuming the return value of the function that follows. - _.compose = function() { - var funcs = _.toArray(arguments); - return function() { - var args = _.toArray(arguments); - for (var i=funcs.length-1; i >= 0; i--) { - args = [funcs[i].apply(this, args)]; - } - return args[0]; - }; - }; - - /* ------------------------- Object Functions: ---------------------------- */ - - // Retrieve the names of an object's properties. - _.keys = function(obj) { - if(_.isArray(obj)) return _.range(0, obj.length); - var keys = []; - for (var key in obj) if (hasOwnProperty.call(obj, key)) keys.push(key); - return keys; - }; - - // Retrieve the values of an object's properties. - _.values = function(obj) { - return _.map(obj, _.identity); - }; - - // Return a sorted list of the function names available in Underscore. - _.functions = function(obj) { - return _.select(_.keys(obj), function(key){ return _.isFunction(obj[key]); }).sort(); - }; - - // Extend a given object with all of the properties in a source object. - _.extend = function(destination, source) { - for (var property in source) destination[property] = source[property]; - return destination; - }; - - // Create a (shallow-cloned) duplicate of an object. - _.clone = function(obj) { - if (_.isArray(obj)) return obj.slice(0); - return _.extend({}, obj); - }; - - // Perform a deep comparison to check if two objects are equal. - _.isEqual = function(a, b) { - // Check object identity. - if (a === b) return true; - // Different types? - var atype = typeof(a), btype = typeof(b); - if (atype != btype) return false; - // Basic equality test (watch out for coercions). - if (a == b) return true; - // One is falsy and the other truthy. - if ((!a && b) || (a && !b)) return false; - // One of them implements an isEqual()? - if (a.isEqual) return a.isEqual(b); - // Check dates' integer values. - if (_.isDate(a) && _.isDate(b)) return a.getTime() === b.getTime(); - // Both are NaN? - if (_.isNaN(a) && _.isNaN(b)) return true; - // Compare regular expressions. - if (_.isRegExp(a) && _.isRegExp(b)) - return a.source === b.source && - a.global === b.global && - a.ignoreCase === b.ignoreCase && - a.multiline === b.multiline; - // If a is not an object by this point, we can't handle it. - if (atype !== 'object') return false; - // Check for different array lengths before comparing contents. - if (a.length && (a.length !== b.length)) return false; - // Nothing else worked, deep compare the contents. - var aKeys = _.keys(a), bKeys = _.keys(b); - // Different object sizes? - if (aKeys.length != bKeys.length) return false; - // Recursive comparison of contents. - for (var key in a) if (!_.isEqual(a[key], b[key])) return false; - return true; - }; - - // Is a given array or object empty? - _.isEmpty = function(obj) { - return _.keys(obj).length == 0; - }; - - // Is a given value a DOM element? - _.isElement = function(obj) { - return !!(obj && obj.nodeType == 1); - }; - - // Is a given variable an arguments object? - _.isArguments = function(obj) { - return obj && _.isNumber(obj.length) && !_.isArray(obj) && !propertyIsEnumerable.call(obj, 'length'); - }; - - // Is the given value NaN -- this one is interesting. NaN != NaN, and - // isNaN(undefined) == true, so we make sure it's a number first. - _.isNaN = function(obj) { - return _.isNumber(obj) && isNaN(obj); - }; - - // Is a given value equal to null? - _.isNull = function(obj) { - return obj === null; - }; - - // Is a given variable undefined? - _.isUndefined = function(obj) { - return typeof obj == 'undefined'; - }; - - // Define the isArray, isDate, isFunction, isNumber, isRegExp, and isString - // functions based on their toString identifiers. - var types = ['Array', 'Date', 'Function', 'Number', 'RegExp', 'String']; - for (var i=0, l=types.length; i)[^\t]*)'/g, "$1\r") - .replace(/\t=(.*?)%>/g, "',$1,'") - .split("\t").join("');") - .split("%>").join("p.push('") - .split("\r").join("\\'") - + "');}return p.join('');"); - return data ? fn(data) : fn; - }; - - /*------------------------------- Aliases ----------------------------------*/ - - _.forEach = _.each; - _.foldl = _.inject = _.reduce; - _.foldr = _.reduceRight; - _.filter = _.select; - _.every = _.all; - _.some = _.any; - _.head = _.first; - _.tail = _.rest; - _.methods = _.functions; - - /*------------------------ Setup the OOP Wrapper: --------------------------*/ - - // Helper function to continue chaining intermediate results. - var result = function(obj, chain) { - return chain ? _(obj).chain() : obj; - }; - - // Add all of the Underscore functions to the wrapper object. - _.each(_.functions(_), function(name) { - var method = _[name]; - wrapper.prototype[name] = function() { - unshift.call(arguments, this._wrapped); - return result(method.apply(_, arguments), this._chain); - }; - }); - - // Add all mutator Array functions to the wrapper. - _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { - var method = Array.prototype[name]; - wrapper.prototype[name] = function() { - method.apply(this._wrapped, arguments); - return result(this._wrapped, this._chain); - }; - }); - - // Add all accessor Array functions to the wrapper. - _.each(['concat', 'join', 'slice'], function(name) { - var method = Array.prototype[name]; - wrapper.prototype[name] = function() { - return result(method.apply(this._wrapped, arguments), this._chain); - }; - }); - - // Start chaining a wrapped Underscore object. - wrapper.prototype.chain = function() { - this._chain = true; - return this; - }; - - // Extracts the result from a wrapped and chained object. - wrapper.prototype.value = function() { - return this._wrapped; - }; - -})(); (function(window, document){/** * @@ -773,19 +164,24 @@ if (typeof Node == 'undefined') { }; } -if (_.isUndefined(window.nglr)) nglr = {}; -if (_.isUndefined(window.angular)) angular = {}; -if (_.isUndefined(angular.validator)) angular.validator = {}; -if (_.isUndefined(angular.filter)) angular.filter = {}; -if (_.isUndefined(window.console)) +var callbacks = {}; +var jQuery = window['jQuery']; +var msie = jQuery['browser']['msie']; + +if (!window.angular){ angular = {}; window['angular'] = angular; } +if (!angular.validator) angular.validator = {}; +if (!angular.filter) angular.filter = {}; +if (!window.console) window.console = { log:function() {}, error:function() {} }; -if (_.isUndefined(alert)) { - alert = function(){console.log(arguments); window.alert.apply(window, arguments); }; +if (!angular.alert) { + angular.alert = function(){console.log(arguments); window.alert.apply(window, arguments); }; } +var consoleNode; + consoleLog = function(level, objs) { var log = document.createElement("div"); log.className = level; @@ -917,7 +313,6 @@ Loader.prototype.load = function() { this.loadCss('/stylesheets/jquery-ui/smoothness/jquery-ui-1.7.1.css'); this.loadCss('/stylesheets/css'); console.log("Server: " + this.config.server); - msie = jQuery.browser.msie; this.configureJQueryPlugins(); this.computeConfiguration(); this.bindHtml(); @@ -925,11 +320,7 @@ Loader.prototype.load = function() { Loader.prototype.configureJQueryPlugins = function() { console.log('Loader.configureJQueryPlugins()'); - jQuery.fn.removeNode = function() { - var node = this.get(0); - node.parentNode.removeChild(node); - }; - jQuery.fn.scope = function() { + jQuery['fn']['scope'] = function() { var element = this; while (element && element.get(0)) { var scope = element.data("scope"); @@ -939,7 +330,7 @@ Loader.prototype.configureJQueryPlugins = function() { } return null; }; - jQuery.fn.controller = function() { + jQuery['fn']['controller'] = function() { return this.data('controller') || NullController.instance; }; }; @@ -977,24 +368,19 @@ Loader.prototype.bindHtml = function() { 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) { + '$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 + '$window' : window, + '$uid' : this.uid, + '$users' : users }, "ROOT"); - jQuery.each(["get", "set", "eval", "addWatchListener", "updateView"], - function(i, method){ - angular[method] = bind(scope, scope[method]); - }); - document.data('scope', scope); console.log('$binder.entity()'); binder.entity(scope); @@ -1032,7 +418,6 @@ Loader.prototype.bindHtml = function() { watcher.watch(); document.find("body").show(); console.log('ready()'); - }; Loader.prototype.visualPost = function(delegate) { @@ -1108,8 +493,8 @@ UrlWatcher.prototype.watch = function() { } self.location.href = self.expectedUrl; var id = '_iframe_notify_' + notify[1]; - var notifyFn = nglr[id]; - delete nglr[id]; + var notifyFn = callbacks[id]; + delete callbacks[id]; try { (notifyFn||noop)(); } catch (e) { @@ -1131,7 +516,7 @@ UrlWatcher.prototype.setUrl = function(url) { existingURL += '#'; if (existingURL != url) window.location.href = url; - self.existingURL = url; + this.existingURL = url; }; UrlWatcher.prototype.getUrl = function() { @@ -1147,11 +532,17 @@ angular['compile'] = function(root, config) { //todo: don't start watcher var loader = new Loader(root, jQuery("head"), _(defaults).extend(config)); loader.load(); - return jQuery(root).scope(); + var scope = jQuery(root).scope(); + //TODO: cleanup + return { + 'updateView':function(){return scope.updateView.apply(scope, arguments);}, + 'set':function(){return scope.set.apply(scope, arguments);}, + 'get':function(){return scope.get.apply(scope, arguments);} + }; }; -angular.Global = { - typeOf:function(obj){ +angular['Global'] = { + 'typeOf':function(obj){ var type = typeof obj; switch(type) { case "object": @@ -1164,10 +555,10 @@ angular.Global = { } }; -angular.Collection = {}; -angular.Object = {}; -angular.Array = { - includeIf:function(array, value, condition) { +angular['Collection'] = {}; +angular['Object'] = {}; +angular['Array'] = { + 'includeIf':function(array, value, condition) { var index = _.indexOf(array, value); if (condition) { if (index == -1) @@ -1177,8 +568,8 @@ angular.Array = { } return array; }, - sum:function(array, expression) { - var fn = angular.Function.compile(expression); + 'sum':function(array, expression) { + var fn = angular['Function']['compile'](expression); var sum = 0; for (var i = 0; i < array.length; i++) { var value = 1 * fn(array[i]); @@ -1188,15 +579,15 @@ angular.Array = { } return sum; }, - remove:function(array, value) { + 'remove':function(array, value) { var index = _.indexOf(array, value); if (index >=0) array.splice(index, 1); return value; }, - find:function(array, condition, defaultValue) { + 'find':function(array, condition, defaultValue) { if (!condition) return undefined; - var fn = angular.Function.compile(condition); + var fn = angular['Function']['compile'](condition); _.detect(array, function($){ if (fn($)){ defaultValue = $; @@ -1205,10 +596,10 @@ angular.Array = { }); return defaultValue; }, - findById:function(array, id) { + 'findById':function(array, id) { return angular.Array.find(array, function($){return $.$id == id;}, null); }, - filter:function(array, expression) { + 'filter':function(array, expression) { var predicates = []; predicates.check = function(value) { for (var j = 0; j < predicates.length; j++) { @@ -1288,16 +679,16 @@ angular.Array = { } return filtered; }, - add:function(array, value) { + 'add':function(array, value) { array.push(_.isUndefined(value)? {} : value); return array; }, - count:function(array, condition) { + 'count':function(array, condition) { if (!condition) return array.length; - var fn = angular.Function.compile(condition); + var fn = angular['Function']['compile'](condition); return _.reduce(array, 0, function(count, $){return count + (fn($)?1:0);}); }, - orderBy:function(array, expression, descend) { + 'orderBy':function(array, expression, descend) { function reverse(comp, descending) { return toBoolean(descending) ? function(a,b){return comp(b,a);} : comp; @@ -1321,7 +712,7 @@ angular.Array = { descending = $.charAt(0) == '-'; $ = $.substring(1); } - var get = $ ? angular.Function.compile($) : _.identity; + var get = $ ? angular['Function']['compile']($) : _.identity; return reverse(function(a,b){ return compare(get(a),get(b)); }, descending); @@ -1335,7 +726,7 @@ angular.Array = { }; return _.clone(array).sort(reverse(comparator, descend)); }, - orderByToggle:function(predicate, attribute) { + 'orderByToggle':function(predicate, attribute) { var STRIP = /^([+|-])?(.*)/; var ascending = false; var index = -1; @@ -1357,7 +748,7 @@ angular.Array = { predicate.unshift((ascending ? "-" : "+") + attribute); return predicate; }, - orderByDirection:function(predicate, attribute, ascend, descend) { + 'orderByDirection':function(predicate, attribute, ascend, descend) { ascend = ascend || 'ng-ascend'; descend = descend || 'ng-descend'; var att = predicate[0] || ''; @@ -1370,7 +761,7 @@ angular.Array = { } return att == attribute ? (direction ? ascend : descend) : ""; }, - merge:function(array, index, mergeValue) { + 'merge':function(array, index, mergeValue) { var value = array[index]; if (!value) { value = {}; @@ -1380,8 +771,8 @@ angular.Array = { return array; } }; -angular.String = { - quote:function(string) { +angular['String'] = { + 'quote':function(string) { return '"' + string.replace(/\\/g, '\\\\'). replace(/"/g, '\\"'). replace(/\n/g, '\\n'). @@ -1391,8 +782,8 @@ angular.String = { replace(/\v/g, '\\v') + '"'; }, - quoteUnicode:function(string) { - var str = angular.String.quote(string); + 'quoteUnicode':function(string) { + var str = angular['String']['quote'](string); var chars = []; for ( var i = 0; i < str.length; i++) { var ch = str.charCodeAt(i); @@ -1405,7 +796,7 @@ angular.String = { } return chars.join(''); }, - toDate:function(string){ + 'toDate':function(string){ var match; if (typeof string == 'string' && (match = string.match(/^(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)Z$/))){ @@ -1417,8 +808,8 @@ angular.String = { return string; } }; -angular.Date = { - toString:function(date){ +angular['Date'] = { + 'toString':function(date){ function pad(n) { return n < 10 ? "0" + n : n; } return (date.getUTCFullYear()) + '-' + pad(date.getUTCMonth() + 1) + '-' + @@ -1428,8 +819,8 @@ angular.Date = { pad(date.getUTCSeconds()) + 'Z'; } }; -angular.Function = { - compile:function(expression) { +angular['Function'] = { + 'compile':function(expression) { if (_.isFunction(expression)){ return expression; } else if (expression){ @@ -1451,21 +842,21 @@ angular.Function = { dst[name] = _[name]; }); }; - extend(angular.Global, {}, + extend(angular['Global'], {}, ['extend', 'clone','isEqual', 'isElement', 'isArray', 'isFunction', 'isUndefined']); - extend(angular.Collection, angular.Global, + extend(angular['Collection'], angular['Global'], ['each', 'map', 'reduce', 'reduceRight', 'detect', 'select', 'reject', 'all', 'any', 'include', 'invoke', 'pluck', 'max', 'min', 'sortBy', 'sortedIndex', 'toArray', 'size']); - extend(angular.Array, angular.Collection, + extend(angular['Array'], angular['Collection'], ['first', 'last', 'compact', 'flatten', 'without', 'uniq', 'intersect', 'zip', 'indexOf', 'lastIndexOf']); - extend(angular.Object, angular.Collection, + extend(angular['Object'], angular['Collection'], ['keys', 'values']); - extend(angular.String, angular.Global); - extend(angular.Function, angular.Global, + extend(angular['String'], angular['Global']); + extend(angular['Function'], angular['Global'], ['bind', 'bindAll', 'delay', 'defer', 'wrap', 'compose']); })();// Copyright (C) 2009 BRAT Tech LLC Binder = function(doc, widgetFactory, urlWatcher, config) { @@ -1868,7 +1259,7 @@ ControlBar.prototype.doTemplate = function (path) { resizable: false, modal:true, title: 'Authentication: <angular/>' }); - nglr["_iframe_notify_" + id] = function() { + callbacks["_iframe_notify_" + id] = function() { loginView.dialog("destroy"); loginView.remove(); jQuery.each(self.callbacks, function(i, callback){ @@ -1982,7 +1373,7 @@ DataStore.prototype.save = function(document, callback) { var cachedDoc = self.cache(document); _.each(self._cache.$collections, function(collection){ if (collection.$$accept(document)) { - angular.Array.includeIf(collection, cachedDoc, true); + angular['Array']['includeIf'](collection, cachedDoc, true); } }); if (document.$$anchor) { @@ -2244,7 +1635,7 @@ angular.filter.Meta.get = function(obj, attr){ } }; -angular.filter.currency = function(amount){ +angular.filter['currency'] = function(amount){ jQuery(this.element).toggleClass('ng-format-negative', amount < 0); return '$' + angular.filter.number.apply(this, [amount, 2]); }; @@ -2550,7 +1941,7 @@ toJsonArray = function(buf, obj, pretty){ buf.push('' + obj); } } else if (type === 'string') { - return buf.push(angular.String.quoteUnicode(obj)); + return buf.push(angular['String']['quoteUnicode'](obj)); } else if (type === 'object') { if (obj instanceof Array) { buf.push("["); @@ -2568,7 +1959,7 @@ toJsonArray = function(buf, obj, pretty){ } buf.push("]"); } else if (obj instanceof Date) { - buf.push(angular.String.quoteUnicode(angular.Date.toString(obj))); + buf.push(angular['String']['quoteUnicode'](angular['Date']['toString'](obj))); } else { buf.push("{"); if (pretty) buf.push(pretty); @@ -2590,7 +1981,7 @@ toJsonArray = function(buf, obj, pretty){ buf.push(","); if (pretty) buf.push(pretty); } - buf.push(angular.String.quote(key)); + buf.push(angular['String']['quote'](key)); buf.push(":"); toJsonArray(buf, value, childPretty); comma = true; @@ -2854,7 +2245,7 @@ Lexer.prototype.readString = function(quote) { this.tokens.push({index:start, text:string, fn:function(){ return (string.length == dateParseLength) ? - angular.String.toDate(string) : string; + angular['String']['toDate'](string) : string; }}); return; } else { @@ -3015,11 +2406,11 @@ Parser.prototype.filterChain = function(){ }; Parser.prototype.filter = function(){ - return this._pipeFunction(angular.filter); + return this._pipeFunction(angular['filter']); }; Parser.prototype.validator = function(){ - return this._pipeFunction(angular.validator); + return this._pipeFunction(angular['validator']); }; Parser.prototype._pipeFunction = function(fnScope){ @@ -3366,7 +2757,7 @@ Parser.prototype.entityDecl = function () { self.scope.set(instance, document); return "$anchor." + instance + ":{" + instance + "=" + entity + ".load($anchor." + instance + ");" + - instance + ".$$anchor=" + angular.String.quote(instance) + ";" + + instance + ".$$anchor=" + angular['String']['quote'](instance) + ";" + "};"; } else { return ""; @@ -3465,7 +2856,7 @@ Scope.getter = function(instance, path) { instance = instance[key]; } if (_.isUndefined(instance) && key.charAt(0) == '$') { - var type = angular.Global.typeOf(lastInstance); + var type = angular['Global']['typeOf'](lastInstance); type = angular[type.charAt(0).toUpperCase()+type.substring(1)]; var fn = type ? type[[key.substring(1)]] : undefined; if (fn) { @@ -3622,8 +3013,8 @@ Server.prototype.base64url = function(txt) { Server.prototype.request = function(method, url, request, callback) { var requestId = this.uuid + (this.nextId++); - nglr[requestId] = function(response) { - delete nglr[requestId]; + callbacks[requestId] = function(response) { + delete angular[requestId]; callback(200, response); }; var payload = {u:url, m:method, p:request}; @@ -3798,7 +3189,13 @@ WidgetFactory = function(serverUrl, database) { this.nextUploadId = 0; this.serverUrl = serverUrl; this.database = database; - this.createSWF = swfobject.createSWF; + if (window.swfobject) { + this.createSWF = swfobject.createSWF; + } else { + this.createSWF = function(){ + alert("ERROR: swfobject not loaded!"); + }; + } this.onChangeListener = function(){}; }; @@ -3938,7 +3335,7 @@ FileController.prototype._on_uploadCompleteData = function(data) { FileController.prototype._on_select = function(name, size, type) { this.name = name; this.view.find("a").text(name).attr('href', name); - this.view.find("span").text(angular.filter.bytes(size)); + this.view.find("span").text(angular['filter']['bytes'](size)); this.upload(); }; @@ -3960,7 +3357,7 @@ FileController.prototype.updateView = function(scope) { this.view.find("a"). attr("href", this.value.url). text(this.value.text); - this.view.find("span").text(angular.filter.bytes(this.value.size)); + this.view.find("span").text(angular['filter']['bytes'](this.value.size)); } this.view.find("input").attr('checked', !!modelValue); }; @@ -4470,8 +3867,8 @@ RepeaterUpdater.prototype.updateView = function(scope) { }); // shrink children for ( var r = childrenLength; r > iteratorLength; --r) { - var unneeded = this.children.pop(); - unneeded.element.removeNode(); + var unneeded = this.children.pop().element[0]; + unneeded.parentNode.removeChild(unneeded); } // Special case for option in select if (child && child.element[0].nodeName === "OPTION") { diff --git a/example/calculator-manual_init.html b/example/calculator-manual_init.html index 175d07b9..1a6571c2 100644 --- a/example/calculator-manual_init.html +++ b/example/calculator-manual_init.html @@ -1,7 +1,8 @@ - + + + + + + + + Quantity: + * + Cost: + = {{a * b | currency}} + + diff --git a/src/API.js b/src/API.js index 6fb6e8fc..49089da0 100644 --- a/src/API.js +++ b/src/API.js @@ -1,5 +1,5 @@ -angular.Global = { - typeOf:function(obj){ +angular['Global'] = { + 'typeOf':function(obj){ var type = typeof obj; switch(type) { case "object": @@ -12,10 +12,10 @@ angular.Global = { } }; -angular.Collection = {}; -angular.Object = {}; -angular.Array = { - includeIf:function(array, value, condition) { +angular['Collection'] = {}; +angular['Object'] = {}; +angular['Array'] = { + 'includeIf':function(array, value, condition) { var index = _.indexOf(array, value); if (condition) { if (index == -1) @@ -25,8 +25,8 @@ angular.Array = { } return array; }, - sum:function(array, expression) { - var fn = angular.Function.compile(expression); + 'sum':function(array, expression) { + var fn = angular['Function']['compile'](expression); var sum = 0; for (var i = 0; i < array.length; i++) { var value = 1 * fn(array[i]); @@ -36,15 +36,15 @@ angular.Array = { } return sum; }, - remove:function(array, value) { + 'remove':function(array, value) { var index = _.indexOf(array, value); if (index >=0) array.splice(index, 1); return value; }, - find:function(array, condition, defaultValue) { + 'find':function(array, condition, defaultValue) { if (!condition) return undefined; - var fn = angular.Function.compile(condition); + var fn = angular['Function']['compile'](condition); _.detect(array, function($){ if (fn($)){ defaultValue = $; @@ -53,10 +53,10 @@ angular.Array = { }); return defaultValue; }, - findById:function(array, id) { + 'findById':function(array, id) { return angular.Array.find(array, function($){return $.$id == id;}, null); }, - filter:function(array, expression) { + 'filter':function(array, expression) { var predicates = []; predicates.check = function(value) { for (var j = 0; j < predicates.length; j++) { @@ -136,16 +136,16 @@ angular.Array = { } return filtered; }, - add:function(array, value) { + 'add':function(array, value) { array.push(_.isUndefined(value)? {} : value); return array; }, - count:function(array, condition) { + 'count':function(array, condition) { if (!condition) return array.length; - var fn = angular.Function.compile(condition); + var fn = angular['Function']['compile'](condition); return _.reduce(array, 0, function(count, $){return count + (fn($)?1:0);}); }, - orderBy:function(array, expression, descend) { + 'orderBy':function(array, expression, descend) { function reverse(comp, descending) { return toBoolean(descending) ? function(a,b){return comp(b,a);} : comp; @@ -169,7 +169,7 @@ angular.Array = { descending = $.charAt(0) == '-'; $ = $.substring(1); } - var get = $ ? angular.Function.compile($) : _.identity; + var get = $ ? angular['Function']['compile']($) : _.identity; return reverse(function(a,b){ return compare(get(a),get(b)); }, descending); @@ -183,7 +183,7 @@ angular.Array = { }; return _.clone(array).sort(reverse(comparator, descend)); }, - orderByToggle:function(predicate, attribute) { + 'orderByToggle':function(predicate, attribute) { var STRIP = /^([+|-])?(.*)/; var ascending = false; var index = -1; @@ -205,7 +205,7 @@ angular.Array = { predicate.unshift((ascending ? "-" : "+") + attribute); return predicate; }, - orderByDirection:function(predicate, attribute, ascend, descend) { + 'orderByDirection':function(predicate, attribute, ascend, descend) { ascend = ascend || 'ng-ascend'; descend = descend || 'ng-descend'; var att = predicate[0] || ''; @@ -218,7 +218,7 @@ angular.Array = { } return att == attribute ? (direction ? ascend : descend) : ""; }, - merge:function(array, index, mergeValue) { + 'merge':function(array, index, mergeValue) { var value = array[index]; if (!value) { value = {}; @@ -228,8 +228,8 @@ angular.Array = { return array; } }; -angular.String = { - quote:function(string) { +angular['String'] = { + 'quote':function(string) { return '"' + string.replace(/\\/g, '\\\\'). replace(/"/g, '\\"'). replace(/\n/g, '\\n'). @@ -239,8 +239,8 @@ angular.String = { replace(/\v/g, '\\v') + '"'; }, - quoteUnicode:function(string) { - var str = angular.String.quote(string); + 'quoteUnicode':function(string) { + var str = angular['String']['quote'](string); var chars = []; for ( var i = 0; i < str.length; i++) { var ch = str.charCodeAt(i); @@ -253,7 +253,7 @@ angular.String = { } return chars.join(''); }, - toDate:function(string){ + 'toDate':function(string){ var match; if (typeof string == 'string' && (match = string.match(/^(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)Z$/))){ @@ -265,8 +265,8 @@ angular.String = { return string; } }; -angular.Date = { - toString:function(date){ +angular['Date'] = { + 'toString':function(date){ function pad(n) { return n < 10 ? "0" + n : n; } return (date.getUTCFullYear()) + '-' + pad(date.getUTCMonth() + 1) + '-' + @@ -276,8 +276,8 @@ angular.Date = { pad(date.getUTCSeconds()) + 'Z'; } }; -angular.Function = { - compile:function(expression) { +angular['Function'] = { + 'compile':function(expression) { if (_.isFunction(expression)){ return expression; } else if (expression){ @@ -299,20 +299,20 @@ angular.Function = { dst[name] = _[name]; }); }; - extend(angular.Global, {}, + extend(angular['Global'], {}, ['extend', 'clone','isEqual', 'isElement', 'isArray', 'isFunction', 'isUndefined']); - extend(angular.Collection, angular.Global, + extend(angular['Collection'], angular['Global'], ['each', 'map', 'reduce', 'reduceRight', 'detect', 'select', 'reject', 'all', 'any', 'include', 'invoke', 'pluck', 'max', 'min', 'sortBy', 'sortedIndex', 'toArray', 'size']); - extend(angular.Array, angular.Collection, + extend(angular['Array'], angular['Collection'], ['first', 'last', 'compact', 'flatten', 'without', 'uniq', 'intersect', 'zip', 'indexOf', 'lastIndexOf']); - extend(angular.Object, angular.Collection, + extend(angular['Object'], angular['Collection'], ['keys', 'values']); - extend(angular.String, angular.Global); - extend(angular.Function, angular.Global, + extend(angular['String'], angular['Global']); + extend(angular['Function'], angular['Global'], ['bind', 'bindAll', 'delay', 'defer', 'wrap', 'compose']); })(); \ No newline at end of file diff --git a/src/DataStore.js b/src/DataStore.js index bdf882a0..6eeabb21 100644 --- a/src/DataStore.js +++ b/src/DataStore.js @@ -92,7 +92,7 @@ DataStore.prototype.save = function(document, callback) { var cachedDoc = self.cache(document); _.each(self._cache.$collections, function(collection){ if (collection.$$accept(document)) { - angular.Array.includeIf(collection, cachedDoc, true); + angular['Array']['includeIf'](collection, cachedDoc, true); } }); if (document.$$anchor) { diff --git a/src/Filters.js b/src/Filters.js index dd4217be..b3f56e75 100644 --- a/src/Filters.js +++ b/src/Filters.js @@ -22,7 +22,7 @@ angular.filter.Meta.get = function(obj, attr){ } }; -angular.filter.currency = function(amount){ +angular.filter['currency'] = function(amount){ jQuery(this.element).toggleClass('ng-format-negative', amount < 0); return '$' + angular.filter.number.apply(this, [amount, 2]); }; diff --git a/src/JSON.js b/src/JSON.js index 84c9a857..238ed489 100644 --- a/src/JSON.js +++ b/src/JSON.js @@ -38,7 +38,7 @@ toJsonArray = function(buf, obj, pretty){ buf.push('' + obj); } } else if (type === 'string') { - return buf.push(angular.String.quoteUnicode(obj)); + return buf.push(angular['String']['quoteUnicode'](obj)); } else if (type === 'object') { if (obj instanceof Array) { buf.push("["); @@ -56,7 +56,7 @@ toJsonArray = function(buf, obj, pretty){ } buf.push("]"); } else if (obj instanceof Date) { - buf.push(angular.String.quoteUnicode(angular.Date.toString(obj))); + buf.push(angular['String']['quoteUnicode'](angular['Date']['toString'](obj))); } else { buf.push("{"); if (pretty) buf.push(pretty); @@ -78,7 +78,7 @@ toJsonArray = function(buf, obj, pretty){ buf.push(","); if (pretty) buf.push(pretty); } - buf.push(angular.String.quote(key)); + buf.push(angular['String']['quote'](key)); buf.push(":"); toJsonArray(buf, value, childPretty); comma = true; diff --git a/src/Loader.js b/src/Loader.js index dfaa355a..19840567 100644 --- a/src/Loader.js +++ b/src/Loader.js @@ -23,6 +23,8 @@ if (typeof Node == 'undefined') { } var callbacks = {}; +var jQuery = window['jQuery']; +var msie = jQuery['browser']['msie']; if (!window.angular){ angular = {}; window['angular'] = angular; } if (!angular.validator) angular.validator = {}; @@ -32,8 +34,8 @@ if (!window.console) log:function() {}, error:function() {} }; -if (_.isUndefined(alert)) { - alert = function(){console.log(arguments); window.alert.apply(window, arguments); }; +if (!angular.alert) { + angular.alert = function(){console.log(arguments); window.alert.apply(window, arguments); }; } var consoleNode; @@ -169,7 +171,6 @@ Loader.prototype.load = function() { this.loadCss('/stylesheets/jquery-ui/smoothness/jquery-ui-1.7.1.css'); this.loadCss('/stylesheets/css'); console.log("Server: " + this.config.server); - msie = jQuery.browser.msie; this.configureJQueryPlugins(); this.computeConfiguration(); this.bindHtml(); @@ -177,11 +178,7 @@ Loader.prototype.load = function() { Loader.prototype.configureJQueryPlugins = function() { console.log('Loader.configureJQueryPlugins()'); - jQuery.fn.removeNode = function() { - var node = this.get(0); - node.parentNode.removeChild(node); - }; - jQuery.fn.scope = function() { + jQuery['fn']['scope'] = function() { var element = this; while (element && element.get(0)) { var scope = element.data("scope"); @@ -191,7 +188,7 @@ Loader.prototype.configureJQueryPlugins = function() { } return null; }; - jQuery.fn.controller = function() { + jQuery['fn']['controller'] = function() { return this.data('controller') || NullController.instance; }; }; @@ -229,24 +226,19 @@ Loader.prototype.bindHtml = function() { 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) { + '$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 + '$window' : window, + '$uid' : this.uid, + '$users' : users }, "ROOT"); - jQuery.each(["get", "set", "eval", "addWatchListener", "updateView"], - function(i, method){ - angular[method] = bind(scope, scope[method]); - }); - document.data('scope', scope); console.log('$binder.entity()'); binder.entity(scope); @@ -284,7 +276,6 @@ Loader.prototype.bindHtml = function() { watcher.watch(); document.find("body").show(); console.log('ready()'); - }; Loader.prototype.visualPost = function(delegate) { @@ -399,6 +390,12 @@ angular['compile'] = function(root, config) { //todo: don't start watcher var loader = new Loader(root, jQuery("head"), _(defaults).extend(config)); loader.load(); - return jQuery(root).scope(); + var scope = jQuery(root).scope(); + //TODO: cleanup + return { + 'updateView':function(){return scope.updateView.apply(scope, arguments);}, + 'set':function(){return scope.set.apply(scope, arguments);}, + 'get':function(){return scope.get.apply(scope, arguments);} + }; }; diff --git a/src/Parser.js b/src/Parser.js index b23215be..cdece11e 100644 --- a/src/Parser.js +++ b/src/Parser.js @@ -185,7 +185,7 @@ Lexer.prototype.readString = function(quote) { this.tokens.push({index:start, text:string, fn:function(){ return (string.length == dateParseLength) ? - angular.String.toDate(string) : string; + angular['String']['toDate'](string) : string; }}); return; } else { @@ -346,11 +346,11 @@ Parser.prototype.filterChain = function(){ }; Parser.prototype.filter = function(){ - return this._pipeFunction(angular.filter); + return this._pipeFunction(angular['filter']); }; Parser.prototype.validator = function(){ - return this._pipeFunction(angular.validator); + return this._pipeFunction(angular['validator']); }; Parser.prototype._pipeFunction = function(fnScope){ @@ -697,7 +697,7 @@ Parser.prototype.entityDecl = function () { self.scope.set(instance, document); return "$anchor." + instance + ":{" + instance + "=" + entity + ".load($anchor." + instance + ");" + - instance + ".$$anchor=" + angular.String.quote(instance) + ";" + + instance + ".$$anchor=" + angular['String']['quote'](instance) + ";" + "};"; } else { return ""; diff --git a/src/Scope.js b/src/Scope.js index e3634cee..dff3bfbd 100644 --- a/src/Scope.js +++ b/src/Scope.js @@ -55,7 +55,7 @@ Scope.getter = function(instance, path) { instance = instance[key]; } if (_.isUndefined(instance) && key.charAt(0) == '$') { - var type = angular.Global.typeOf(lastInstance); + var type = angular['Global']['typeOf'](lastInstance); type = angular[type.charAt(0).toUpperCase()+type.substring(1)]; var fn = type ? type[[key.substring(1)]] : undefined; if (fn) { diff --git a/src/Widgets.js b/src/Widgets.js index 3a0f2845..4e4facf8 100644 --- a/src/Widgets.js +++ b/src/Widgets.js @@ -5,7 +5,13 @@ WidgetFactory = function(serverUrl, database) { this.nextUploadId = 0; this.serverUrl = serverUrl; this.database = database; - this.createSWF = swfobject.createSWF; + if (window.swfobject) { + this.createSWF = swfobject.createSWF; + } else { + this.createSWF = function(){ + alert("ERROR: swfobject not loaded!"); + }; + } this.onChangeListener = function(){}; }; @@ -145,7 +151,7 @@ FileController.prototype._on_uploadCompleteData = function(data) { FileController.prototype._on_select = function(name, size, type) { this.name = name; this.view.find("a").text(name).attr('href', name); - this.view.find("span").text(angular.filter.bytes(size)); + this.view.find("span").text(angular['filter']['bytes'](size)); this.upload(); }; @@ -167,7 +173,7 @@ FileController.prototype.updateView = function(scope) { this.view.find("a"). attr("href", this.value.url). text(this.value.text); - this.view.find("span").text(angular.filter.bytes(this.value.size)); + this.view.find("span").text(angular['filter']['bytes'](this.value.size)); } this.view.find("input").attr('checked', !!modelValue); }; @@ -677,8 +683,8 @@ RepeaterUpdater.prototype.updateView = function(scope) { }); // shrink children for ( var r = childrenLength; r > iteratorLength; --r) { - var unneeded = this.children.pop(); - unneeded.element.removeNode(); + var unneeded = this.children.pop().element[0]; + unneeded.parentNode.removeChild(unneeded); } // Special case for option in select if (child && child.element[0].nodeName === "OPTION") { diff --git a/src/test/Runner.js b/src/test/Runner.js index 5840282e..c6684951 100644 --- a/src/test/Runner.js +++ b/src/test/Runner.js @@ -110,7 +110,7 @@ test.Scenario.prototype = { }, verb:function(step){ var fn = null; - if (!step) fn = function (){ throw "Step is null!"; } + if (!step) fn = function (){ throw "Step is null!"; }; else if (step.Given) fn = angular.test.GIVEN[step.Given]; else if (step.When) fn = angular.test.WHEN[step.When]; else if (step.Then) fn = angular.test.THEN[step.Then]; diff --git a/src/test/_namespace.js b/src/test/_namespace.js index 78f430f1..e29ae72a 100644 --- a/src/test/_namespace.js +++ b/src/test/_namespace.js @@ -1,5 +1,5 @@ -if (!angular) angular = {}; -if (!angular.test) angular.test = {}; -if (!angular.test.GIVEN) angular.test.GIVEN = {}; -if (!angular.test.WHEN) angular.test.WHEN = {}; -if (!angular.test.THEN) angular.test.THEN = {}; +if (!angular) var angular = window['angular'] = {}; +if (!angular['test']) var angularTest = angular['test'] = {}; +if (!angular['test']['GIVEN']) angularTest['GIVEN'] = {}; +if (!angular['test']['WHEN']) angularTest['WHEN'] = {}; +if (!angular['test']['THEN']) angularTest['THEN'] = {}; -- cgit v1.2.3 From 1a42a3fab99ca02af0476f5a87175c53104aa2e3 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Mon, 11 Jan 2010 16:15:12 -0800 Subject: green --- angular-minified.js | 208 +++--- angular.js | 1218 ++++++++++++++++++----------------- example/calculator-manual_init.html | 1 + src/API.js | 77 +-- src/Binder.js | 8 +- src/ControlBar.js | 2 +- src/DataStore.js | 18 +- src/Filters.js | 511 ++++++++------- src/JSON.js | 2 +- src/Loader.js | 491 +++++++------- src/Scope.js | 6 +- src/Server.js | 2 +- src/Validators.js | 152 ++--- src/Widgets.js | 4 +- src/angular.prefix | 3 +- test/LoaderTest.js | 4 +- test/ServerTest.js | 4 +- 17 files changed, 1368 insertions(+), 1343 deletions(-) diff --git a/angular-minified.js b/angular-minified.js index ce5155f2..9b4eec6e 100644 --- a/angular-minified.js +++ b/angular-minified.js @@ -1,109 +1,99 @@ -function m(){return function(){}}function u(n){return function(q){this[n]=q}}function w(n){return function(){return n}} -(function(n,q){var z={z:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=",$:function(a){var b="",c,d,e,f,g,h,i=0;for(a=z.Gb(a);i>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.z.charAt(f)+this.z.charAt(c)+this.z.charAt(g)+this.z.charAt(h)}return b},Gd: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=z.Fb(b)},Gb: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},Fb: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 q.getAttribute=="undefined")q.getAttribute=m();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};var x={},j=n.jQuery,A=j.browser.msie;if(!n.ld){angular={};n.angular=angular}if(!angular.h)angular.h={};if(!angular.filter)angular.filter={};if(!n.console)n.console={log:m(),error:m()};if(!angular.alert)angular.alert=function(){console.log(arguments);n.alert.apply(n,arguments)};var y;consoleLog=function(a,b){var c= -q.createElement("div");c.className=a;for(var d=a="",e=0;e/g,">")};escapeAttr=function(a){if(!a||!a.replace)return a;return a.replace(//g,">").replace(/\"/g,""")};bind=function(a,b){if(!a)throw"Missing this";if(!_.isFunction(b))throw"Missing function";return function(){return b.apply(a,arguments)}};shiftBind=function(a,b){return function(){for(var c=[this],d=0;d=0&&a.splice(c,1);return b},find:function(a,b,c){if(b){var d= -angular.Function.compile(b);_.detect(a,function(e){if(d(e)){c=e;return true}});return c}},findById:function(a,b){return angular.gd.find(a,function(c){return c.C==b},null)},filter:function(a,b){var c=[];c.Pb=function(k){for(var l=0;l-1;case "object":for(var o in k)if(o.charAt(0)!== -"$"&&e(k[o],l))return true;return false;case "array":for(o=0;o=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}merge(c,d);return a}};angular.String={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=angular.String.quote(a);for(var b=[],c=0;c-1;){c1||Binder.N(a[0])!==null};Binder.N=function(a){return(a=a.replace(/\n/gm, -" ").match(/^\{\{(.*)\}\}$/))?a[1]:null};Binder.prototype.Jc=function(a){var b={};a.replace(/(?:^|&)([^&=]*)=?([^&]*)/g,function(c,d,e){if(d)b[decodeURIComponent(d)]=decodeURIComponent(e)});return b};Binder.prototype.ib=function(a){var b=this;a=a||this.ya.Va();var c=a.indexOf("#");if(!(c<0)){a=this.Jc(a.substring(c+1));j.each(b.anchor,function(d){delete b.anchor[d]});j.each(a,function(d,e){b.anchor[d]=e})}};Binder.prototype.Ic=function(a){console.log("URL change detected",a);this.ib(a);this.c()}; -Binder.prototype.Vc=function(){var a=this.ya.Va(),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.ya.Rc(a);return a};Binder.prototype.c=function(){(new Date).getTime();var a=j(this.F).scope();a.l("$invalidWidgets",[]);a.c();(new Date).getTime();this.Vc();_.each(this.rb,function(b){b()})};Binder.prototype.Z= -function(a){var b=j(this.F),c=b.find(a);if(b.is(a))c=c.andSelf();return c};Binder.prototype.fc=function(){this.Z("[ng-init]").each(function(){var a=j(this),b=a.scope();try{b.eval(a.attr("ng-init"))}catch(c){alert("EVAL ERROR:\n"+a.attr("ng-init")+"\n"+toJson(c,true))}})};Binder.prototype.O=function(a){this.Z("[ng-entity]").attr("ng-watch",function(){try{var b=j(this);return a.O(b.attr("ng-entity"))+(b.attr("ng-watch")||"")}catch(c){alert(c)}})};Binder.prototype.compile=function(){var a=j(this.F), -b=this;if(this.k.nd){var c=this.Z(":submit").not("[ng-action]");c.attr("ng-action","$save()");c.not(":disabled").not("ng-bind-attr").attr("ng-bind-attr",'{disabled:"{{$invalidWidgets}}"}')}this.jb(this.F)(this.F,a.scope(),"");this.Z("a[ng-action]").live("click",function(){var d=j(this);try{d.scope().eval(d.attr("ng-action"));d.removeAttr("ng-error");d.removeClass("ng-exception")}catch(e){d.addClass("ng-exception");d.attr("ng-error",toJson(e,true))}b.c();return false})};Binder.prototype.Tc=function(a, -b,c){b=b.concat();var d=b.pop(),e=Binder.ga(a.nodeValue);if(e.length>1||Binder.N(e[0])){var f=a.parentNode;if(isLeafNode(f)){f.setAttribute("ng-bind-template",a.nodeValue);c.push({path:b,b:function(l){return new BindUpdater(l,l.getAttribute("ng-bind-template"))}})}else for(var g=0;g2&&a.setAttribute("ng-bind-attr",d)}a.getAttribute||console.log(a);var k=a.getAttribute("ng-repeat");if(k){a.removeAttribute("ng-repeat");var l=this.jb(a);d=q.createComment("ng-repeat: "+k);e=a.parentNode;e.insertBefore(d,a); -e.removeChild(a);var o=function(p,r,s){var v=j(a).clone();v.css("display","");v.attr("ng-repeat-index",""+s);v.data("scope",p);l(v[0],p,r+s+":");return v};c.push({path:b,b:function(p,r,s){return new RepeaterUpdater(j(p),k,o,s)}})}else{a.getAttribute("ng-eval")&&c.push({path:b,b:this.Ac});a.getAttribute("ng-bind")&&c.push({path:b,b:this.hb});a.getAttribute("ng-bind-attr")&&c.push({path:b,b:this.wc});a.getAttribute("ng-hide")&&c.push({path:b,b:this.Bc});a.getAttribute("ng-show")&&c.push({path:b,b:this.Cc}); -a.getAttribute("ng-class")&&c.push({path:b,b:this.xc});a.getAttribute("ng-class-odd")&&c.push({path:b,b:this.zc});a.getAttribute("ng-class-even")&&c.push({path:b,b:this.yc});a.getAttribute("ng-style")&&c.push({path:b,b:this.Dc});a.getAttribute("ng-watch")&&c.push({path:b,b:this.Ec});d=a.nodeName;if(d=="INPUT"||d=="TEXTAREA"||d=="SELECT"||d=="BUTTON"){var t=this;c.push({path:b,b:function(p,r,s){p.name=s+p.name.split(":").pop();return t.ad.Ub(j(p),r)}})}if(d=="OPTION")if(!j("')};FileController.prototype.e=function(a){var b=this.view.find("input").attr("checked")?this.value:null;if(this.d===b)return false;else{a.l(this.nb,b);return true}};FileController.prototype.c=function(a){if((a=a.get(this.nb))&&this.value!==a){this.value=a;this.view.find("a").attr("href",this.value.url).text(this.value.text); -this.view.find("span").text(angular.filter.bytes(this.value.size))}this.view.find("input").attr("checked",!!a)};NullController=u("view");NullController.prototype.e=w(true);NullController.prototype.c=m();NullController.lc=new NullController;ButtonController=u("view");ButtonController.prototype.e=w(true);ButtonController.prototype.c=m();TextController=function(a,b){this.view=a;this.exp=b;this.h=a.getAttribute("ng-validate");this.Nc=typeof a.attributes["ng-required"]!="undefined";this.Ya=null;this.d= -undefined;this.s=a.value;a.getAttribute("ng-widget")==="datepicker"&&j(a).Fd()};TextController.prototype.e=function(a){var b=this.view.value;if(this.d===b)return false;else{a.p(this.exp,b);this.d=b;return true}};TextController.prototype.c=function(a){var b=this.view,c=a.get(this.exp);if(typeof c==="undefined"){c=this.s;a.p(this.exp,c)}c=c?c:"";if(this.d!=c)this.d=b.value=c;var d=false;b.removeAttribute("ng-error");if(this.Nc)d=!(c&&c.length>0);var e=d?"Required Value":null;if(!d&&this.h&&c){e=a.Yc(this.h, -c);d=!!e}if(this.Ya!==e){this.Ya=d;if(e!==null){b.setAttribute("ng-error",e);a.sc(this)}j(b).toggleClass("ng-validation-error",d)}};CheckboxController=function(a,b){this.view=a;this.exp=b;this.d=undefined;this.s=a.checked?a.value:""};CheckboxController.prototype.e=function(a){var b=this.view;b=b.checked?b.value:"";if(this.d===b)return false;else{a.p(this.exp,b);this.d=b;return true}};CheckboxController.prototype.c=function(a){var b=this.view,c=a.eval(this.exp);if(typeof c==="undefined"){c=this.s; -a.p(this.exp,c)}b.checked=b.value==""+c};SelectController=function(a,b){this.view=a;this.exp=b;this.d=undefined;this.s=a.value};SelectController.prototype.e=function(a){if(this.view.selectedIndex<0)a.p(this.exp,null);else{var b=this.view.value;if(this.d===b)return false;else{a.p(this.exp,b);this.d=b;return true}}};SelectController.prototype.c=function(a){var b=this.view,c=a.get(this.exp);if(typeof c==="undefined"){c=this.s;a.p(this.exp,c)}if(c!==this.d){b.value=c?c:"";this.d=c}};MultiSelectController= -function(a,b){this.view=a;this.exp=b;this.d=undefined;this.s=this.selected()};MultiSelectController.prototype.selected=function(){for(var a=[],b=this.view.options,c=0;cd;--b){var o= -this.children.pop().element[0];o.parentNode.removeChild(o)}if(h&&h.element[0].nodeName==="OPTION")if(d=j(h.element[0].parentNode).data("controller")){d.d=undefined;d.c(a)}})};PopUp=u("F");PopUp.Fa="mouseleave mouseout click dblclick keypress keyup";PopUp.prototype.bind=function(){this.F.find(".ng-validation-error,.ng-exception").live("mouseover",PopUp.Hc)};PopUp.Hc=function(){PopUp.wa();var a=j(this);a.bind(PopUp.Fa,PopUp.wa);var b=a.position(),c=q.documentElement,d=(self.innerWidth||c&&c.clientWidth|| -q.body.clientWidth)-b.left;c=a.hasClass("ng-exception")?"EXCEPTION:":"Validation error...";a=a.attr("ng-error");d=d>375?"left":"right";c=j("
      "+c+"
      "+a+"
      ");j("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};PopUp.wa=function(){j("#ng-callout").unbind(PopUp.Fa, -PopUp.wa).remove();return true};Status=function(a){this.cb=a.append(Status.yb).find("#ng-loading");this.ja=0};Status.yb='
      loading....
      ';Status.prototype.Nb=function(){this.ja===0&&this.cb.show();this.ja++};Status.prototype.cc=function(){this.ja--;this.ja===0&&this.cb.hide("fold")}})(window,document); +function p(){return function(){}}function A(n){return function(r){this[n]=r}}function E(n){return function(){return n}} +(function(n,r){function x(){}function M(a,b){var c=r.createElement("div");c.className=a;for(var d=a="",e=0;e/g,">")}function X(a){if(!a||!a.replace)return a;return a.replace(//g,">").replace(/\"/g,""")}function P(a,b){if(!a)throw"Missing this";if(!_.isFunction(b))throw"Missing function";return function(){return b.apply(a,arguments)}}function Y(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},sd: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=H.xb(b)},yb: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},xb: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 r.getAttribute=="undefined")r.getAttribute=p();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};var F,o=_.each,V=_.extend,I=n.console||{log:x,error:x},i=n.jQuery,G=i.browser.msie;function q(){I.log.apply(this,arguments)}function J(){I.error.apply(this,arguments)}var m=n.angular||(n.angular={}),W=m.validator||(m.validator={}),s=m.filter||(m.filter={}),K=m.callbacks||(m.callbacks={});m.alert||(m.alert=function(){q(arguments);n.alert.apply(n,arguments)}); +T.prototype={load:function(){this.Lb();this.Wa("/stylesheets/jquery-ui/smoothness/jquery-ui-1.7.1.css");this.Wa("/stylesheets/css");q("Server: "+this.i.z);this.Kb();this.Jb();this.Gb()},Kb:function(){q("Loader.configureJQueryPlugins()");i.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};i.fn.controller=function(){return this.data("controller")||NullController.cc}},Kc:function(){return""+(new Date).getTime()},Jb:function(){var a=this.i; +if(!a.G){var b=a.z.match(/https?:\/\/([\w]*)/);a.G=b?b[1]:"$MEMORY"}},Gb:function(){function a(){g.Ma(function(l){!l&&c.find("[ng-auth=eager]").length&&g.ba()})}q("Loader.bindHtml()");var b=new U(this.location),c=this.document,d=new WidgetFactory(this.i.z,this.i.G),e=new Binder(c[0],d,b,this.i);d.wc=Y(e,e.e);d=new ControlBar(c.find("body"),this.i.z);var f=this.i.G=="$MEMORY"?new FrameServer(this.window):new Server(this.i.z,i.getScript);f=new VisualServer(f,new Status(i(c.body)),function(){e.c()}); +var g=new Users(f,d),h="/data/"+this.i.G,j=new DataStore(function(l,y){f.P("POST",h,l,y)},g,e.anchor);e.lb.push(function(){j.Oa()});var k=new Scope({$anchor:e.anchor,$binder:e,$config:this.i,$console:n.console,$datastore:j,$save:function(l){j.Ec(k.s,l,e.anchor)},$window:n,$uid:this.Kc,$users:g},"ROOT");c.data("scope",k);q("$binder.entity()");e.M(k);q("$binder.compile()");e.compile();q("ControlBar.bind()");d.bind();q("$users.fetchCurrentUser()");a();q("PopUp.bind()");(new PopUp(c)).bind();q("$binder.parseAnchor()"); +e.db();q("$binder.executeInit()");e.Xb();q("$binder.updateView()");e.c();b.Ua=P(e,e.yc,b);b.zd=function(){alert("update")};b.watch();c.find("body").show();q("ready()")},Lb:function(){var a=n.location.href+"#";a=a.split("#")[1];var b={Qb:null};a=a.split("&");for(var c=0;c-1;){c1||Binder.L(a[0])!==null};Binder.L=function(a){return(a=a.replace(/\n/gm," ").match(/^\{\{(.*)\}\}$/))?a[1]:null};Binder.prototype.zc=function(a){var b={};a.replace(/(?:^|&)([^&=]*)=?([^&]*)/g,function(c,d,e){if(d)b[decodeURIComponent(d)]=decodeURIComponent(e)});return b};Binder.prototype.db=function(a){var b=this;a=a||this.va.Qa();var c=a.indexOf("#");if(!(c<0)){a=this.zc(a.substring(c+1));o(b.anchor,function(d,e){delete b.anchor[e]}); +o(a,function(d,e){b.anchor[e]=d})}};Binder.prototype.yc=function(a){q("URL change detected",a);this.db(a);this.c()};Binder.prototype.Lc=function(){var a=this.va.Qa(),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.va.Hc(a);return a};Binder.prototype.c=function(){(new Date).getTime();var a=i(this.B).scope(); +a.j("$invalidWidgets",[]);a.c();(new Date).getTime();this.Lc();_.each(this.lb,function(b){b()})};Binder.prototype.W=function(a){var b=i(this.B),c=b.find(a);if(b.is(a))c=c.andSelf();return c};Binder.prototype.Xb=function(){this.W("[ng-init]").each(function(){var a=i(this),b=a.scope();try{b.eval(a.attr("ng-init"))}catch(c){alert("EVAL ERROR:\n"+a.attr("ng-init")+"\n"+toJson(c,true))}})};Binder.prototype.M=function(a){this.W("[ng-entity]").attr("ng-watch",function(){try{var b=i(this);return a.M(b.attr("ng-entity"))+ +(b.attr("ng-watch")||"")}catch(c){alert(c)}})};Binder.prototype.compile=function(){var a=i(this.B),b=this;if(this.i.$c){var c=this.W(":submit").not("[ng-action]");c.attr("ng-action","$save()");c.not(":disabled").not("ng-bind-attr").attr("ng-bind-attr",'{disabled:"{{$invalidWidgets}}"}')}this.eb(this.B)(this.B,a.scope(),"");this.W("a[ng-action]").live("click",function(){var d=i(this);try{d.scope().eval(d.attr("ng-action"));d.removeAttr("ng-error");d.removeClass("ng-exception")}catch(e){d.addClass("ng-exception"); +d.attr("ng-error",toJson(e,true))}b.c();return false})};Binder.prototype.Jc=function(a,b,c){b=b.concat();var d=b.pop(),e=Binder.da(a.nodeValue);if(e.length>1||Binder.L(e[0])){var f=a.parentNode;if(N(f)){f.setAttribute("ng-bind-template",a.nodeValue);c.push({path:b,b:function(l){return new BindUpdater(l,l.getAttribute("ng-bind-template"))}})}else for(var g=0;g2&&a.setAttribute("ng-bind-attr",d)}a.getAttribute||q(a);var k=a.getAttribute("ng-repeat");if(k){a.removeAttribute("ng-repeat");var l=this.eb(a);d=r.createComment("ng-repeat: "+k);e=a.parentNode; +e.insertBefore(d,a);e.removeChild(a);var y=function(t,u,w){var B=i(a).clone();B.css("display","");B.attr("ng-repeat-index",""+w);B.data("scope",t);l(B[0],t,u+w+":");return B};c.push({path:b,b:function(t,u,w){return new RepeaterUpdater(i(t),k,y,w)}})}else{a.getAttribute("ng-eval")&&c.push({path:b,b:this.qc});a.getAttribute("ng-bind")&&c.push({path:b,b:this.cb});a.getAttribute("ng-bind-attr")&&c.push({path:b,b:this.mc});a.getAttribute("ng-hide")&&c.push({path:b,b:this.rc});a.getAttribute("ng-show")&& +c.push({path:b,b:this.sc});a.getAttribute("ng-class")&&c.push({path:b,b:this.nc});a.getAttribute("ng-class-odd")&&c.push({path:b,b:this.pc});a.getAttribute("ng-class-even")&&c.push({path:b,b:this.oc});a.getAttribute("ng-style")&&c.push({path:b,b:this.tc});a.getAttribute("ng-watch")&&c.push({path:b,b:this.uc});d=a.nodeName;if(d=="INPUT"||d=="TEXTAREA"||d=="SELECT"||d=="BUTTON"){var z=this;c.push({path:b,b:function(t,u,w){t.name=w+t.name.split(":").pop();return z.Rc.Mb(i(t),u)}})}if(d=="OPTION")if(!i("')};FileController.prototype.e=function(a){var b=this.view.find("input").attr("checked")?this.value:null;if(this.d===b)return false;else{a.j(this.ib,b);return true}};FileController.prototype.c= +function(a){if((a=a.get(this.ib))&&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)};NullController=A("view");NullController.prototype.e=E(true);NullController.prototype.c=p();NullController.cc=new NullController;ButtonController=A("view");ButtonController.prototype.e=E(true);ButtonController.prototype.c=p();TextController=function(a,b){this.view= +a;this.exp=b;this.Q=a.getAttribute("ng-validate");this.Dc=typeof a.attributes["ng-required"]!="undefined";this.Ta=null;this.d=undefined;this.p=a.value;a.getAttribute("ng-widget")==="datepicker"&&i(a).rd()};TextController.prototype.e=function(a){var b=this.view.value;if(this.d===b)return false;else{a.m(this.exp,b);this.d=b;return true}};TextController.prototype.c=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.d!=c)this.d=b.value=c; +var d=false;b.removeAttribute("ng-error");if(this.Dc)d=!(c&&c.length>0);var e=d?"Required Value":null;if(!d&&this.Q&&c){e=a.Oc(this.Q,c);d=!!e}if(this.Ta!==e){this.Ta=d;if(e!==null){b.setAttribute("ng-error",e);a.ic(this)}i(b).toggleClass("ng-validation-error",d)}};CheckboxController=function(a,b){this.view=a;this.exp=b;this.d=undefined;this.p=a.checked?a.value:""};CheckboxController.prototype.e=function(a){var b=this.view;b=b.checked?b.value:"";if(this.d===b)return false;else{a.m(this.exp,b);this.d= +b;return true}};CheckboxController.prototype.c=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};SelectController=function(a,b){this.view=a;this.exp=b;this.d=undefined;this.p=a.value};SelectController.prototype.e=function(a){if(this.view.selectedIndex<0)a.m(this.exp,null);else{var b=this.view.value;if(this.d===b)return false;else{a.m(this.exp,b);this.d=b;return true}}};SelectController.prototype.c=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.d){b.value=c?c:"";this.d=c}};MultiSelectController=function(a,b){this.view=a;this.exp=b;this.d=undefined;this.p=this.selected()};MultiSelectController.prototype.selected=function(){for(var a=[],b=this.view.options,c=0;cd;--b){var y=this.children.pop().element[0];y.parentNode.removeChild(y)}if(h&&h.element[0].nodeName==="OPTION")if(d=i(h.element[0].parentNode).data("controller")){d.d=undefined;d.c(a)}})};PopUp=A("B");PopUp.Ca="mouseleave mouseout click dblclick keypress keyup";PopUp.prototype.bind=function(){this.B.find(".ng-validation-error,.ng-exception").live("mouseover", +PopUp.xc)};PopUp.xc=function(){PopUp.sa();var a=i(this);a.bind(PopUp.Ca,PopUp.sa);var b=a.position(),c=r.documentElement,d=(self.innerWidth||c&&c.clientWidth||r.body.clientWidth)-b.left;c=a.hasClass("ng-exception")?"EXCEPTION:":"Validation error...";a=a.attr("ng-error");d=d>375?"left":"right";c=i("
      "+c+"
      "+a+"
      ");i("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};PopUp.sa=function(){i("#ng-callout").unbind(PopUp.Ca,PopUp.sa).remove();return true};Status=function(a){this.Ya=a.append(Status.sb).find("#ng-loading");this.ga=0};Status.sb='
      loading....
      ';Status.prototype.Fb=function(){this.ga===0&&this.Ya.show();this.ga++};Status.prototype.Ub=function(){this.ga--;this.ga===0&&this.Ya.hide("fold")}})(window, +document); diff --git a/angular.js b/angular.js index c113d56b..bea8200d 100644 --- a/angular.js +++ b/angular.js @@ -1,5 +1,5 @@ - -(function(window, document){/** +(function(window, document){ +/** * * Base64 encode / decode * http://www.webtoolkit.info/ @@ -145,8 +145,7 @@ var Base64 = { // IE compatibility if (typeof document.getAttribute == 'undefined') - document.getAttribute = function() { - }; + document.getAttribute = function() {}; if (typeof Node == 'undefined') { Node = { ELEMENT_NODE : 1, @@ -164,25 +163,25 @@ if (typeof Node == 'undefined') { }; } -var callbacks = {}; -var jQuery = window['jQuery']; -var msie = jQuery['browser']['msie']; - -if (!window.angular){ angular = {}; window['angular'] = angular; } -if (!angular.validator) angular.validator = {}; -if (!angular.filter) angular.filter = {}; -if (!window.console) - window.console = { - log:function() {}, - error:function() {} - }; -if (!angular.alert) { - angular.alert = function(){console.log(arguments); window.alert.apply(window, arguments); }; -} - -var consoleNode; +function noop() {}; + +var consoleNode, + foreach = _.each, + extend = _.extend, + console = window['console'] || ({'log':noop, 'error':noop }), + jQuery = window['jQuery'], + msie = jQuery['browser']['msie'], + log = function(){console.log.apply(this, arguments);}, + error = function(){console.error.apply(this, arguments);}, + angular = window['angular'] || (window['angular'] = {}), + angularValidator = angular['validator'] || (angular['validator'] = {}), + angularFilter = angular['filter'] || (angular['filter'] = {}), + angularCallbacks = angular['callbacks'] || (angular['callbacks'] = {}), + angularAlert = angular['alert'] || (angular['alert'] = function(){ + log(arguments); window.alert.apply(window, arguments); + }); -consoleLog = function(level, objs) { +function consoleLog(level, objs) { var log = document.createElement("div"); log.className = level; var msg = ""; @@ -194,17 +193,17 @@ consoleLog = function(level, objs) { } log.appendChild(document.createTextNode(msg)); consoleNode.appendChild(log); -}; +} -isNode = function(inp) { +function isNode(inp) { return inp && inp.tagName && inp.nodeName && inp.ownerDocument && inp.removeAttribute; -}; +} -isLeafNode = function(node) { +function isLeafNode (node) { switch (node.nodeName) { case "OPTION": case "PRE": @@ -215,9 +214,7 @@ isLeafNode = function(node) { } }; -noop = function() { -}; -setHtml = function(node, html) { +function setHtml(node, html) { if (isLeafNode(node)) { if (msie) { node.innerText = html; @@ -227,9 +224,9 @@ setHtml = function(node, html) { } else { node.innerHTML = html; } -}; +} -escapeHtml = function(html) { +function escapeHtml(html) { if (!html || !html.replace) return html; return html. @@ -238,14 +235,14 @@ escapeHtml = function(html) { replace(/>/g, '>'); }; -escapeAttr = function(html) { +function escapeAttr(html) { if (!html || !html.replace) return html; return html.replace(//g, '>').replace(/\"/g, '"'); }; -bind = function(_this, _function) { +function bind(_this, _function) { if (!_this) throw "Missing this"; if (!_.isFunction(_function)) @@ -255,7 +252,7 @@ bind = function(_this, _function) { }; }; -shiftBind = function(_this, _function) { +function shiftBind(_this, _function) { return function() { var args = [ this ]; for ( var i = 0; i < arguments.length; i++) { @@ -265,7 +262,7 @@ shiftBind = function(_this, _function) { }; }; -outerHTML = function(node) { +function outerHTML(node) { var temp = document.createElement('div'); temp.appendChild(node); var outerHTML = temp.innerHTML; @@ -273,18 +270,18 @@ outerHTML = function(node) { return outerHTML; }; -trim = function(str) { +function trim(str) { return str.replace(/^ */, '').replace(/ *$/, ''); }; -toBoolean = function(value) { +function toBoolean(value) { var v = ("" + value).toLowerCase(); if (v == 'f' || v == '0' || v == 'false' || v == 'no') value = false; return !!value; }; -merge = function(src, dst) { +function merge(src, dst) { for ( var key in src) { var value = dst[key]; var type = typeof value; @@ -301,176 +298,178 @@ merge = function(src, dst) { // Loader // //////////////////////////// -Loader = function(document, head, config) { +function Loader(document, head, config) { this.document = jQuery(document); this.head = jQuery(head); this.config = config; this.location = window.location; }; -Loader.prototype.load = function() { - this.configureLogging(); - this.loadCss('/stylesheets/jquery-ui/smoothness/jquery-ui-1.7.1.css'); - this.loadCss('/stylesheets/css'); - console.log("Server: " + this.config.server); - this.configureJQueryPlugins(); - this.computeConfiguration(); - this.bindHtml(); -}; - -Loader.prototype.configureJQueryPlugins = function() { - console.log('Loader.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; - }; -}; - -Loader.prototype.uid = function() { - return "" + new Date().getTime(); -}; - -Loader.prototype.computeConfiguration = function() { - var config = this.config; - if (!config.database) { - var match = config.server.match(/https?:\/\/([\w]*)/); - config.database = match ? match[1] : "$MEMORY"; - } -}; - -Loader.prototype.bindHtml = function() { - console.log('Loader.bindHtml()'); - var 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); - console.log('$binder.entity()'); - binder.entity(scope); - - console.log('$binder.compile()'); - binder.compile(); - - console.log('ControlBar.bind()'); - controlBar.bind(); - - console.log('$users.fetchCurrentUser()'); - function fetchCurrentUser() { - users.fetchCurrentUser(function(u) { - if (!u && document.find("[ng-auth=eager]").length) { - users.login(); - } - }); - } - fetchCurrentUser(); - - console.log('PopUp.bind()'); - new PopUp(document).bind(); - - console.log('$binder.parseAnchor()'); - binder.parseAnchor(); - - console.log('$binder.executeInit()'); - binder.executeInit(); - - console.log('$binder.updateView()'); - binder.updateView(); - - watcher.listener = bind(binder, binder.onUrlChange, watcher); - watcher.onUpdate = function(){alert("update");}; - watcher.watch(); - document.find("body").show(); - console.log('ready()'); -}; - -Loader.prototype.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)); +Loader.prototype = { + load: function() { + this.configureLogging(); + this.loadCss('/stylesheets/jquery-ui/smoothness/jquery-ui-1.7.1.css'); + this.loadCss('/stylesheets/css'); + log("Server: " + this.config.server); + this.configureJQueryPlugins(); + this.computeConfiguration(); + this.bindHtml(); + }, + + configureJQueryPlugins: function() { + log('Loader.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; }; - delegate(request, callback); - }; -}; - -Loader.prototype.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); - console.log = function() { - consoleLog('ng-console-info', arguments); + 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('Loader.bindHtml()'); + var 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(); + + log('$binder.executeInit()'); + binder.executeInit(); + + log('$binder.updateView()'); + binder.updateView(); + + watcher.listener = bind(binder, binder.onUrlChange, watcher); + watcher.onUpdate = function(){alert("update");}; + watcher.watch(); + 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); }; - console.error = function() { - consoleLog('ng-console-error', arguments); + }, + + 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); + }; + } + }, + + loadCss: function(css) { + var cssTag = document.createElement('link'); + cssTag.rel = "stylesheet"; + cssTag.type = "text/css"; + if (!css.match(/^http:/)) + css = this.config.server + css; + cssTag.href = css; + this.head[0].appendChild(cssTag); } }; -Loader.prototype.loadCss = function(css) { - var cssTag = document.createElement('link'); - cssTag.rel = "stylesheet"; - cssTag.type = "text/css"; - if (!css.match(/^http:/)) - css = this.config.server + css; - cssTag.href = css; - this.head[0].appendChild(cssTag); -}; - -UrlWatcher = function(location) { +function UrlWatcher(location) { this.location = location; this.delay = 25; this.setTimeout = function(fn, delay) { @@ -482,47 +481,49 @@ UrlWatcher = function(location) { this.expectedUrl = location.href; }; -UrlWatcher.prototype.watch = function() { - var self = this; - var pull = function() { - if (self.expectedUrl !== self.location.href) { - var notify = self.location.hash.match(/^#\$iframe_notify=(.*)$/); - if (notify) { - if (!self.expectedUrl.match(/#/)) { - self.expectedUrl += "#"; - } - self.location.href = self.expectedUrl; - var id = '_iframe_notify_' + notify[1]; - var notifyFn = callbacks[id]; - delete callbacks[id]; - try { - (notifyFn||noop)(); - } catch (e) { - alert(e); +UrlWatcher.prototype = { + watch: function() { + var self = this; + var pull = function() { + if (self.expectedUrl !== self.location.href) { + var notify = self.location.hash.match(/^#\$iframe_notify=(.*)$/); + if (notify) { + if (!self.expectedUrl.match(/#/)) { + self.expectedUrl += "#"; + } + self.location.href = self.expectedUrl; + var id = '_iframe_notify_' + notify[1]; + var notifyFn = angularCallbacks[id]; + delete angularCallbacks[id]; + try { + (notifyFn||noop)(); + } catch (e) { + alert(e); + } + } else { + self.listener(self.location.href); + self.expectedUrl = self.location.href; } - } else { - self.listener(self.location.href); - self.expectedUrl = self.location.href; } - } - self.setTimeout(pull, self.delay); - }; - pull(); -}; - -UrlWatcher.prototype.setUrl = function(url) { - var existingURL = window.location.href; - if (!existingURL.match(/#/)) - existingURL += '#'; - if (existingURL != url) - window.location.href = url; - this.existingURL = url; -}; - -UrlWatcher.prototype.getUrl = function() { - return window.location.href; + self.setTimeout(pull, self.delay); + }; + pull(); + }, + + setUrl: function(url) { + var existingURL = window.location.href; + if (!existingURL.match(/#/)) + existingURL += '#'; + if (existingURL != url) + window.location.href = url; + this.existingURL = url; + }, + + getUrl: function() { + return window.location.href; + } }; - + angular['compile'] = function(root, config) { config = config || {}; var defaults = { @@ -539,14 +540,11 @@ angular['compile'] = function(root, config) { 'set':function(){return scope.set.apply(scope, arguments);}, 'get':function(){return scope.get.apply(scope, arguments);} }; -}; - -angular['Global'] = { +};var angularGlobal = { 'typeOf':function(obj){ + if (obj === null) return "null"; var type = typeof obj; - switch(type) { - case "object": - if (obj === null) return "null"; + if (type == "object") { if (obj instanceof Array) return "array"; if (obj instanceof Date) return "date"; if (obj.nodeType == 1) return "element"; @@ -555,9 +553,9 @@ angular['Global'] = { } }; -angular['Collection'] = {}; -angular['Object'] = {}; -angular['Array'] = { +var angularCollection = {}; +var angularObject = {}; +var angularArray = { 'includeIf':function(array, value, condition) { var index = _.indexOf(array, value); if (condition) { @@ -720,7 +718,7 @@ angular['Array'] = { var comparator = function(o1, o2){ for ( var i = 0; i < expression.length; i++) { var comp = expression[i](o1, o2); - if (comp != 0) return comp; + if (comp !== 0) return comp; } return 0; }; @@ -740,7 +738,7 @@ angular['Array'] = { ascending = $.charAt(0) == '+'; index = i; return true; - }; + } }); if (index >= 0) { predicate.splice(index, 1); @@ -771,7 +769,8 @@ angular['Array'] = { return array; } }; -angular['String'] = { + +var angularString = { 'quote':function(string) { return '"' + string.replace(/\\/g, '\\\\'). replace(/"/g, '\\"'). @@ -808,7 +807,8 @@ angular['String'] = { return string; } }; -angular['Date'] = { + +var angularDate = { 'toString':function(date){ function pad(n) { return n < 10 ? "0" + n : n; } return (date.getUTCFullYear()) + '-' + @@ -819,7 +819,8 @@ angular['Date'] = { pad(date.getUTCSeconds()) + 'Z'; } }; -angular['Function'] = { + +var angularFunction = { 'compile':function(expression) { if (_.isFunction(expression)){ return expression; @@ -836,27 +837,29 @@ angular['Function'] = { }; (function(){ - function extend(dst, src, names){ - _.extend(dst, src); - _.each((names||[]), function(name){ - dst[name] = _[name]; + function define(dst, chain, names){ + foreach(chain, function(parent){ + extend(angular[dst], parent); }); - }; - extend(angular['Global'], {}, + foreach(names, function(name){ + angular[dst][name] = _[name]; + }); + } + define('Global', [angularGlobal], ['extend', 'clone','isEqual', 'isElement', 'isArray', 'isFunction', 'isUndefined']); - extend(angular['Collection'], angular['Global'], + define('Collection', [angularGlobal, angularCollection], ['each', 'map', 'reduce', 'reduceRight', 'detect', 'select', 'reject', 'all', 'any', 'include', 'invoke', 'pluck', 'max', 'min', 'sortBy', 'sortedIndex', 'toArray', 'size']); - extend(angular['Array'], angular['Collection'], + define('Array', [angularGlobal, angularCollection], ['first', 'last', 'compact', 'flatten', 'without', 'uniq', 'intersect', 'zip', 'indexOf', 'lastIndexOf']); - extend(angular['Object'], angular['Collection'], + define('Object', [angularGlobal, angularCollection], ['keys', 'values']); - extend(angular['String'], angular['Global']); - extend(angular['Function'], angular['Global'], + define('String', [angularGlobal]); + define('Function', [angularGlobal], ['bind', 'bindAll', 'delay', 'defer', 'wrap', 'compose']); })();// Copyright (C) 2009 BRAT Tech LLC Binder = function(doc, widgetFactory, urlWatcher, config) { @@ -917,16 +920,16 @@ Binder.prototype.parseAnchor = function(url) { var anchor = url.substring(anchorIndex + 1); var anchorQuery = this.parseQueryString(anchor); - jQuery.each(self.anchor, function(key, newValue) { + foreach(self.anchor, function(newValue, key) { delete self.anchor[key]; }); - jQuery.each(anchorQuery, function(key, newValue) { + foreach(anchorQuery, function(newValue, key) { self.anchor[key] = newValue; }); }; Binder.prototype.onUrlChange = function (url) { - console.log("URL change detected", url); + log("URL change detected", url); this.parseAnchor(url); this.updateView(); }; @@ -1112,7 +1115,7 @@ Binder.prototype.precompileNode = function(node, path, factories) { } } - if (!node.getAttribute) console.log(node); + if (!node.getAttribute) log(node); var repeaterExpression = node.getAttribute('ng-repeat'); if (repeaterExpression) { node.removeAttribute('ng-repeat'); @@ -1262,7 +1265,7 @@ ControlBar.prototype.doTemplate = function (path) { callbacks["_iframe_notify_" + id] = function() { loginView.dialog("destroy"); loginView.remove(); - jQuery.each(self.callbacks, function(i, callback){ + foreach(self.callbacks, function(callback){ callback(); }); self.callbacks = []; @@ -1322,7 +1325,7 @@ DataStore.prototype.loadMany = function(entity, ids, callback) { var self=this; var list = []; var callbackCount = 0; - jQuery.each(ids, function(i, id){ + foreach(ids, function(id){ list.push(self.load(entity(), id, function(){ callbackCount++; if (callbackCount == ids.length) { @@ -1415,9 +1418,9 @@ DataStore.prototype.flush = function() { var self = this; var bulkRequest = this.bulkRequest; this.bulkRequest = []; - console.log('REQUEST:', bulkRequest); + log('REQUEST:', bulkRequest); function callback(code, bulkResponse){ - console.log('RESPONSE[' + code + ']: ', bulkResponse); + log('RESPONSE[' + code + ']: ', bulkResponse); if(bulkResponse.$status_code == 401) { self.users.login(function(){ self.post(bulkRequest, callback); @@ -1498,7 +1501,7 @@ DataStore.prototype.documentCountsByUser = function(){ var counts = {}; var self = this; self.post([["GET", "$users"]], function(code, response){ - jQuery.each(response[0], function(key, value){ + foreach(response[0], function(value, key){ counts[key] = value; }); }); @@ -1509,7 +1512,7 @@ DataStore.prototype.userDocumentIdsByEntity = function(user){ var ids = {}; var self = this; self.post([["GET", "$users/" + user]], function(code, response){ - jQuery.each(response[0], function(key, value){ + foreach(response[0], function(value, key){ ids[key] = value; }); }); @@ -1613,14 +1616,14 @@ DataStore.prototype.join = function(join){ }; // Copyright (C) 2009 BRAT Tech LLC -angular.filter.Meta = function(obj){ +angularFilter.Meta = function(obj){ if (obj) { for ( var key in obj) { this[key] = obj[key]; } } }; -angular.filter.Meta.get = function(obj, attr){ +angularFilter.Meta.get = function(obj, attr){ attr = attr || 'text'; switch(typeof obj) { case "string": @@ -1635,272 +1638,283 @@ angular.filter.Meta.get = function(obj, attr){ } }; -angular.filter['currency'] = function(amount){ - jQuery(this.element).toggleClass('ng-format-negative', amount < 0); - return '$' + angular.filter.number.apply(this, [amount, 2]); -}; - -angular.filter.number = function(amount, fractionSize){ - if (isNaN(amount) || !isFinite(amount)) { - return ''; - } - fractionSize = typeof fractionSize == 'undefined' ? 2 : fractionSize; - var isNegative = amount < 0; - amount = Math.abs(amount); - var pow = Math.pow(10, fractionSize); - var text = "" + Math.round(amount * pow); - var whole = text.substring(0, text.length - fractionSize); - whole = whole || '0'; - var frc = text.substring(text.length - fractionSize); - text = isNegative ? '-' : ''; - for (var i = 0; i < whole.length; i++) { - if ((whole.length - i)%3 === 0 && i !== 0) { - text += ','; - } - text += whole.charAt(i); - } - if (fractionSize > 0) { - for (var j = frc.length; j < fractionSize; j++) { - frc += '0'; - } - text += '.' + frc.substring(0, fractionSize); - } - return text; -}; - -angular.filter.date = function(amount) { -}; - -angular.filter.json = function(object) { - jQuery(this.element).addClass("ng-monospace"); - return toJson(object, true); -}; +var angularFilterGoogleChartApi; -angular.filter.trackPackage = function(trackingNo, noMatch) { - trackingNo = trim(trackingNo); - var tNo = trackingNo.replace(/ /g, ''); - var MATCHERS = angular.filter.trackPackage.MATCHERS; - for ( var i = 0; i < MATCHERS.length; i++) { - var carrier = MATCHERS[i]; - for ( var j = 0; j < carrier.regexp.length; j++) { - var regexp = carrier.regexp[j]; - if (regexp.test(tNo)) { - var text = carrier.name + ": " + trackingNo; - var url = carrier.url + trackingNo; - return new angular.filter.Meta({ - text:text, - url:url, - html: '' + text + '', - trackingNo:trackingNo}); +foreach({ + 'currency': function(amount){ + jQuery(this.element).toggleClass('ng-format-negative', amount < 0); + return '$' + angularFilter['number'].apply(this, [amount, 2]); + }, + + 'number': function(amount, fractionSize){ + if (isNaN(amount) || !isFinite(amount)) { + return ''; + } + fractionSize = typeof fractionSize == 'undefined' ? 2 : fractionSize; + var isNegative = amount < 0; + amount = Math.abs(amount); + var pow = Math.pow(10, fractionSize); + var text = "" + Math.round(amount * pow); + var whole = text.substring(0, text.length - fractionSize); + whole = whole || '0'; + var frc = text.substring(text.length - fractionSize); + text = isNegative ? '-' : ''; + for (var i = 0; i < whole.length; i++) { + if ((whole.length - i)%3 === 0 && i !== 0) { + text += ','; } + text += whole.charAt(i); } - } - if (trackingNo) - return noMatch || - new angular.filter.Meta({text:trackingNo + " is not recognized"}); - else - return null; -}; - -angular.filter.trackPackage.MATCHERS = [ - { name: "UPS", - url: "http://wwwapps.ups.com/WebTracking/processInputRequest?sort_by=status&tracknums_displayed=1&TypeOfInquiryNumber=T&loc=en_US&track.x=0&track.y=0&InquiryNumber1=", - regexp: [ - /^1Z[0-9A-Z]{16}$/i]}, - { name: "FedEx", - url: "http://www.fedex.com/Tracking?tracknumbers=", - regexp: [ - /^96\d{10}?$/i, - /^96\d{17}?$/i, - /^96\d{20}?$/i, - /^\d{15}$/i, - /^\d{12}$/i]}, - { name: "USPS", - url: "http://trkcnfrm1.smi.usps.com/PTSInternetWeb/InterLabelInquiry.do?origTrackNum=", - regexp: [ - /^(91\d{20})$/i, - /^(91\d{18})$/i]}]; - -angular.filter.link = function(obj, title) { - var text = title || angular.filter.Meta.get(obj); - var url = angular.filter.Meta.get(obj, "url") || angular.filter.Meta.get(obj); - if (url) { - if (angular.validator.email(url) === null) { - url = "mailto:" + url; - } - var html = '' + text + ''; - return new angular.filter.Meta({text:text, url:url, html:html}); - } - return obj; -}; - - -angular.filter.bytes = function(size) { - if(size === null) return ""; - - var suffix = 0; - while (size > 1000) { - size = size / 1024; - suffix++; - } - var txt = "" + size; - var dot = txt.indexOf('.'); - if (dot > -1 && dot + 2 < txt.length) { - txt = txt.substring(0, dot + 2); - } - return txt + " " + angular.filter.bytes.SUFFIX[suffix]; -}; -angular.filter.bytes.SUFFIX = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB']; - -angular.filter.image = function(obj, width, height) { - if (obj && obj.url) { - var style = ""; - if (width) { - style = ' style="max-width: ' + width + - 'px; max-height: ' + (height || width) + 'px;"'; + if (fractionSize > 0) { + for (var j = frc.length; j < fractionSize; j++) { + frc += '0'; + } + text += '.' + frc.substring(0, fractionSize); } - return new angular.filter.Meta({url:obj.url, text:obj.url, - html:''}); - } - return null; -}; - -angular.filter.lowercase = function (obj) { - var text = angular.filter.Meta.get(obj); - return text ? ("" + text).toLowerCase() : text; -}; - -angular.filter.uppercase = function (obj) { - var text = angular.filter.Meta.get(obj); - return text ? ("" + text).toUpperCase() : text; -}; - -angular.filter.linecount = function (obj) { - var text = angular.filter.Meta.get(obj); - if (text==='' || !text) return 1; - return text.split(/\n|\f/).length; -}; - -angular.filter['if'] = function (result, expression) { - return expression ? result : undefined; -}; - -angular.filter.unless = function (result, expression) { - return expression ? undefined : result; -}; - -angular.filter.googleChartApi = function(type, data, width, height) { - data = data || {}; - var api = angular.filter.googleChartApi; - var chart = { - cht:type, - chco:api.collect(data, 'color'), - chtt:api.title(data), - chdl:api.collect(data, 'label'), - chd:api.values(data), - chf:'bg,s,FFFFFF00' - }; - if (_.isArray(data.xLabels)) { - chart.chxt='x'; - chart.chxl='0:|' + data.xLabels.join('|'); - } - return angular.filter.googleChartApi.encode(chart, width, height); -}; - -angular.filter.googleChartApi.values = function(data){ - var seriesValues = []; - _.each(data.series||[], function(serie){ - var values = []; - _.each(serie.values||[], function(value){ - values.push(value); - }); - seriesValues.push(values.join(',')); - }); - var values = seriesValues.join('|'); - return values === "" ? null : "t:" + values; -}; - -angular.filter.googleChartApi.title = function(data){ - var titles = []; - var title = data.title || []; - _.each(_.isArray(title)?title:[title], function(text){ - titles.push(encodeURIComponent(text)); - }); - return titles.join('|'); -}; - -angular.filter.googleChartApi.collect = function(data, key){ - var outterValues = []; - var count = 0; - _.each(data.series||[], function(serie){ - var innerValues = []; - var value = serie[key] || []; - _.each(_.isArray(value)?value:[value], function(color){ - innerValues.push(encodeURIComponent(color)); - count++; + return text; + }, + + 'date': function(amount) { + }, + + 'json': function(object) { + jQuery(this.element).addClass("ng-monospace"); + return toJson(object, true); + }, + + 'trackPackage': (function(){ + var MATCHERS = [ + { name: "UPS", + url: "http://wwwapps.ups.com/WebTracking/processInputRequest?sort_by=status&tracknums_displayed=1&TypeOfInquiryNumber=T&loc=en_US&track.x=0&track.y=0&InquiryNumber1=", + regexp: [ + /^1Z[0-9A-Z]{16}$/i]}, + { name: "FedEx", + url: "http://www.fedex.com/Tracking?tracknumbers=", + regexp: [ + /^96\d{10}?$/i, + /^96\d{17}?$/i, + /^96\d{20}?$/i, + /^\d{15}$/i, + /^\d{12}$/i]}, + { name: "USPS", + url: "http://trkcnfrm1.smi.usps.com/PTSInternetWeb/InterLabelInquiry.do?origTrackNum=", + regexp: [ + /^(91\d{20})$/i, + /^(91\d{18})$/i]}]; + return function(trackingNo, noMatch) { + trackingNo = trim(trackingNo); + var tNo = trackingNo.replace(/ /g, ''); + var returnValue; + foreach(MATCHERS, function(carrier){ + foreach(carrier.regexp, function(regexp){ + if (regexp.test(tNo)) { + var text = carrier.name + ": " + trackingNo; + var url = carrier.url + trackingNo; + returnValue = new angularFilter.Meta({ + text:text, + url:url, + html: '' + text + '', + trackingNo:trackingNo}); + _.breakLoop(); + } + }); + if (returnValue) _.breakLoop(); }); - outterValues.push(innerValues.join('|')); - }); - return count?outterValues.join(','):null; -}; - -angular.filter.googleChartApi.encode= function(params, width, height) { - width = width || 200; - height = height || width; - var url = "http://chart.apis.google.com/chart?"; - var urlParam = []; - params.chs = width + "x" + height; - for ( var key in params) { - var value = params[key]; - if (value) { - urlParam.push(key + "=" + value); + if (returnValue) + return returnValue; + else if (trackingNo) + return noMatch || new angularFilter.Meta({text:trackingNo + " is not recognized"}); + else + return null; + };})(), + + 'link': function(obj, title) { + var text = title || angularFilter.Meta.get(obj); + var url = angularFilter.Meta.get(obj, "url") || angularFilter.Meta.get(obj); + if (url) { + if (angular.validator.email(url) === null) { + url = "mailto:" + url; + } + var html = '' + text + ''; + return new angularFilter.Meta({text:text, url:url, html:html}); } - } - urlParam.sort(); - url += urlParam.join("&"); - return new angular.filter.Meta({url:url, text:value, - html:''}); -}; - -angular.filter.qrcode = function(value, width, height) { - return angular.filter.googleChartApi.encode({cht:'qr', chl:encodeURIComponent(value)}, width, height); -}; -angular.filter.chart = { - pie:function(data, width, height) { - return angular.filter.googleChartApi('p', data, width, height); + return obj; }, - pie3d:function(data, width, height) { - return angular.filter.googleChartApi('p3', data, width, height); + + + 'bytes': (function(){ + var SUFFIX = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB']; + return function(size) { + if(size === null) return ""; + + var suffix = 0; + while (size > 1000) { + size = size / 1024; + suffix++; + } + var txt = "" + size; + var dot = txt.indexOf('.'); + if (dot > -1 && dot + 2 < txt.length) { + txt = txt.substring(0, dot + 2); + } + return txt + " " + SUFFIX[suffix]; + }; + })(), + + 'image': function(obj, width, height) { + if (obj && obj.url) { + var style = ""; + if (width) { + style = ' style="max-width: ' + width + + 'px; max-height: ' + (height || width) + 'px;"'; + } + return new angularFilter.Meta({url:obj.url, text:obj.url, + html:''}); + } + return null; }, - pieConcentric:function(data, width, height) { - return angular.filter.googleChartApi('pc', data, width, height); + + 'lowercase': function (obj) { + var text = angularFilter.Meta.get(obj); + return text ? ("" + text).toLowerCase() : text; }, - barHorizontalStacked:function(data, width, height) { - return angular.filter.googleChartApi('bhs', data, width, height); + + 'uppercase': function (obj) { + var text = angularFilter.Meta.get(obj); + return text ? ("" + text).toUpperCase() : text; }, - barHorizontalGrouped:function(data, width, height) { - return angular.filter.googleChartApi('bhg', data, width, height); + + 'linecount': function (obj) { + var text = angularFilter.Meta.get(obj); + if (text==='' || !text) return 1; + return text.split(/\n|\f/).length; }, - barVerticalStacked:function(data, width, height) { - return angular.filter.googleChartApi('bvs', data, width, height); + + 'if': function (result, expression) { + return expression ? result : undefined; }, - barVerticalGrouped:function(data, width, height) { - return angular.filter.googleChartApi('bvg', data, width, height); + + 'unless': function (result, expression) { + return expression ? undefined : result; }, - line:function(data, width, height) { - return angular.filter.googleChartApi('lc', data, width, height); + + 'googleChartApi': extend( + function(type, data, width, height) { + data = data || {}; + var chart = { + cht:type, + chco:angularFilterGoogleChartApi.collect(data, 'color'), + chtt:angularFilterGoogleChartApi.title(data), + chdl:angularFilterGoogleChartApi.collect(data, 'label'), + chd:angularFilterGoogleChartApi.values(data), + chf:'bg,s,FFFFFF00' + }; + if (_.isArray(data.xLabels)) { + chart.chxt='x'; + chart.chxl='0:|' + data.xLabels.join('|'); + } + return angularFilterGoogleChartApi['encode'](chart, width, height); + }, + { + 'values': function(data){ + var seriesValues = []; + foreach(data.series||[], function(serie){ + var values = []; + foreach(serie.values||[], function(value){ + values.push(value); + }); + seriesValues.push(values.join(',')); + }); + var values = seriesValues.join('|'); + return values === "" ? null : "t:" + values; + }, + + 'title': function(data){ + var titles = []; + var title = data.title || []; + foreach(_.isArray(title)?title:[title], function(text){ + titles.push(encodeURIComponent(text)); + }); + return titles.join('|'); + }, + + 'collect': function(data, key){ + var outterValues = []; + var count = 0; + foreach(data.series||[], function(serie){ + var innerValues = []; + var value = serie[key] || []; + foreach(_.isArray(value)?value:[value], function(color){ + innerValues.push(encodeURIComponent(color)); + count++; + }); + outterValues.push(innerValues.join('|')); + }); + return count?outterValues.join(','):null; + }, + + 'encode': function(params, width, height) { + width = width || 200; + height = height || width; + var url = "http://chart.apis.google.com/chart?"; + var urlParam = []; + params.chs = width + "x" + height; + foreach(params, function(value, key){ + if (value) { + urlParam.push(key + "=" + value); + } + }); + urlParam.sort(); + url += urlParam.join("&"); + return new angularFilter.Meta({url:url, + html:''}); + } + } + ), + + + 'qrcode': function(value, width, height) { + return angularFilterGoogleChartApi['encode']({cht:'qr', chl:encodeURIComponent(value)}, width, height); }, - sparkline:function(data, width, height) { - return angular.filter.googleChartApi('ls', data, width, height); + 'chart': { + pie:function(data, width, height) { + return angularFilterGoogleChartApi('p', data, width, height); + }, + pie3d:function(data, width, height) { + return angularFilterGoogleChartApi('p3', data, width, height); + }, + pieConcentric:function(data, width, height) { + return angularFilterGoogleChartApi('pc', data, width, height); + }, + barHorizontalStacked:function(data, width, height) { + return angularFilterGoogleChartApi('bhs', data, width, height); + }, + barHorizontalGrouped:function(data, width, height) { + return angularFilterGoogleChartApi('bhg', data, width, height); + }, + barVerticalStacked:function(data, width, height) { + return angularFilterGoogleChartApi('bvs', data, width, height); + }, + barVerticalGrouped:function(data, width, height) { + return angularFilterGoogleChartApi('bvg', data, width, height); + }, + line:function(data, width, height) { + return angularFilterGoogleChartApi('lc', data, width, height); + }, + sparkline:function(data, width, height) { + return angularFilterGoogleChartApi('ls', data, width, height); + }, + scatter:function(data, width, height) { + return angularFilterGoogleChartApi('s', data, width, height); + } }, - scatter:function(data, width, height) { - return angular.filter.googleChartApi('s', data, width, height); + + 'html': function(html){ + return new angularFilter.Meta({html:html}); } -}; +}, function(v,k){angularFilter[k] = v;}); -angular.filter.html = function(html){ - return new angular.filter.Meta({html:html}); -}; +angularFilterGoogleChartApi = angularFilter['googleChartApi']; array = [].constructor; toJson = function(obj, pretty){ @@ -1920,7 +1934,7 @@ fromJson = function(json) { parser.assertAllConsumed(); return expression(); } catch (e) { - console.error("fromJson error: ", json, e); + error("fromJson error: ", json, e); throw e; } }; @@ -2925,7 +2939,7 @@ Scope.prototype.evalWidget = function(widget, expression, context, onSuccess, on } return true; } catch (e){ - console.error('Eval Widget Error:', e); + error('Eval Widget Error:', e); var jsonError = toJson(e, true); widget.hasError = true; jQuery(widget.view). @@ -2985,10 +2999,10 @@ Scope.prototype.addWatchListener = function(watchExpression, listener) { Scope.prototype.fireWatchers = function() { var self = this; var fired = false; - jQuery.each(this.watchListeners, function(name, watcher) { + foreach(this.watchListeners, function(watcher) { var value = self.eval(watcher.expression); if (value !== watcher.lastValue) { - jQuery.each(watcher.listeners, function(i, listener){ + foreach(watcher.listeners, function(listener){ listener(value, watcher.lastValue); fired = true; }); @@ -3013,7 +3027,7 @@ Server.prototype.base64url = function(txt) { Server.prototype.request = function(method, url, request, callback) { var requestId = this.uuid + (this.nextId++); - callbacks[requestId] = function(response) { + angularCallbacks[requestId] = function(response) { delete angular[requestId]; callback(200, response); }; @@ -3104,84 +3118,86 @@ Users.prototype = { }; // Copyright (C) 2009 BRAT Tech LLC -angular.validator.regexp = function(value, regexp, msg) { - if (!value.match(regexp)) { - return msg || - "Value does not match expected format " + regexp + "."; - } else { - return null; - } -}; - -angular.validator.number = function(value, min, max) { - var num = 1 * value; - if (num == value) { - if (typeof min != 'undefined' && num < min) { - return "Value can not be less than " + min + "."; +foreach({ + 'regexp': function(value, regexp, msg) { + if (!value.match(regexp)) { + return msg || + "Value does not match expected format " + regexp + "."; + } else { + return null; } - if (typeof min != 'undefined' && num > max) { - return "Value can not be greater than " + max + "."; + }, + + 'number': function(value, min, max) { + var num = 1 * value; + if (num == value) { + if (typeof min != 'undefined' && num < min) { + return "Value can not be less than " + min + "."; + } + if (typeof min != 'undefined' && num > max) { + return "Value can not be greater than " + max + "."; + } + return null; + } else { + return "Value is not a number."; + } + }, + + 'integer': function(value, min, max) { + var number = angularValidator['number'](value, min, max); + if (number === null && value != Math.round(value)) { + return "Value is not a whole number."; + } + return number; + }, + + 'date': function(value, min, max) { + if (value.match(/^\d\d?\/\d\d?\/\d\d\d\d$/)) { + return null; + } + return "Value is not a date. (Expecting format: 12/31/2009)."; + }, + + 'ssn': function(value) { + if (value.match(/^\d\d\d-\d\d-\d\d\d\d$/)) { + return null; + } + return "SSN needs to be in 999-99-9999 format."; + }, + + 'email': function(value) { + if (value.match(/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/)) { + return null; + } + return "Email needs to be in username@host.com format."; + }, + + 'phone': function(value) { + if (value.match(/^1\(\d\d\d\)\d\d\d-\d\d\d\d$/)) { + return null; + } + if (value.match(/^\+\d{2,3} (\(\d{1,5}\))?[\d ]+\d$/)) { + return null; + } + return "Phone number needs to be in 1(987)654-3210 format in North America or +999 (123) 45678 906 internationaly."; + }, + + 'url': function(value) { + if (value.match(/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/)) { + return null; + } + return "URL needs to be in http://server[:port]/path format."; + }, + + 'json': function(value) { + try { + fromJson(value); + return null; + } catch (e) { + return e.toString(); } - return null; - } else { - return "Value is not a number."; - } -}; - -angular.validator.integer = function(value, min, max) { - var number = angular.validator.number(value, min, max); - if (number === null && value != Math.round(value)) { - return "Value is not a whole number."; - } - return number; -}; - -angular.validator.date = function(value, min, max) { - if (value.match(/^\d\d?\/\d\d?\/\d\d\d\d$/)) { - return null; - } - return "Value is not a date. (Expecting format: 12/31/2009)."; -}; - -angular.validator.ssn = function(value) { - if (value.match(/^\d\d\d-\d\d-\d\d\d\d$/)) { - return null; - } - return "SSN needs to be in 999-99-9999 format."; -}; - -angular.validator.email = function(value) { - if (value.match(/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/)) { - return null; - } - return "Email needs to be in username@host.com format."; -}; - -angular.validator.phone = function(value) { - if (value.match(/^1\(\d\d\d\)\d\d\d-\d\d\d\d$/)) { - return null; - } - if (value.match(/^\+\d{2,3} (\(\d{1,5}\))?[\d ]+\d$/)) { - return null; - } - return "Phone number needs to be in 1(987)654-3210 format in North America or +999 (123) 45678 906 internationaly."; -}; - -angular.validator.url = function(value) { - if (value.match(/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/)) { - return null; - } - return "URL needs to be in http://server[:port]/path format."; -}; - -angular.validator.json = function(value) { - try { - fromJson(value); - return null; - } catch (e) { - return e.toString(); } -}; +}, function(v,k) {angularValidator[k] = v;}); // Copyright (C) 2009 BRAT Tech LLC @@ -3709,7 +3725,7 @@ BindAttrUpdater.prototype.updateView = function(scope) { attrValues.push(value); } catch (e) { this.hasError = true; - console.error('BindAttrUpdater', e); + error('BindAttrUpdater', e); var jsonError = toJson(e, true); attrValues.push('[' + jsonError + ']'); jNode. @@ -3841,7 +3857,7 @@ RepeaterUpdater.prototype.updateView = function(scope) { var keyExp = this.keyExp; var valueExp = this.valueExp; var i = 0; - jQuery.each(iterator, function(key, value){ + foreach(iterator, function(value, key){ if (i < childrenLength) { // reuse children child = self.children[i]; diff --git a/example/calculator-manual_init.html b/example/calculator-manual_init.html index 1a6571c2..43d013fc 100644 --- a/example/calculator-manual_init.html +++ b/example/calculator-manual_init.html @@ -1,6 +1,7 @@ + + + + + + + +
      + Fetch tweets for: + + +
      +
      Loading...
      +
        +
      • [{{tweet.user.screen_name}}]: {{tweet.text}}
      • +
      + + -- cgit v1.2.3 From 595b4ea097bcb512173b6d4a12924ea1a3d70ecd Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Mon, 18 Jan 2010 10:47:03 -0800 Subject: checkpoint for integration with angular --- Rakefile | 2 +- css/angular.css | 8 +- css/angular_images/arrow_ascend.png | Bin 0 -> 3093 bytes css/angular_images/arrow_descend.png | Bin 0 -> 3076 bytes css/angular_images/arrow_left.gif | Bin 0 -> 102 bytes css/angular_images/arrow_right.gif | Bin 0 -> 102 bytes css/angular_images/loader-bar.gif | Bin 0 -> 10819 bytes css/images/arrow_ascend.png | Bin 3093 -> 0 bytes css/images/arrow_descend.png | Bin 3076 -> 0 bytes css/images/arrow_left.gif | Bin 102 -> 0 bytes css/images/arrow_right.gif | Bin 102 -> 0 bytes css/images/loader-bar.gif | Bin 10819 -> 0 bytes jsTestDriver.conf | 4 +- lib/jasmine-jstd-adapter/JasmineAdapter.js | 96 ++ lib/jasmine/jasmine-0.10.0.js | 2261 ++++++++++++++++++++++++++++ src/Angular.js | 414 +++++ src/Loader.js | 424 ------ src/angular-bootstrap.js | 115 -- test/AngularTest.js | 60 + test/LoaderTest.js | 70 - test/testabilityPatch.js | 2 +- 21 files changed, 2838 insertions(+), 618 deletions(-) create mode 100644 css/angular_images/arrow_ascend.png create mode 100644 css/angular_images/arrow_descend.png create mode 100644 css/angular_images/arrow_left.gif create mode 100644 css/angular_images/arrow_right.gif create mode 100644 css/angular_images/loader-bar.gif delete mode 100644 css/images/arrow_ascend.png delete mode 100644 css/images/arrow_descend.png delete mode 100644 css/images/arrow_left.gif delete mode 100644 css/images/arrow_right.gif delete mode 100644 css/images/loader-bar.gif create mode 100644 lib/jasmine-jstd-adapter/JasmineAdapter.js create mode 100644 lib/jasmine/jasmine-0.10.0.js create mode 100644 src/Angular.js delete mode 100644 src/Loader.js delete mode 100644 src/angular-bootstrap.js create mode 100644 test/AngularTest.js delete mode 100644 test/LoaderTest.js diff --git a/Rakefile b/Rakefile index 8f7bc7ec..56f7a9c4 100644 --- a/Rakefile +++ b/Rakefile @@ -34,7 +34,7 @@ task :compile do concat = %x(cat \ src/angular.prefix \ lib/webtoolkit/webtoolkit.base64.js \ - src/Loader.js \ + src/Angular.js \ src/API.js \ src/Binder.js \ src/ControlBar.js \ diff --git a/css/angular.css b/css/angular.css index 08065295..f90c5d07 100644 --- a/css/angular.css +++ b/css/angular.css @@ -63,8 +63,8 @@ div.ui-widget { background-repeat: no-repeat; background-position: right; } -.ng-ascend { background-image: url(images/arrow_ascend.png); } -.ng-descend { background-image: url(images/arrow_descend.png); } +.ng-ascend { background-image: url(angular_images/arrow_ascend.png); } +.ng-descend { background-image: url(angular_images/arrow_descend.png); } /***************** * TIP @@ -83,7 +83,7 @@ div.ui-widget { } #ng-callout .ng-arrow-left{ - background-image: url(images/arrow_left.gif); + background-image: url(angular_images/arrow_left.gif); background-repeat: no-repeat; background-position: left top; position: absolute; @@ -95,7 +95,7 @@ div.ui-widget { } #ng-callout .ng-arrow-right{ - background-image: url(images/arrow_right.gif); + background-image: url(angular_images/arrow_right.gif); background-repeat: no-repeat; background-position: left top; position: absolute; diff --git a/css/angular_images/arrow_ascend.png b/css/angular_images/arrow_ascend.png new file mode 100644 index 00000000..dd27b92b Binary files /dev/null and b/css/angular_images/arrow_ascend.png differ diff --git a/css/angular_images/arrow_descend.png b/css/angular_images/arrow_descend.png new file mode 100644 index 00000000..ec1cb5df Binary files /dev/null and b/css/angular_images/arrow_descend.png differ diff --git a/css/angular_images/arrow_left.gif b/css/angular_images/arrow_left.gif new file mode 100644 index 00000000..4c9e5c66 Binary files /dev/null and b/css/angular_images/arrow_left.gif differ diff --git a/css/angular_images/arrow_right.gif b/css/angular_images/arrow_right.gif new file mode 100644 index 00000000..3252c359 Binary files /dev/null and b/css/angular_images/arrow_right.gif differ diff --git a/css/angular_images/loader-bar.gif b/css/angular_images/loader-bar.gif new file mode 100644 index 00000000..47adbf03 Binary files /dev/null and b/css/angular_images/loader-bar.gif differ diff --git a/css/images/arrow_ascend.png b/css/images/arrow_ascend.png deleted file mode 100644 index dd27b92b..00000000 Binary files a/css/images/arrow_ascend.png and /dev/null differ diff --git a/css/images/arrow_descend.png b/css/images/arrow_descend.png deleted file mode 100644 index ec1cb5df..00000000 Binary files a/css/images/arrow_descend.png and /dev/null differ diff --git a/css/images/arrow_left.gif b/css/images/arrow_left.gif deleted file mode 100644 index 4c9e5c66..00000000 Binary files a/css/images/arrow_left.gif and /dev/null differ diff --git a/css/images/arrow_right.gif b/css/images/arrow_right.gif deleted file mode 100644 index 3252c359..00000000 Binary files a/css/images/arrow_right.gif and /dev/null differ diff --git a/css/images/loader-bar.gif b/css/images/loader-bar.gif deleted file mode 100644 index 47adbf03..00000000 Binary files a/css/images/loader-bar.gif and /dev/null differ diff --git a/jsTestDriver.conf b/jsTestDriver.conf index ebd45d9d..1fbe05a7 100644 --- a/jsTestDriver.conf +++ b/jsTestDriver.conf @@ -5,7 +5,7 @@ load: - lib/jquery/jquery-1.3.2.js - lib/jquery/jquery-ui-1.7.1.custom.min.js - lib/underscore/underscore.js - - src/Loader.js + - src/Angular.js - src/*.js - src/test/_namespace.js - src/test/*.js @@ -14,7 +14,5 @@ load: - test/*.js exclude: - - src/angular-bootstrap.js - src/angular.prefix - src/angular.suffix - \ No newline at end of file diff --git a/lib/jasmine-jstd-adapter/JasmineAdapter.js b/lib/jasmine-jstd-adapter/JasmineAdapter.js new file mode 100644 index 00000000..83a1deed --- /dev/null +++ b/lib/jasmine-jstd-adapter/JasmineAdapter.js @@ -0,0 +1,96 @@ +/** + * @fileoverview Jasmine JsTestDriver Adapter. + * @author ibolmo@gmail.com (Olmo Maldonado) + */ + +(function() { + +// Suite/TestCase before and after function stacks. +var before = []; +var after = []; + +jasmine.Env.prototype.describe = (function(describe){ + + // TODO(ibolmo): Support nested describes. + return function(description, specDefinitions){ + this.currentTestCase = TestCase(description); + return describe.call(this, description, specDefinitions); + }; + +})(jasmine.Env.prototype.describe); + + +jasmine.Env.prototype.it = (function(it){ + + return function(desc, func){ + var spec = it.call(this, desc, func); + this.currentTestCase.prototype['test that it ' + desc] = func; + return spec; + }; + +})(jasmine.Env.prototype.it); + + +jasmine.Env.prototype.beforeEach = (function(beforeEach){ + + // TODO(ibolmo): Support beforeEach TestCase. + return function(beforeEachFunction) { + beforeEach.call(this, beforeEachFunction); + if (this.currentTestCase) { + this.currentTestCase.prototype.setUp = beforeEachFunction; + } else { + before.push(beforeEachFunction); + } + }; + +})(jasmine.Env.prototype.beforeEach); + + +jasmine.Env.prototype.afterEach = (function(afterEach){ + + // TODO(ibolmo): Support afterEach TestCase. + return function(afterEachFunction) { + afterEach.call(this, afterEachFunction); + if (this.currentTestCase) { + this.currentTestCase.prototype.tearDown = afterEachFunction; + } else { + after.push(afterEachFunction); + } + }; + +})(jasmine.Env.prototype.afterEach); + + +jasmine.NestedResults.prototype.addResult = (function(addResult){ + + return function(result) { + addResult.call(this, result); + if (result.type != 'MessageResult' && !result.passed()) fail(result.message); + }; + +})(jasmine.NestedResults.prototype.addResult); + + +jstestdriver.plugins.TestRunnerPlugin.prototype.runTestConfiguration = (function(runTestConfiguration){ + + return function(testRunConfiguration, onTestDone, onTestRunConfigurationComplete){ + for (var i = 0, l = before.length; i < l; i++) before[i](); + onTestRunConfigurationComplete = (function(configurationComplete){ + + return function() { + for (var i = 0, l = after.length; i < l; i++) after[i](); + configurationComplete(); + }; + + })(onTestRunConfigurationComplete); + runTestConfiguration.call(this, testRunConfiguration, onTestDone, onTestRunConfigurationComplete); + }; + +})(jstestdriver.plugins.TestRunnerPlugin.prototype.runTestConfiguration); + + +// Reset environment with overriden methods. +jasmine.currentEnv_ = null; +jasmine.getEnv(); + +})(); diff --git a/lib/jasmine/jasmine-0.10.0.js b/lib/jasmine/jasmine-0.10.0.js new file mode 100644 index 00000000..bb7547e8 --- /dev/null +++ b/lib/jasmine/jasmine-0.10.0.js @@ -0,0 +1,2261 @@ +/** + * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework. + * + * @namespace + */ +var jasmine = {}; + +/** + * @private + */ +jasmine.unimplementedMethod_ = function() { + throw new Error("unimplemented method"); +}; + +/** + * Use jasmine.undefined instead of undefined, since undefined 0; +}; + +/** + * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter. + * + * @example + * // don't care about which function is passed in, as long as it's a function + * expect(mySpy).wasCalledWith(jasmine.any(Function)); + * + * @param {Class} clazz + * @returns matchable object of the type clazz + */ +jasmine.any = function(clazz) { + return new jasmine.Matchers.Any(clazz); +}; + +/** + * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks. + * + * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine + * expectation syntax. Spies can be checked if they were called or not and what the calling params were. + * + * A Spy has the following mehtod: wasCalled, callCount, mostRecentCall, and argsForCall (see docs) + * Spies are torn down at the end of every spec. + * + * Note: Do not call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj. + * + * @example + * // a stub + * var myStub = jasmine.createSpy('myStub'); // can be used anywhere + * + * // spy example + * var foo = { + * not: function(bool) { return !bool; } + * } + * + * // actual foo.not will not be called, execution stops + * spyOn(foo, 'not'); + + // foo.not spied upon, execution will continue to implementation + * spyOn(foo, 'not').andCallThrough(); + * + * // fake example + * var foo = { + * not: function(bool) { return !bool; } + * } + * + * // foo.not(val) will return val + * spyOn(foo, 'not').andCallFake(function(value) {return value;}); + * + * // mock example + * foo.not(7 == 7); + * expect(foo.not).wasCalled(); + * expect(foo.not).wasCalledWith(true); + * + * @constructor + * @see spyOn, jasmine.createSpy, jasmine.createSpyObj + * @param {String} name + */ +jasmine.Spy = function(name) { + /** + * The name of the spy, if provided. + */ + this.identity = name || 'unknown'; + /** + * Is this Object a spy? + */ + this.isSpy = true; + /** + * The actual function this spy stubs. + */ + this.plan = function() { + }; + /** + * Tracking of the most recent call to the spy. + * @example + * var mySpy = jasmine.createSpy('foo'); + * mySpy(1, 2); + * mySpy.mostRecentCall.args = [1, 2]; + */ + this.mostRecentCall = {}; + + /** + * Holds arguments for each call to the spy, indexed by call count + * @example + * var mySpy = jasmine.createSpy('foo'); + * mySpy(1, 2); + * mySpy(7, 8); + * mySpy.mostRecentCall.args = [7, 8]; + * mySpy.argsForCall[0] = [1, 2]; + * mySpy.argsForCall[1] = [7, 8]; + */ + this.argsForCall = []; + this.calls = []; +}; + +/** + * Tells a spy to call through to the actual implemenatation. + * + * @example + * var foo = { + * bar: function() { // do some stuff } + * } + * + * // defining a spy on an existing property: foo.bar + * spyOn(foo, 'bar').andCallThrough(); + */ +jasmine.Spy.prototype.andCallThrough = function() { + this.plan = this.originalValue; + return this; +}; + +/** + * For setting the return value of a spy. + * + * @example + * // defining a spy from scratch: foo() returns 'baz' + * var foo = jasmine.createSpy('spy on foo').andReturn('baz'); + * + * // defining a spy on an existing property: foo.bar() returns 'baz' + * spyOn(foo, 'bar').andReturn('baz'); + * + * @param {Object} value + */ +jasmine.Spy.prototype.andReturn = function(value) { + this.plan = function() { + return value; + }; + return this; +}; + +/** + * For throwing an exception when a spy is called. + * + * @example + * // defining a spy from scratch: foo() throws an exception w/ message 'ouch' + * var foo = jasmine.createSpy('spy on foo').andThrow('baz'); + * + * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch' + * spyOn(foo, 'bar').andThrow('baz'); + * + * @param {String} exceptionMsg + */ +jasmine.Spy.prototype.andThrow = function(exceptionMsg) { + this.plan = function() { + throw exceptionMsg; + }; + return this; +}; + +/** + * Calls an alternate implementation when a spy is called. + * + * @example + * var baz = function() { + * // do some stuff, return something + * } + * // defining a spy from scratch: foo() calls the function baz + * var foo = jasmine.createSpy('spy on foo').andCall(baz); + * + * // defining a spy on an existing property: foo.bar() calls an anonymnous function + * spyOn(foo, 'bar').andCall(function() { return 'baz';} ); + * + * @param {Function} fakeFunc + */ +jasmine.Spy.prototype.andCallFake = function(fakeFunc) { + this.plan = fakeFunc; + return this; +}; + +/** + * Resets all of a spy's the tracking variables so that it can be used again. + * + * @example + * spyOn(foo, 'bar'); + * + * foo.bar(); + * + * expect(foo.bar.callCount).toEqual(1); + * + * foo.bar.reset(); + * + * expect(foo.bar.callCount).toEqual(0); + */ +jasmine.Spy.prototype.reset = function() { + this.wasCalled = false; + this.callCount = 0; + this.argsForCall = []; + this.calls = []; + this.mostRecentCall = {}; +}; + +jasmine.createSpy = function(name) { + + var spyObj = function() { + spyObj.wasCalled = true; + spyObj.callCount++; + var args = jasmine.util.argsToArray(arguments); + spyObj.mostRecentCall.object = this; + spyObj.mostRecentCall.args = args; + spyObj.argsForCall.push(args); + spyObj.calls.push({object: this, args: args}); + return spyObj.plan.apply(this, arguments); + }; + + var spy = new jasmine.Spy(name); + + for (var prop in spy) { + spyObj[prop] = spy[prop]; + } + + spyObj.reset(); + + return spyObj; +}; + +/** + * Determines whether an object is a spy. + * + * @param {jasmine.Spy|Object} putativeSpy + * @returns {Boolean} + */ +jasmine.isSpy = function(putativeSpy) { + return putativeSpy && putativeSpy.isSpy; +}; + +/** + * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something + * large in one call. + * + * @param {String} baseName name of spy class + * @param {Array} methodNames array of names of methods to make spies + */ +jasmine.createSpyObj = function(baseName, methodNames) { + var obj = {}; + for (var i = 0; i < methodNames.length; i++) { + obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]); + } + return obj; +}; + +jasmine.log = function(message) { + jasmine.getEnv().currentSpec.log(message); +}; + +/** + * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy. + * + * @example + * // spy example + * var foo = { + * not: function(bool) { return !bool; } + * } + * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops + * + * @see jasmine.createSpy + * @param obj + * @param methodName + * @returns a Jasmine spy that can be chained with all spy methods + */ +var spyOn = function(obj, methodName) { + return jasmine.getEnv().currentSpec.spyOn(obj, methodName); +}; + +/** + * Creates a Jasmine spec that will be added to the current suite. + * + * // TODO: pending tests + * + * @example + * it('should be true', function() { + * expect(true).toEqual(true); + * }); + * + * @param {String} desc description of this specification + * @param {Function} func defines the preconditions and expectations of the spec + */ +var it = function(desc, func) { + return jasmine.getEnv().it(desc, func); +}; + +/** + * Creates a disabled Jasmine spec. + * + * A convenience method that allows existing specs to be disabled temporarily during development. + * + * @param {String} desc description of this specification + * @param {Function} func defines the preconditions and expectations of the spec + */ +var xit = function(desc, func) { + return jasmine.getEnv().xit(desc, func); +}; + +/** + * Starts a chain for a Jasmine expectation. + * + * It is passed an Object that is the actual value and should chain to one of the many + * jasmine.Matchers functions. + * + * @param {Object} actual Actual value to test against and expected value + */ +var expect = function(actual) { + return jasmine.getEnv().currentSpec.expect(actual); +}; + +/** + * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs. + * + * @param {Function} func Function that defines part of a jasmine spec. + */ +var runs = function(func) { + jasmine.getEnv().currentSpec.runs(func); +}; + +/** + * Waits for a timeout before moving to the next runs()-defined block. + * @param {Number} timeout + */ +var waits = function(timeout) { + jasmine.getEnv().currentSpec.waits(timeout); +}; + +/** + * Waits for the latchFunction to return true before proceeding to the next runs()-defined block. + * + * @param {Number} timeout + * @param {Function} latchFunction + * @param {String} message + */ +var waitsFor = function(timeout, latchFunction, message) { + jasmine.getEnv().currentSpec.waitsFor(timeout, latchFunction, message); +}; + +/** + * A function that is called before each spec in a suite. + * + * Used for spec setup, including validating assumptions. + * + * @param {Function} beforeEachFunction + */ +var beforeEach = function(beforeEachFunction) { + jasmine.getEnv().beforeEach(beforeEachFunction); +}; + +/** + * A function that is called after each spec in a suite. + * + * Used for restoring any state that is hijacked during spec execution. + * + * @param {Function} afterEachFunction + */ +var afterEach = function(afterEachFunction) { + jasmine.getEnv().afterEach(afterEachFunction); +}; + +/** + * Defines a suite of specifications. + * + * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared + * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization + * of setup in some tests. + * + * @example + * // TODO: a simple suite + * + * // TODO: a simple suite with a nested describe block + * + * @param {String} description A string, usually the class under test. + * @param {Function} specDefinitions function that defines several specs. + */ +var describe = function(description, specDefinitions) { + return jasmine.getEnv().describe(description, specDefinitions); +}; + +/** + * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development. + * + * @param {String} description A string, usually the class under test. + * @param {Function} specDefinitions function that defines several specs. + */ +var xdescribe = function(description, specDefinitions) { + return jasmine.getEnv().xdescribe(description, specDefinitions); +}; + + +// Provide the XMLHttpRequest class for IE 5.x-6.x: +jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() { + try { + return new ActiveXObject("Msxml2.XMLHTTP.6.0"); + } catch(e) { + } + try { + return new ActiveXObject("Msxml2.XMLHTTP.3.0"); + } catch(e) { + } + try { + return new ActiveXObject("Msxml2.XMLHTTP"); + } catch(e) { + } + try { + return new ActiveXObject("Microsoft.XMLHTTP"); + } catch(e) { + } + throw new Error("This browser does not support XMLHttpRequest."); +} : XMLHttpRequest; + +/** + * Adds suite files to an HTML document so that they are executed, thus adding them to the current + * Jasmine environment. + * + * @param {String} url path to the file to include + * @param {Boolean} opt_global + */ +jasmine.include = function(url, opt_global) { + if (opt_global) { + document.write(''); - }; - - if (scriptConfig.autoLoadDependencies) { - addScript("/../lib/webtoolkit/webtoolkit.base64.js"); - addScript("/../lib/swfobject/swfobject.js"); - addScript("/../lib/jquery/jquery-1.3.2.js"); - addScript("/../lib/jquery/jquery-ui-1.7.1.custom.min.js"); - addScript("/../lib/underscore/underscore.js"); - addScript("/Loader.js"); - addScript("/API.js"); - addScript("/Binder.js"); - addScript("/ControlBar.js"); - addScript("/DataStore.js"); - addScript("/Filters.js"); - addScript("/JSON.js"); - addScript("/Model.js"); - addScript("/Parser.js"); - addScript("/Scope.js"); - addScript("/Server.js"); - addScript("/Users.js"); - addScript("/Validators.js"); - addScript("/Widgets.js"); - } else { - addScript("/ajax/libs/swfobject/2.2/swfobject.js", "http://ajax.googleapis.com"); - addScript("/ajax/libs/jquery/1.3.2/jquery.min.js", "http://ajax.googleapis.com"); - addScript("/ajax/libs/jqueryui/1.7.2/jquery-ui.min.js", "http://ajax.googleapis.com"); - } - - window.onload = function() { - var doc = window.document; - if (scriptConfig.bindRootId) { - doc = null; - var ids = scriptConfig.bindRootId.split('|'); - for ( var i = 0; i < ids.length && !doc; i++) { - var idCond = ids[i].split('?'); - var id = idCond[0]; - if (idCond.length > 1) { - if (!window.document.getElementById(idCond[1])) { - continue; - } - } - doc = window.document.getElementById(id); - } - } - if (scriptConfig.autoBind && doc) { - window.angularScope = angular.compile(doc, scriptConfig); - } - if (typeof previousOnLoad === 'function') { - try { - previousOnLoad.apply(this, arguments); - } catch (e) {} - } - }; -})(window.onload); - - diff --git a/test/AngularTest.js b/test/AngularTest.js new file mode 100644 index 00000000..9610ef76 --- /dev/null +++ b/test/AngularTest.js @@ -0,0 +1,60 @@ +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'); + +UrlWatcherTest.prototype.testUrlWatcher = function () { + expectAsserts(2); + var location = {href:"http://server", hash:""}; + var watcher = new UrlWatcher(location); + watcher.delay = 1; + watcher.listener = function(url){ + assertEquals('http://getangular.test', url); + }; + watcher.setTimeout = function(fn, delay){ + assertEquals(1, delay); + location.href = "http://getangular.test"; + watcher.setTimeout = function(fn, delay) { + }; + fn(); + }; + watcher.watch(); +}; + +UrlWatcherTest.prototype.testItShouldFireOnUpdateEventWhenSpecialURLSet = function(){ + expectAsserts(2); + var location = {href:"http://server", hash:"#$iframe_notify=1234"}; + var watcher = new UrlWatcher(location); + angular.callbacks._iframe_notify_1234 = function () { + assertEquals("undefined", typeof angularCallbacks._iframe_notify_1234); + assertEquals("http://server2#", location.href); + }; + watcher.delay = 1; + watcher.expectedUrl = "http://server2"; + watcher.setTimeout = function(fn, delay){ + watcher.setTimeout = function(fn, delay) {}; + fn(); + }; + watcher.watch(); +}; + +FunctionTest = TestCase("FunctionTest"); + +FunctionTest.prototype.testEscapeHtml = function () { + assertEquals("<div>&amp;</div>", escapeHtml('
      &
      ')); +}; \ No newline at end of file diff --git a/test/LoaderTest.js b/test/LoaderTest.js deleted file mode 100644 index a474c8a4..00000000 --- a/test/LoaderTest.js +++ /dev/null @@ -1,70 +0,0 @@ -LoaderTest = TestCase('LoaderTest'); - -LoaderTest.prototype.testLoadCss = function(){ - if ($.browser.safari) return; - var head = jQuery('')[0]; - var loader = new Loader(document, head, {}); - var log = ''; - loader.config.server = 'http://'; - loader.loadCss('x'); - assertEquals($(head).find('link').attr('href'), 'http://x'); -}; - -LoaderTest.prototype.testDefaultDatabasePathFromSubdomain = function() { - var loader = new Loader(null, null, {server:"http://account.getangular.com", database:"database"}); - loader.computeConfiguration(); - assertEquals("database", loader.config.database); - - loader = new Loader(null, null, {server:"http://account.getangular.com"}); - loader.computeConfiguration(); - assertEquals("account", loader.config.database); - - loader = new Loader(null, null, {server:"https://account.getangular.com"}); - loader.computeConfiguration(); - assertEquals("account", loader.config.database); -}; - - - -UrlWatcherTest = TestCase('UrlWatcherTest'); - -UrlWatcherTest.prototype.testUrlWatcher = function () { - expectAsserts(2); - var location = {href:"http://server", hash:""}; - var watcher = new UrlWatcher(location); - watcher.delay = 1; - watcher.listener = function(url){ - assertEquals('http://getangular.test', url); - }; - watcher.setTimeout = function(fn, delay){ - assertEquals(1, delay); - location.href = "http://getangular.test"; - watcher.setTimeout = function(fn, delay) { - }; - fn(); - }; - watcher.watch(); -}; - -UrlWatcherTest.prototype.testItShouldFireOnUpdateEventWhenSpecialURLSet = function(){ - expectAsserts(2); - var location = {href:"http://server", hash:"#$iframe_notify=1234"}; - var watcher = new UrlWatcher(location); - angular.callbacks._iframe_notify_1234 = function () { - assertEquals("undefined", typeof angularCallbacks._iframe_notify_1234); - assertEquals("http://server2#", location.href); - }; - watcher.delay = 1; - watcher.expectedUrl = "http://server2"; - watcher.setTimeout = function(fn, delay){ - watcher.setTimeout = function(fn, delay) {}; - fn(); - }; - watcher.watch(); -}; - -FunctionTest = TestCase("FunctionTest"); - -FunctionTest.prototype.testEscapeHtml = function () { - assertEquals("<div>&amp;</div>", escapeHtml('
      &
      ')); -}; \ No newline at end of file diff --git a/test/testabilityPatch.js b/test/testabilityPatch.js index dde21846..78ffd380 100644 --- a/test/testabilityPatch.js +++ b/test/testabilityPatch.js @@ -96,7 +96,7 @@ function decode64(base64){ return fromJson(Base64.decode(base64)); } -Loader.prototype.configureJQueryPlugins(); +Angular.prototype.configureJQueryPlugins(); function assertHidden(node) { var display = node.css('display'); -- cgit v1.2.3 From 0e566fe6cb41de388df6793b350fb81aaa4a8476 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Mon, 18 Jan 2010 17:56:08 -0800 Subject: tweeter demo client --- .externalToolBuilders/JSTD_Tests.launch | 7 +- angular-minified.js | 193 ++++++++++++++++--------------- angular.js | 71 +++++++----- example/business_card.html | 50 -------- example/tweeter/style.css | 87 ++++++++++++++ example/tweeter/tweeter_addressbook.html | 64 ++++++++++ example/tweeter/tweeterclient.js | 35 ++++++ src/Angular.js | 30 ++--- src/Binder.js | 1 - src/Filters.js | 17 +++ test.sh | 3 +- test/FiltersTest.js | 9 ++ 12 files changed, 369 insertions(+), 198 deletions(-) delete mode 100644 example/business_card.html create mode 100644 example/tweeter/style.css create mode 100644 example/tweeter/tweeter_addressbook.html create mode 100644 example/tweeter/tweeterclient.js diff --git a/.externalToolBuilders/JSTD_Tests.launch b/.externalToolBuilders/JSTD_Tests.launch index 17fb5035..503cbaff 100644 --- a/.externalToolBuilders/JSTD_Tests.launch +++ b/.externalToolBuilders/JSTD_Tests.launch @@ -1,10 +1,9 @@ - + - - + + - diff --git a/angular-minified.js b/angular-minified.js index e49e0324..f590b704 100644 --- a/angular-minified.js +++ b/angular-minified.js @@ -1,98 +1,99 @@ -function J(){return function(){}}function K(p){return function(){return p}} -(function(p,y){function n(){}function v(a,b,c){var d=p.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=p.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 Ea(a,b){return function(){for(var c= -[this],d=0;d/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},qd: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};p.console||(p.console={log:n,error:n});var R,q=_.each,S=_.extend,k=p.jQuery,X=k.browser.msie,m=p.angular||(p.angular= -{}),Ba=m.validator||(m.validator={}),r=m.filter||(m.filter={}),ca=m.callbacks||(m.callbacks={});m.alert||(m.alert=function(){v(arguments);p.alert.apply(p,arguments)});ha.prototype={load:function(){this.Cb();v("Server: "+this.j.H);this.Bb();this.Ab();this.wb()},Bb:function(){v("Loader.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.Ub}},Ec:function(){return""+ -(new Date).getTime()},Ab:function(){var a=this.j;if(!a.F){var b=a.H.match(/https?:\/\/([\w]*)/);a.F=b?b[1]:"$MEMORY"}},wb:function(){function a(){g.Ob(function(l){!l&&c.find("[ng-auth=eager]").length&&g.ja()})}v("Loader.bindHtml()");var b=new ia(this.location),c=this.document,d=new ma(this.j.H,this.j.F),e=new x(c[0],d,b,this.j);d.qc=Ea(e,e.e);b=new P(c.find("body"),this.j.H);var f=this.j.F=="$MEMORY"?new aa(this.window):new ja(this.j.H,k.getScript);f=new ka(f,new U(k(c.body)),function(){e.c()});var g= -new la(f,b),h="/data/"+this.j.F,i=new T(function(l,o){f.N("POST",h,l,o)},g,e.anchor);e.Za.push(function(){i.Ea()});var j=new w({$anchor:e.anchor,$binder:e,$config:this.j,$console:p.console,$datastore:i,$save:function(l){i.zc(j.q,l,e.anchor)},$window:p,$uid:this.Ec,$users:g},"ROOT");c.data("scope",j);v("$binder.entity()");e.K(j);v("$binder.compile()");e.compile();v("ControlBar.bind()");b.bind();v("$users.fetchCurrentUser()");a();v("PopUp.bind()");(new B(c)).bind();v("$binder.parseAnchor()");e.sc(); -v("$binder.updateView()");e.c();c.find("body").show();v("ready()")},Cb:function(){var a=p.location.href+"#";a=a.split("#")[1];var b={Ib: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.Tc.find(a,function(c){return c.z==b},null)},filter:function(a,b){var c=[];c.xb=function(j){for(var l=0;l-1;case "object":for(var o in j)if(o.charAt(0)!=="$"&&e(j[o],l))return true;return false;case "array":for(o=0;o=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={tc:function(a){var b={};a.replace(/(?:^|&)([^&=]*)=?([^&]*)/g,function(c,d,e){if(d)b[decodeURIComponent(d)]=decodeURIComponent(e)});return b},sc:function(a){var b= -this;a=a||this.$a.Ga();var c=a.indexOf("#");if(!(c<0)){a=this.tc(a.substring(c+1));q(b.anchor,function(d,e){delete b.anchor[e]});q(a,function(d,e){b.anchor[e]=d})}},Fc:function(){var a=this.$a.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="&"}}return a},c:function(){(new Date).getTime();var a=k(this.B).scope(); -a.i("$invalidWidgets",[]);a.c();(new Date).getTime();this.Fc();_.each(this.Za,function(b){b()})},ga:function(a){var b=k(this.B),c=b.find(a);if(b.is(a))c=c.andSelf();return c},K:function(a){this.ga("[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),b=this;if(this.j.Yc){var c=this.ga(":submit").not("[ng-action]");c.attr("ng-action","$save()");c.not(":disabled").not("ng-bind-attr").attr("ng-bind-attr", -'{disabled:"{{$invalidWidgets}}"}')}this.Sa(this.B)(this.B,a.scope(),"");this.ga("a[ng-action]").live("click",function(){var d=k(this);try{d.scope().eval(d.attr("ng-action"));d.removeAttr("ng-error");d.removeClass("ng-exception")}catch(e){d.addClass("ng-exception");d.attr("ng-error",z(e,true))}b.c();return false})},Dc: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||v(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 o(s,t,A){var I=k(a).clone();I.css("display","");I.attr("ng-repeat-index",""+A);I.data("scope",s);l(I[0],s,t+A+":");return I}c.push({path:b,b:function(s,t,A){return new Aa(k(s),j,o,A)}})}else{a.getAttribute("ng-eval")&&c.push({path:b,b:this.kc});a.getAttribute("ng-bind")&&c.push({path:b,b:this.Qa}); -a.getAttribute("ng-bind-attr")&&c.push({path:b,b:this.gc});a.getAttribute("ng-hide")&&c.push({path:b,b:this.lc});a.getAttribute("ng-show")&&c.push({path:b,b:this.mc});a.getAttribute("ng-class")&&c.push({path:b,b:this.hc});a.getAttribute("ng-class-odd")&&c.push({path:b,b:this.jc});a.getAttribute("ng-class-even")&&c.push({path:b,b:this.ic});a.getAttribute("ng-style")&&c.push({path:b,b:this.nc});a.getAttribute("ng-watch")&&c.push({path:b,b:this.oc});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.Lc.Db(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.d===b)return false;else{a.i(this.Wa,b);return true}},c:function(a){if((a=a.get(this.Wa))&&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:K(true),c:n};Q.Ub=new Q;var Ja=Q;na.prototype={e:function(a){var b=this.view.value;if(this.d===b)return false;else{a.m(this.exp,b);this.d=b;return true}},c: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.d!=c)this.d=b.value=c;var d=false;b.removeAttribute("ng-error");if(this.yc)d=!(c&&c.length>0);var e=d?"Required Value":null;if(!d&&this.O&&c){e=a.Ic(this.O,c);d=!!e}if(this.Ja!==e){this.Ja=d;if(e!==null){b.setAttribute("ng-error",e);a.cc(this)}k(b).toggleClass("ng-validation-error",d)}}};oa.prototype={e:function(a){var b=this.view;b=b.checked?b.value:"";if(this.d===b)return false;else{a.m(this.exp,b); -this.d=b;return true}},c: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}};pa.prototype={e:function(a){if(this.view.selectedIndex<0)a.m(this.exp,null);else{var b=this.view.value;if(this.d===b)return false;else{a.m(this.exp,b);this.d=b;return true}}},c: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.d){b.value=c?c:"";this.d=c}}};qa.prototype={selected:function(){for(var a= -[],b=this.view.options,c=0;cd;--b){var o=this.children.pop().element[0];o.parentNode.removeChild(o)}if(h&&h.element[0].nodeName==="OPTION")if(d=k(h.element[0].parentNode).data("controller")){d.d= -undefined;d.c(a)}})}};B.ua="mouseleave mouseout click dblclick keypress keyup";B.rc=function(){B.ma();var a=k(this);a.bind(B.ua,B.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};B.ma=function(){k("#ng-callout").unbind(B.ua,B.ma).remove();return true};B.prototype={bind:function(){this.B.find(".ng-validation-error,.ng-exception").live("mouseover",B.rc)}};U.hb='
      loading....
      ';U.prototype={vb:function(){this.ba===0&&this.Ma.show();this.ba++},Lb:function(){this.ba--;this.ba=== -0&&this.Ma.hide("fold")}}})(window,document); +a;this.R(b||{});this.Q=a.title;this.hb()}function F(a,b){this.text=a;this.Jb=b?20:-1;this.h=[];this.index=0}function D(a,b){this.text=a;this.h=(new F(a,b)).parse();this.index=0}function v(a,b){this.eb=[];this.qa={};this.name=b;a=a||{};function c(){}c.prototype=a;this.q=new c;this.q.Uc=a;if(b=="ROOT")this.q.Vc=this.q}function ka(a,b){this.url=a;this.fc=0;this.getScript=b;this.Jc="_"+(""+Math.random()).substr(2)+"_";this.ka=1800}function aa(a){this.frame=a}function la(a,b,c){this.Lb=a;this.update=c; +this.status=b}function ma(a,b){this.H=a;this.ea=b}function na(a,b){this.gc=0;this.ca=a;this.F=b;this.Aa=o.Jd?swfobject.Aa:function(){alert("ERROR: swfobject not loaded!")};this.rc=J()}function M(a,b,c,d){this.view=a;this.Md=c;this.Za=b;this.$c=d+"/_attachments";this.value=null;this.d=undefined}function Q(a){this.view=a}function oa(a,b){this.view=a;this.exp=b;this.O=a.getAttribute("ng-validate");this.zc=typeof a.attributes["ng-required"]!="undefined";this.Ka=null;this.d=undefined;this.p=a.value;a.getAttribute("ng-widget")=== +"datepicker"&&k(a).sd()}function pa(a,b){this.view=a;this.exp=b;this.d=undefined;this.p=a.checked?a.value:""}function qa(a,b){this.view=a;this.exp=b;this.d=undefined;this.p=a.value}function ra(a,b){this.view=a;this.exp=b;this.d=undefined;this.p=this.selected()}function sa(a,b){this.view=a;this.exp=b;this.d=this.ia=undefined;this.Ja=a.value;this.p=a.checked?a.value:null}function N(a,b){this.view=a;this.exp=x.Z(b);this.w=false;this.Bc={element:a}}function ta(a,b){this.view=a;this.vb=b}function ua(a, +b){this.view=a;this.exp=b;this.w=false}function va(a,b){this.view=a;this.exp=b}function wa(a,b){this.view=a;this.exp=b}function xa(a,b){this.view=a;this.exp=b}function ya(a,b){this.view=a;this.exp=b}function za(a,b){this.view=a;this.exp=b}function Aa(a,b){this.view=a;this.exp=b}function Ba(a,b,c,d){this.view=a;this.template=c;this.prefix=d;this.children=[];a=b.match(/^\s*(.+)\s+in\s+(.*)\s*$/);if(!a)throw"Expected ng-repeat in form of 'item in collection' but got '"+b+"'.";b=a[1];this.X=a[2];a=b.match(/^([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\)$/); +if(!a)throw"'item' in 'item in collection' should be identifier or (key, value) but get '"+b+"'.";this.Lc=a[3]||a[1];this.$b=a[2]}function B(a){this.B=a}function U(a){this.Oa=a.append(U.jb).find("#ng-loading");this.ba=0}var ba={u:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=",Mb:function(a){var b="",c,d,e,f,g,h,i=0;for(a=ba.qb(a);i>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.pb(b)},qb: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},pb: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};o.console||(o.console={log:n,error:n});var R,p=_.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.Eb();w("Server: "+this.j.H);this.Db();this.Cb();this.yb()},Db: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()},Cb:function(){var a=this.j;if(!a.F){var b=a.H.match(/https?:\/\/([\w]*)/);a.F=b?b[1]:"$MEMORY"}},yb:function(){function a(){g.Qb(function(l){!l&&c.find("[ng-auth=eager]").length&&g.ja()})}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.c()}); +var g=new ma(f,b),h="/data/"+this.j.F,i=new T(function(l,q){f.N("POST",h,l,q)},g,e.anchor);e.bb.push(function(){i.Fa()});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.Ua();c.find("body").show();w("ready()")},Eb:function(){var a=o.location.href+"#";a=a.split("#")[1];var b={Kb: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.zb=function(j){for(var l=0;l-1;case "object":for(var q in j)if(q.charAt(0)!=="$"&&e(j[q],l))return true;return false;case "array":for(q=0;q=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},Ua:function(a){var b=this;a=a||this.pa.Ha();var c=a.indexOf("#");if(!(c<0)){a=this.uc(a.substring(c+1));p(b.anchor,function(d,e){delete b.anchor[e]});p(a,function(d,e){b.anchor[e]=d})}},tc:function(a){this.Ua(a);this.c()},Hc:function(){var a=this.pa.Ha(),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.pa.Dc(a);return a},c:function(){(new Date).getTime();var a=k(this.B).scope();a.i("$invalidWidgets",[]);a.c();(new Date).getTime();this.Hc();_.each(this.bb,function(b){b()})},ga:function(a){var b=k(this.B),c=b.find(a);if(b.is(a))c=c.andSelf();return c},K:function(a){this.ga("[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),b=this;if(this.j.ad){var c=this.ga(":submit").not("[ng-action]");c.attr("ng-action","$save()");c.not(":disabled").not("ng-bind-attr").attr("ng-bind-attr",'{disabled:"{{$invalidWidgets}}"}')}this.Va(this.B)(this.B,a.scope(),"");this.ga("a[ng-action]").live("click",function(){var d=k(this);try{d.scope().eval(d.attr("ng-action"));d.removeAttr("ng-error");d.removeClass("ng-exception")}catch(e){d.addClass("ng-exception"); +d.attr("ng-error",z(e,true))}b.c();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.Va(a);d=y.createComment("ng-repeat: "+j);e=a.parentNode;e.insertBefore(d,a);e.removeChild(a);function q(s,t,A){var I= +k(a).clone();I.css("display","");I.attr("ng-repeat-index",""+A);I.data("scope",s);l(I[0],s,t+A+":");return I}c.push({path:b,b:function(s,t,A){return new Ba(k(s),j,q,A)}})}else{a.getAttribute("ng-eval")&&c.push({path:b,b:this.lc});a.getAttribute("ng-bind")&&c.push({path:b,b:this.Sa});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.Fb(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.d===b)return false;else{a.i(this.Za,b);return true}},c:function(a){if((a=a.get(this.Za))&&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:K(true),c:n};Q.Wb=new Q;var Ja=Q;oa.prototype={e:function(a){var b=this.view.value;if(this.d===b)return false;else{a.m(this.exp,b);this.d=b;return true}},c: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.d!=c)this.d=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.Ka!==e){this.Ka=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.d===b)return false;else{a.m(this.exp,b);this.d=b;return true}},c: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.d===b)return false;else{a.m(this.exp,b);this.d=b;return true}}},c: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.d){b.value=c?c:"";this.d=c}}};ra.prototype={selected:function(){for(var a=[],b=this.view.options,c=0;cd;--b){var q=this.children.pop().element[0];q.parentNode.removeChild(q)}if(h&&h.element[0].nodeName==="OPTION")if(d=k(h.element[0].parentNode).data("controller")){d.d=undefined;d.c(a)}})}};B.va="mouseleave mouseout click dblclick keypress keyup";B.sc=function(){B.ma();var a=k(this);a.bind(B.va,B.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};B.ma=function(){k("#ng-callout").unbind(B.va, +B.ma).remove();return true};B.prototype={bind:function(){this.B.find(".ng-validation-error,.ng-exception").live("mouseover",B.sc)}};U.jb='
      loading....
      ';U.prototype={xb:function(){this.ba===0&&this.Oa.show();this.ba++},Nb:function(){this.ba--;this.ba===0&&this.Oa.hide("fold")}}})(window,document); diff --git a/angular.js b/angular.js index e3f24ecb..dee0b57e 100644 --- a/angular.js +++ b/angular.js @@ -342,17 +342,17 @@ function merge(src, dst) { } // //////////////////////////// -// Loader +// Angular // //////////////////////////// -function Loader(document, head, config) { +function Angular(document, head, config) { this.document = jQuery(document); this.head = jQuery(head); this.config = config; this.location = window.location; } -Loader.prototype = { +Angular.prototype = { load: function() { this.configureLogging(); log("Server: " + this.config.server); @@ -362,7 +362,7 @@ Loader.prototype = { }, configureJQueryPlugins: function() { - log('Loader.configureJQueryPlugins()'); + log('Angular.configureJQueryPlugins()'); jQuery['fn']['scope'] = function() { var element = this; while (element && element.get(0)) { @@ -391,8 +391,8 @@ Loader.prototype = { }, bindHtml: function() { - log('Loader.bindHtml()'); - var watcher = new UrlWatcher(this.location); + 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); @@ -450,12 +450,6 @@ Loader.prototype = { log('$binder.parseAnchor()'); binder.parseAnchor(); - log('$binder.updateView()'); - binder.updateView(); - - //watcher.listener = bind(binder, binder.onUrlChange, watcher); - //watcher.onUpdate = function(){alert("update");}; - //watcher.watch(); document.find("body").show(); log('ready()'); }, @@ -498,16 +492,6 @@ Loader.prototype = { consoleLog('ng-console-error', arguments); }; } - }, - - loadCss: function(css) { - var cssTag = document.createElement('link'); - cssTag.rel = "stylesheet"; - cssTag.type = "text/css"; - if (!css.match(/^http:/)) - css = this.config.server + css; - cssTag.href = css; - this.head[0].appendChild(cssTag); } }; @@ -553,12 +537,12 @@ UrlWatcher.prototype = { }, setUrl: function(url) { -// var existingURL = window.location.href; -// if (!existingURL.match(/#/)) -// existingURL += '#'; -// if (existingURL != url) -// window.location.href = url; -// this.existingURL = url; + var existingURL = window.location.href; + if (!existingURL.match(/#/)) + existingURL += '#'; + if (existingURL != url) + window.location.href = url; + this.existingURL = url; }, getUrl: function() { @@ -573,18 +557,25 @@ angular['compile'] = function(root, config) { 'addUrlChangeListener': noop }; //todo: don't start watcher - var loader = new Loader(root, jQuery("head"), _(defaults).extend(config)); + 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'); - loader.load(); + 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();} + '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(); + } }; };var angularGlobal = { 'typeOf':function(obj){ @@ -977,7 +968,6 @@ Binder.prototype = { }, onUrlChange: function (url) { - log("URL change detected", url); this.parseAnchor(url); this.updateView(); }, @@ -1959,6 +1949,23 @@ foreach({ 'html': function(html){ return new angularFilter.Meta({html:html}); + }, + + 'linky': function(text){ + function regExpEscape(text) { + return text.replace(/([\/\.\*\+\?\|\(\)\[\]\{\}\\])/g, '\\$1'); + } + var URL = /(ftp|http|https):\/\/([^\(\)|\s]+)/gm; + var html = text; + var dups = {}; + foreach(text.match(URL)||[], function(url){ + url = url.replace(/\.$/, ''); + if (!dups[url]) { + html = html.replace(new RegExp(regExpEscape(url), 'gm'), ''+url+''); + dups[url] = true; + } + }); + return new angularFilter.Meta({text:text, html:html}); } }, function(v,k){angularFilter[k] = v;}); diff --git a/example/business_card.html b/example/business_card.html deleted file mode 100644 index cc45529f..00000000 --- a/example/business_card.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - -
      - Fetch tweets for: - - -
      -
      Loading...
      -
        -
      • [{{tweet.user.screen_name}}]: {{tweet.text}}
      • -
      - - diff --git a/example/tweeter/style.css b/example/tweeter/style.css new file mode 100644 index 00000000..769a6f45 --- /dev/null +++ b/example/tweeter/style.css @@ -0,0 +1,87 @@ +.loading {display: none;} +.fetching .loading {display: block;} + +a { + color: blue; +} + +h1 { + background-color: black; + margin: 0; + padding: .25em; + color: white; + border-bottom: 5px solid gray; +} + +.box { + border: 2px solid gray; +} + +.tweeter { + margin-right: 360px; +} + +ul { + list-style: none; + margin: 0; + padding: 0; +} + +li { + margin: .25em; + padding: 2px; +} + +li img { + float: left; + margin: 2px; + margin-right: .5em; + max-height: 48px; + min-height: 48px; +} + +li.even { + background-color: lightgray; +} + + +.addressbook { + float: right; + width: 350px; +} + +.clrleft { + clear: left; +} + +.notes { + font-size: .8em; + color: gray; +} + +.username, .nickname { + font-weight: bold; +} + +.editor { + padding: 4px; +} + +label { + color: gray; + display: inline-block; + width: 75px; + text-align: right; + padding: 2px; + margin-top: 10px; +} + +.editor input[type=text], +.editor textarea { + width: 230px; + vertical-align: text-top; +} + +.editor TEXTAREA { + height: 50px; +} \ No newline at end of file diff --git a/example/tweeter/tweeter_addressbook.html b/example/tweeter/tweeter_addressbook.html new file mode 100644 index 00000000..d8b8b893 --- /dev/null +++ b/example/tweeter/tweeter_addressbook.html @@ -0,0 +1,64 @@ + + + + + + + + + + + +
      +

      Address Book

      + [ Filter: ] + +
      +
      +
      + + + + + + + + + + +
      +
      +
      +
      +

      Tweets: {{$anchor.user}}

      + [ Filter: + | << All + ] +
      Loading...
      + +
      + + diff --git a/example/tweeter/tweeterclient.js b/example/tweeter/tweeterclient.js new file mode 100644 index 00000000..27ec3348 --- /dev/null +++ b/example/tweeter/tweeterclient.js @@ -0,0 +1,35 @@ +function noop(){} +$(document).ready(function(){ + var scope = window.scope = angular.compile(document); + scope.getJSON = function(url, callback) { + var list = []; + var self = this; + self.set('status', 'fetching'); + $.getJSON(url, function(response){ + _(response).forEach(function(v,k){ + list[k] = v; + }); + (callback||noop)(response); + self.set('status', ''); + self.updateView(); + }); + return list; + }; + + function fetchTweets(username){ + return scope.getJSON( + username ? + "http://twitter.com/statuses/user_timeline/"+username+".json" : + "http://twitter.com/statuses/home_timeline.json"); + } + + scope.set('fetchTweets', fetchTweets); + scope.set('users', [ + {screen_name:'mhevery', name:'Mi\u0161ko Hevery', notes:'Author of .', + profile_image_url:'http://a3.twimg.com/profile_images/54360179/Me_-_Small_Banner_normal.jpg'}, + {screen_name:'abrons', name:'Adam Abrons', notes:'Author of & Ruby guru.', + profile_image_url:'http://a1.twimg.com/profile_images/533646480/PIC00024_normal.jpg'} + ]); + scope.watchUrl(); + scope.init(); +}); diff --git a/src/Angular.js b/src/Angular.js index 69cab0a2..3dc72ff7 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -227,7 +227,7 @@ Angular.prototype = { bindHtml: function() { log('Angular.bindHtml()'); - var watcher = new UrlWatcher(this.location); + 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); @@ -285,12 +285,6 @@ Angular.prototype = { log('$binder.parseAnchor()'); binder.parseAnchor(); - log('$binder.updateView()'); - binder.updateView(); - - //watcher.listener = bind(binder, binder.onUrlChange, watcher); - //watcher.onUpdate = function(){alert("update");}; - //watcher.watch(); document.find("body").show(); log('ready()'); }, @@ -378,12 +372,13 @@ UrlWatcher.prototype = { }, setUrl: function(url) { -// var existingURL = window.location.href; -// if (!existingURL.match(/#/)) -// existingURL += '#'; -// if (existingURL != url) -// window.location.href = url; -// this.existingURL = url; + //TODO: conditionaly? + var existingURL = window.location.href; + if (!existingURL.match(/#/)) + existingURL += '#'; + if (existingURL != url) + window.location.href = url; + this.existingURL = url; }, getUrl: function() { @@ -409,6 +404,13 @@ angular['compile'] = function(root, config) { '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();} + '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(); + } }; }; \ No newline at end of file diff --git a/src/Binder.js b/src/Binder.js index 36cb6ec3..b29a07c6 100644 --- a/src/Binder.js +++ b/src/Binder.js @@ -66,7 +66,6 @@ Binder.prototype = { }, onUrlChange: function (url) { - log("URL change detected", url); this.parseAnchor(url); this.updateView(); }, diff --git a/src/Filters.js b/src/Filters.js index 666c9f30..833d5630 100644 --- a/src/Filters.js +++ b/src/Filters.js @@ -293,6 +293,23 @@ foreach({ 'html': function(html){ return new angularFilter.Meta({html:html}); + }, + + 'linky': function(text){ + function regExpEscape(text) { + return text.replace(/([\/\.\*\+\?\|\(\)\[\]\{\}\\])/g, '\\$1'); + } + var URL = /(ftp|http|https):\/\/([^\(\)|\s]+)/gm; + var html = text; + var dups = {}; + foreach(text.match(URL)||[], function(url){ + url = url.replace(/\.$/, ''); + if (!dups[url]) { + html = html.replace(new RegExp(regExpEscape(url), 'gm'), ''+url+''); + dups[url] = true; + } + }); + return new angularFilter.Meta({text:text, html:html}); } }, function(v,k){angularFilter[k] = v;}); diff --git a/test.sh b/test.sh index 8993c3ff..32e8b4b7 100755 --- a/test.sh +++ b/test.sh @@ -1 +1,2 @@ -java -jar lib/jstestdriver/JsTestDriver.jar --tests all +java -jar lib/jstestdriver/JsTestDriver.jar --tests all 2>/dev/null | grep -v lib/jasmine + diff --git a/test/FiltersTest.js b/test/FiltersTest.js index c219f24f..fee59bac 100644 --- a/test/FiltersTest.js +++ b/test/FiltersTest.js @@ -151,3 +151,12 @@ FiltersTest.prototype.testHtml = function() { angular.filter.html("acd").html); assertTrue(angular.filter.html("acd") instanceof angular.filter.Meta); }; + +FiltersTest.prototype.testLinky = function() { + assertEquals( + 'a (http://a) http://a \n http://1.2/v:~-123. c', + angular.filter.linky("a (http://a) http://a \n http://1.2/v:~-123. c").html); + assertTrue(angular.filter.linky("a") instanceof angular.filter.Meta); +}; + + -- cgit v1.2.3 From 910ddbe34eb6268e9049ca91211d43663d629879 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Mon, 18 Jan 2010 17:58:43 -0800 Subject: update image for adam --- example/tweeter/tweeterclient.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/tweeter/tweeterclient.js b/example/tweeter/tweeterclient.js index 27ec3348..f5f2d731 100644 --- a/example/tweeter/tweeterclient.js +++ b/example/tweeter/tweeterclient.js @@ -28,7 +28,7 @@ $(document).ready(function(){ {screen_name:'mhevery', name:'Mi\u0161ko Hevery', notes:'Author of .', profile_image_url:'http://a3.twimg.com/profile_images/54360179/Me_-_Small_Banner_normal.jpg'}, {screen_name:'abrons', name:'Adam Abrons', notes:'Author of & Ruby guru.', - profile_image_url:'http://a1.twimg.com/profile_images/533646480/PIC00024_normal.jpg'} + profile_image_url:'http://media.linkedin.com/mpr/mpr/shrink_80_80/p/2/000/005/0a8/044278d.jpg'} ]); scope.watchUrl(); scope.init(); -- cgit v1.2.3 From db2031c5a1df205e6db40ca6aba80930375069c0 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Tue, 19 Jan 2010 17:53:20 -0800 Subject: added debug info; fix parser bug with double negation --- example/tweeter/style.css | 7 +++++++ example/tweeter/tweeter_addressbook.html | 12 ++++++++++++ src/Parser.js | 28 +++++++++++++--------------- test/ParserTest.js | 8 ++++++++ 4 files changed, 40 insertions(+), 15 deletions(-) diff --git a/example/tweeter/style.css b/example/tweeter/style.css index 769a6f45..0f2aaecd 100644 --- a/example/tweeter/style.css +++ b/example/tweeter/style.css @@ -84,4 +84,11 @@ label { .editor TEXTAREA { height: 50px; +} + +.debug{ + font-size: .7em; + white-space: pre; + padding: 0; + margin: 0; } \ No newline at end of file diff --git a/example/tweeter/tweeter_addressbook.html b/example/tweeter/tweeter_addressbook.html index d8b8b893..70068b0a 100644 --- a/example/tweeter/tweeter_addressbook.html +++ b/example/tweeter/tweeter_addressbook.html @@ -39,6 +39,18 @@
      +
      +
      +userFilter={{userFilter|json}} + +tweetFilter={{tweetFilter|json}} + +$anchor={{$anchor}} + +users={{users}} + +tweets={{tweets}} +

      Tweets: {{$anchor.user}}

      diff --git a/src/Parser.js b/src/Parser.js index 333b8413..840f5541 100644 --- a/src/Parser.js +++ b/src/Parser.js @@ -294,15 +294,13 @@ Parser.prototype = { } }, - _unary: function(fn, parse) { - var right = parse.apply(this); + _unary: function(fn, right) { return function(self) { return fn(self, right(self)); }; }, - _binary: function(left, fn, parse) { - var right = parse.apply(this); + _binary: function(left, fn, right) { return function(self) { return fn(self, left(self), right(self)); }; @@ -343,7 +341,7 @@ Parser.prototype = { var token; while(true) { if ((token = this.expect('|'))) { - left = this._binary(left, token.fn, this.filter); + left = this._binary(left, token.fn, this.filter()); } else { return left; } @@ -405,7 +403,7 @@ Parser.prototype = { this.text.substring(token.index) + "' is not assignable."; } var ident = function(){return left.isAssignable;}; - return this._binary(ident, token.fn, this.logicalOR); + return this._binary(ident, token.fn, this.logicalOR()); } else { return left; } @@ -416,7 +414,7 @@ Parser.prototype = { var token; while(true) { if ((token = this.expect('||'))) { - left = this._binary(left, token.fn, this.logicalAND); + left = this._binary(left, token.fn, this.logicalAND()); } else { return left; } @@ -428,7 +426,7 @@ Parser.prototype = { var token; while(true) { if ((token = this.expect('&&'))) { - left = this._binary(left, token.fn, this.negated); + left = this._binary(left, token.fn, this.negated()); } else { return left; } @@ -438,9 +436,9 @@ Parser.prototype = { negated: function(){ var token; if (token = this.expect('!')) { - return this._unary(token.fn, this.equality); + return this._unary(token.fn, this.assignment()); } else { - return this.equality(); + return this.equality(); } }, @@ -449,7 +447,7 @@ Parser.prototype = { var token; while(true) { if ((token = this.expect('==','!='))) { - left = this._binary(left, token.fn, this.relational); + left = this._binary(left, token.fn, this.relational()); } else { return left; } @@ -461,7 +459,7 @@ Parser.prototype = { var token; while(true) { if ((token = this.expect('<', '>', '<=', '>='))) { - left = this._binary(left, token.fn, this.additive); + left = this._binary(left, token.fn, this.additive()); } else { return left; } @@ -472,7 +470,7 @@ Parser.prototype = { var left = this.multiplicative(); var token; while(token = this.expect('+','-')) { - left = this._binary(left, token.fn, this.multiplicative); + left = this._binary(left, token.fn, this.multiplicative()); } return left; }, @@ -481,7 +479,7 @@ Parser.prototype = { var left = this.unary(); var token; while(token = this.expect('*','/','%')) { - left = this._binary(left, token.fn, this.unary); + left = this._binary(left, token.fn, this.unary()); } return left; }, @@ -491,7 +489,7 @@ Parser.prototype = { if (this.expect('+')) { return this.primary(); } else if (token = this.expect('-')) { - return this._binary(Parser.ZERO, token.fn, this.multiplicative); + return this._binary(Parser.ZERO, token.fn, this.multiplicative()); } else { return this.primary(); } diff --git a/test/ParserTest.js b/test/ParserTest.js index 058010f3..fbd9f508 100644 --- a/test/ParserTest.js +++ b/test/ParserTest.js @@ -460,3 +460,11 @@ ParserTest.prototype.testReturnFunctionsAreNotBound = function(){ assertEquals("direct Group.all", "function", typeof Group.query); }; +ParserTest.prototype.testDoubleNegationBug = function (){ + var scope = new Scope(); + assertEquals(true, scope.eval('true')); + assertEquals(false, scope.eval('!true')); + assertEquals(true, scope.eval('!!true')); + assertEquals('a', scope.eval('{true:"a", false:"b"}[!!true]')); +}; + -- cgit v1.2.3 From dc0db57b36bb6cd47dfea835a315f61b34ed8e1b Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Wed, 20 Jan 2010 06:52:50 -0800 Subject: tweek tweeter --- angular-minified.js | 170 +++++++++++++++---------------- angular.js | 56 +++++----- example/tweeter/tweeter_addressbook.html | 4 +- example/tweeter/tweeterclient.js | 4 +- src/Filters.js | 27 +++-- test/FiltersTest.js | 11 +- 6 files changed, 143 insertions(+), 129 deletions(-) diff --git a/angular-minified.js b/angular-minified.js index f590b704..ccceb4ec 100644 --- a/angular-minified.js +++ b/angular-minified.js @@ -1,99 +1,99 @@ -function J(){return function(){}}function K(o){return function(){return o}} +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= +default:return false}}function fa(a,b){if(ea(a))if(X)a.innerText=b;else a.textContent=b;else a.innerHTML=b}function D(a){if(!a||!a.replace)return a;return a.replace(/&/g,"&").replace(//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.pb(b)},qb: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},pb: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};o.console||(o.console={log:n,error:n});var R,p=_.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.Eb();w("Server: "+this.j.H);this.Db();this.Cb();this.yb()},Db: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()},Cb:function(){var a=this.j;if(!a.F){var b=a.H.match(/https?:\/\/([\w]*)/);a.F=b?b[1]:"$MEMORY"}},yb:function(){function a(){g.Qb(function(l){!l&&c.find("[ng-auth=eager]").length&&g.ja()})}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.c()}); -var g=new ma(f,b),h="/data/"+this.j.F,i=new T(function(l,q){f.N("POST",h,l,q)},g,e.anchor);e.bb.push(function(){i.Fa()});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.Ua();c.find("body").show();w("ready()")},Eb:function(){var a=o.location.href+"#";a=a.split("#")[1];var b={Kb:null};a=a.split("&");for(var c=0;c>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.c()}); +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.zb=function(j){for(var l=0;l-1;case "object":for(var q in j)if(q.charAt(0)!=="$"&&e(j[q],l))return true;return false;case "array":for(q=0;q-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},Ua:function(a){var b=this;a=a||this.pa.Ha();var c=a.indexOf("#");if(!(c<0)){a=this.uc(a.substring(c+1));p(b.anchor,function(d,e){delete b.anchor[e]});p(a,function(d,e){b.anchor[e]=d})}},tc:function(a){this.Ua(a);this.c()},Hc:function(){var a=this.pa.Ha(),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.pa.Dc(a);return a},c:function(){(new Date).getTime();var a=k(this.B).scope();a.i("$invalidWidgets",[]);a.c();(new Date).getTime();this.Hc();_.each(this.bb,function(b){b()})},ga:function(a){var b=k(this.B),c=b.find(a);if(b.is(a))c=c.andSelf();return c},K:function(a){this.ga("[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),b=this;if(this.j.ad){var c=this.ga(":submit").not("[ng-action]");c.attr("ng-action","$save()");c.not(":disabled").not("ng-bind-attr").attr("ng-bind-attr",'{disabled:"{{$invalidWidgets}}"}')}this.Va(this.B)(this.B,a.scope(),"");this.ga("a[ng-action]").live("click",function(){var d=k(this);try{d.scope().eval(d.attr("ng-action"));d.removeAttr("ng-error");d.removeClass("ng-exception")}catch(e){d.addClass("ng-exception"); -d.attr("ng-error",z(e,true))}b.c();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.Va(a);d=y.createComment("ng-repeat: "+j);e=a.parentNode;e.insertBefore(d,a);e.removeChild(a);function q(s,t,A){var I= -k(a).clone();I.css("display","");I.attr("ng-repeat-index",""+A);I.data("scope",s);l(I[0],s,t+A+":");return I}c.push({path:b,b:function(s,t,A){return new Ba(k(s),j,q,A)}})}else{a.getAttribute("ng-eval")&&c.push({path:b,b:this.lc});a.getAttribute("ng-bind")&&c.push({path:b,b:this.Sa});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.Fb(k(s),t)}})}if(d=="OPTION")if(!k("").append(k(a).clone()).html().match(/.*<\/\s*option\s*>/gi))a.value= +a.text;d=a.childNodes;for(e=0;e' + + '
      '); + this.console = this.body.find(".console"); + this.testFrame = this.body.find("iframe"); + this.console.find(".run").live("click", function(){ + jQuery(this).parent().find('.log').toggle(); + }); + }, + + + runScenarios:function(){ + var runner = new scenario.Runner(this.console, this.testFrame); + _.stepper(this.scenarios, function(next, scenarioObj, name){ + new scenario.Scenario(name, scenarioObj).run(runner, next); + }, function(){ + } + ); + } +}; + +scenario.Runner = function(console, frame){ + this.console = console; + this.current = null; + this.tests = []; + this.frame = frame; +}; +scenario.Runner.prototype = { + start:function(name){ + var current = this.current = { + name:name, + start:new Date().getTime(), + scenario:jQuery('
      ') + }; + current.run = current.scenario.append( + '
      ' + + '.' + + '.' + + '.' + + '').find(".run"); + current.log = current.scenario.append('
      ').find(".log"); + current.run.find(".name").text(name); + this.tests.push(current); + this.console.append(current.scenario); + }, + end:function(name){ + var current = this.current; + var run = current.run; + this.current = null; + current.end = new Date().getTime(); + current.time = current.end - current.start; + run.find(".time").text(current.time); + run.find(".state").text(current.error ? "FAIL" : "PASS"); + run.addClass(current.error ? "fail" : "pass"); + if (current.error) + run.find(".run").append('').text(current.error); + current.scenario.find(".log").hide(); + }, + log:function(level) { + var buf = []; + for ( var i = 1; i < arguments.length; i++) { + var arg = arguments[i]; + buf.push(typeof arg == "string" ?arg:toJson(arg)); + } + var log = jQuery('
      '); + log.text(buf.join(" ")); + this.current.log.append(log); + this.console.scrollTop(this.console[0].scrollHeight); + if (level == "error") + this.current.error = buf.join(" "); + } +}; + +scenario.Scenario = function(name, scenario){ + this.name = name; + this.scenario = scenario; +}; +scenario.Scenario.prototype = { + run:function(runner, callback) { + var self = this; + _.stepper(this.scenario, function(next, steps, name){ + if (name.charAt(0) == '$') { + next(); + } else { + runner.start(self.name + "::" + name); + var allSteps = (self.scenario.$before||[]).concat(steps); + _.stepper(allSteps, function(next, step){ + self.executeStep(runner, step, next); + }, function(){ + runner.end(); + next(); + }); + } + }, callback); + }, + + + verb:function(step){ + var fn = null; + if (!step) fn = function (){ throw "Step is null!"; }; + else if (step.Given) fn = scenario.GIVEN[step.Given]; + else if (step.When) fn = scenario.WHEN[step.When]; + else if (step.Then) fn = scenario.THEN[step.Then]; + return fn || function (){ + throw "ERROR: Need Given/When/Then got: " + toJson(step); + }; + }, + + + context: function(runner) { + var frame = runner.frame; + var window = frame[0].contentWindow; + var document; + if (window.jQuery) + document = window.jQuery(window.document); + var context = { + frame:frame, + window:window, + log:_.bind(runner.log, runner, "info"), + document:document, + assert:function(element, path){ + if (element.size() != 1) { + throw "Expected to find '1' found '"+ + element.size()+"' for '"+path+"'."; + } + return element; + }, + element:function(path){ + var exp = path.replace("{{","[ng-bind=").replace("}}", "]"); + var element = document.find(exp); + return context.assert(element, path); + } + }; + return context; + }, + + + executeStep:function(runner, step, callback) { + if (!step) { + callback(); + return; + } + runner.log("info", toJson(step)); + var fn = this.verb(step); + var context = this.context(runner); + _.extend(context, step); + try { + (fn.call(context)||function(c){c();})(callback); + } catch (e) { + runner.log("error", "ERROR: " + toJson(e)); + } + } +}; diff --git a/src/scenario/Steps.js b/src/scenario/Steps.js new file mode 100644 index 00000000..f8ac173f --- /dev/null +++ b/src/scenario/Steps.js @@ -0,0 +1,57 @@ +angular.scenario.GIVEN = { + browser:function(){ + var self = this; + if (jQuery.browser.safari && this.frame.attr('src') == this.at) { + this.window.location.reload(); + } else { + this.frame.attr('src', this.at); + } + return function(done){ + self.frame.load(function(){ + self.frame.unbind(); + done(); + }); + }; + }, + dataset:function(){ + this.frame.name="$DATASET:" + toJson({dataset:this.dataset}); + } +}; +angular.scenario.WHEN = { + enter:function(){ + var element = this.element(this.at); + element.attr('value', this.text); + element.change(); + }, + click:function(){ + var element = this.element(this.at); + var input = element[0]; + // emulate the browser behavior which causes it + // to be overridden at the end. + var checked = input.checked = !input.checked; + element.click(); + input.checked = checked; + }, + select:function(){ + var element = this.element(this.at); + var path = "option[value=" + this.option + "]"; + var option = this.assert(element.find(path)); + option[0].selected = !option[0].selected; + element.change(); + } +}; +angular.scenario.THEN = { + text:function(){ + var element = this.element(this.at); + if (typeof this.should_be != undefined ) { + var should_be = this.should_be; + if (_.isArray(this.should_be)) + should_be = JSON.stringify(should_be); + if (element.text() != should_be) + throw "Expected " + should_be + + " but was " + element.text() + "."; + } + }, + drainRequestQueue:function(){ + } +}; diff --git a/src/scenario/_namespace.js b/src/scenario/_namespace.js new file mode 100644 index 00000000..7da3a5d8 --- /dev/null +++ b/src/scenario/_namespace.js @@ -0,0 +1,6 @@ +if (!angular) var angular = window['angular'] = {}; +if (!angular['scenario']) var angularScenario = angular['scenario'] = {}; +if (!angular['scenarioDef']) var scenarioDef = angular['scenarioDef'] = {}; +if (!angular['scenario']['GIVEN']) angularScenario['GIVEN'] = {}; +if (!angular['scenario']['WHEN']) angularScenario['WHEN'] = {}; +if (!angular['scenario']['THEN']) angularScenario['THEN'] = {}; diff --git a/src/scenario/bootstrap.js b/src/scenario/bootstrap.js new file mode 100644 index 00000000..1d40b9d0 --- /dev/null +++ b/src/scenario/bootstrap.js @@ -0,0 +1,47 @@ +(function(onLoadDelegate){ + var prefix = (function(){ + var filename = /(.*\/)bootstrap.js(#(.*))?/; + var scripts = document.getElementsByTagName("script"); + for(var j = 0; j < scripts.length; j++) { + var src = scripts[j].src; + if (src && src.match(filename)) { + var parts = src.match(filename); + return parts[1]; + } + } + })(); + function addScript(path) { + document.write(''); + }; + function addCSS(path) { + document.write(''); + }; + window.onload = function(){ + if (!_.stepper) { + _.stepper = function(collection, iterator, done){ + var keys = _.keys(collection); + function next() { + if (keys.length) { + var key = keys.shift(); + iterator(next, collection[key], key); + } else { + (done||_.identity)(); + } + } + next(); + }; + } + _.defer(function(){ + new angular.scenario.SuiteRunner(angular.scenarioDef, jQuery(document.body)).run(); + }); + (onLoadDelegate||function(){})(); + }; + addCSS("../../css/angular-scenario.css"); + addScript("../../lib/underscore/underscore.js"); + addScript("../../lib/jquery/jquery-1.3.2.js"); + addScript("../angular-bootstrap.js"); + addScript("_namespace.js"); + addScript("Steps.js"); + addScript("Runner.js"); +})(window.onload); + diff --git a/src/test/Runner.js b/src/test/Runner.js deleted file mode 100644 index c6684951..00000000 --- a/src/test/Runner.js +++ /dev/null @@ -1,162 +0,0 @@ -if (typeof test == 'undefined') test = {}; - -test.ScenarioRunner = function(scenarios, body) { - this.scenarios = scenarios; - this.body = body; -}; - -test.ScenarioRunner.prototype = { - run:function(){ - this.setUpUI(); - this.runScenarios(); - }, - setUpUI:function(){ - this.body.html( - '
      ' + - '
      ' + - '
      ' + - '
      ' + - '' + - '
      '); - this.console = this.body.find(".console"); - this.testFrame = this.body.find("iframe"); - this.console.find(".run").live("click", function(){ - jQuery(this).parent().find('.log').toggle(); - }); - }, - runScenarios:function(){ - var runner = new test.Runner(this.console, this.testFrame); - _.stepper(this.scenarios, function(next, scenario, name){ - new test.Scenario(name, scenario).run(runner, next); - }, function(){ - } - ); - } -}; - -test.Runner = function(console, frame){ - this.console = console; - this.current = null; - this.tests = []; - this.frame = frame; -}; -test.Runner.prototype = { - start:function(name){ - var current = this.current = { - name:name, - start:new Date().getTime(), - scenario:jQuery('
      ') - }; - current.run = current.scenario.append( - '
      ' + - '.' + - '.' + - '.' + - '').find(".run"); - current.log = current.scenario.append('
      ').find(".log"); - current.run.find(".name").text(name); - this.tests.push(current); - this.console.append(current.scenario); - }, - end:function(name){ - var current = this.current; - var run = current.run; - this.current = null; - current.end = new Date().getTime(); - current.time = current.end - current.start; - run.find(".time").text(current.time); - run.find(".state").text(current.error ? "FAIL" : "PASS"); - run.addClass(current.error ? "fail" : "pass"); - if (current.error) - run.find(".run").append('').text(current.error); - current.scenario.find(".log").hide(); - }, - log:function(level) { - var buf = []; - for ( var i = 1; i < arguments.length; i++) { - var arg = arguments[i]; - buf.push(typeof arg == "string" ?arg:toJson(arg)); - } - var log = jQuery('
      '); - log.text(buf.join(" ")); - this.current.log.append(log); - this.console.scrollTop(this.console[0].scrollHeight); - if (level == "error") - this.current.error = buf.join(" "); - } -}; - -test.Scenario = function(name, scenario){ - this.name = name; - this.scenario = scenario; -}; -test.Scenario.prototype = { - run:function(runner, callback) { - var self = this; - _.stepper(this.scenario, function(next, steps, name){ - if (name.charAt(0) == '$') { - next(); - } else { - runner.start(self.name + "::" + name); - var allSteps = (self.scenario.$before||[]).concat(steps); - _.stepper(allSteps, function(next, step){ - self.executeStep(runner, step, next); - }, function(){ - runner.end(); - next(); - }); - } - }, callback); - }, - verb:function(step){ - var fn = null; - if (!step) fn = function (){ throw "Step is null!"; }; - else if (step.Given) fn = angular.test.GIVEN[step.Given]; - else if (step.When) fn = angular.test.WHEN[step.When]; - else if (step.Then) fn = angular.test.THEN[step.Then]; - return fn || function (){ - throw "ERROR: Need Given/When/Then got: " + toJson(step); - }; - }, - context: function(runner) { - var frame = runner.frame; - var window = frame[0].contentWindow; - var document; - if (window.jQuery) - document = window.jQuery(window.document); - var context = { - frame:frame, - window:window, - log:_.bind(runner.log, runner, "info"), - document:document, - assert:function(element, path){ - if (element.size() != 1) { - throw "Expected to find '1' found '"+ - element.size()+"' for '"+path+"'."; - } - return element; - }, - element:function(path){ - var exp = path.replace("{{","[ng-bind=").replace("}}", "]"); - var element = document.find(exp); - return context.assert(element, path); - } - }; - return context; - }, - executeStep:function(runner, step, callback) { - if (!step) { - callback(); - return; - } - runner.log("info", toJson(step)); - var fn = this.verb(step); - var context = this.context(runner); - _.extend(context, step); - try { - (fn.call(context)||function(c){c();})(callback); - } catch (e) { - runner.log("error", "ERROR: " + toJson(e)); - } - } -}; diff --git a/src/test/Steps.js b/src/test/Steps.js deleted file mode 100644 index cc9ff549..00000000 --- a/src/test/Steps.js +++ /dev/null @@ -1,57 +0,0 @@ -angular.test.GIVEN = { - browser:function(){ - var self = this; - if (jQuery.browser.safari && this.frame.attr('src') == this.at) { - this.window.location.reload(); - } else { - this.frame.attr('src', this.at); - } - return function(done){ - self.frame.load(function(){ - self.frame.unbind(); - done(); - }); - }; - }, - dataset:function(){ - this.frame.name="$DATASET:" + toJson({dataset:this.dataset}); - } -}; -angular.test.WHEN = { - enter:function(){ - var element = this.element(this.at); - element.attr('value', this.text); - element.change(); - }, - click:function(){ - var element = this.element(this.at); - var input = element[0]; - // emulate the browser behavior which causes it - // to be overridden at the end. - var checked = input.checked = !input.checked; - element.click(); - input.checked = checked; - }, - select:function(){ - var element = this.element(this.at); - var path = "option[value=" + this.option + "]"; - var option = this.assert(element.find(path)); - option[0].selected = !option[0].selected; - element.change(); - } -}; -angular.test.THEN = { - text:function(){ - var element = this.element(this.at); - if (typeof this.should_be != undefined ) { - var should_be = this.should_be; - if (_.isArray(this.should_be)) - should_be = JSON.stringify(should_be); - if (element.text() != should_be) - throw "Expected " + should_be + - " but was " + element.text() + "."; - } - }, - drainRequestQueue:function(){ - } -}; diff --git a/src/test/_namespace.js b/src/test/_namespace.js deleted file mode 100644 index e29ae72a..00000000 --- a/src/test/_namespace.js +++ /dev/null @@ -1,5 +0,0 @@ -if (!angular) var angular = window['angular'] = {}; -if (!angular['test']) var angularTest = angular['test'] = {}; -if (!angular['test']['GIVEN']) angularTest['GIVEN'] = {}; -if (!angular['test']['WHEN']) angularTest['WHEN'] = {}; -if (!angular['test']['THEN']) angularTest['THEN'] = {}; diff --git a/test/scenario/StepsTest.js b/test/scenario/StepsTest.js new file mode 100644 index 00000000..9d64d0a9 --- /dev/null +++ b/test/scenario/StepsTest.js @@ -0,0 +1,7 @@ +StepsTest = TestCase("StepsTest"); + +StepsTest.prototype.testGivenDataset=function(){ + var self = {frame:{}, dataset:[]}; + angular.test.GIVEN.dataset.call(self); + assertEquals('$DATASET:{"dataset":[]}', self.frame.name); +}; diff --git a/test/test/StepsTest.js b/test/test/StepsTest.js deleted file mode 100644 index 9d64d0a9..00000000 --- a/test/test/StepsTest.js +++ /dev/null @@ -1,7 +0,0 @@ -StepsTest = TestCase("StepsTest"); - -StepsTest.prototype.testGivenDataset=function(){ - var self = {frame:{}, dataset:[]}; - angular.test.GIVEN.dataset.call(self); - assertEquals('$DATASET:{"dataset":[]}', self.frame.name); -}; -- cgit v1.2.3 From 8e9b7c671ddf31c50dd0191a765a787780be97ed Mon Sep 17 00:00:00 2001 From: Adam Abrons Date: Mon, 15 Mar 2010 17:03:24 -0700 Subject: IDEA files --- .idea/.rakeTasks | 7 +++++++ .idea/encodings.xml | 5 +++++ .idea/master.iml | 9 +++++++++ .idea/misc.xml | 14 ++++++++++++++ .idea/modules.xml | 9 +++++++++ .idea/vcs.xml | 8 ++++++++ 6 files changed, 52 insertions(+) create mode 100644 .idea/.rakeTasks create mode 100644 .idea/encodings.xml create mode 100644 .idea/master.iml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml diff --git a/.idea/.rakeTasks b/.idea/.rakeTasks new file mode 100644 index 00000000..50fb6fec --- /dev/null +++ b/.idea/.rakeTasks @@ -0,0 +1,7 @@ + + diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 00000000..e206d70d --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/.idea/master.iml b/.idea/master.iml new file mode 100644 index 00000000..8f7472a8 --- /dev/null +++ b/.idea/master.iml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..bf08d02d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..12b24804 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..9d32e507 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,8 @@ + + + + + + + + -- cgit v1.2.3 From 9e1f085ba6e224893abc04be181f8748939bfe77 Mon Sep 17 00:00:00 2001 From: Adam Abrons Date: Mon, 15 Mar 2010 17:05:45 -0700 Subject: datastore scenario failing as expected --- scenario/Runner.html | 2 +- scenario/datastore.html | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/scenario/Runner.html b/scenario/Runner.html index fb4b0f8f..c5eb6205 100644 --- a/scenario/Runner.html +++ b/scenario/Runner.html @@ -3,7 +3,7 @@ - + diff --git a/scenario/datastore.html b/scenario/datastore.html index 355d151c..525d3636 100644 --- a/scenario/datastore.html +++ b/scenario/datastore.html @@ -1,7 +1,12 @@ - + + + +

      {{book.$id}}

      -- cgit v1.2.3 From 923289112e34c2d426f6b2e687e93601894fb088 Mon Sep 17 00:00:00 2001 From: Adam Abrons Date: Tue, 16 Mar 2010 10:30:26 -0700 Subject: spike on directives --- src/directives.js | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 src/directives.js diff --git a/src/directives.js b/src/directives.js new file mode 100644 index 00000000..0e99d633 --- /dev/null +++ b/src/directives.js @@ -0,0 +1,121 @@ + +angular.directive("auth", function(expression, element){ + return function(){ + if(expression == "eager") { + this.$users.fetchCurrent(); + } + } +}); + + +//expression = "book=Book:{year=2000}" +angular.directive("entity", function(expression, element){ + //parse expression, ignore element + var entityName; // "Book"; + var instanceName; // "book"; + var defaults; // {year: 2000}; + + parse(expression); + + return function(){ + this[entityName] = this.$datastore.entity(entityName, defaults); + this[instanceName] = this[entityName](); + this.$watch("$anchor."+instanceName, function(newAnchor){ + this[instanceName] = this[entityName].get(this.$anchor[instanceName]); + }); + }; +}); + + +angular.directive("init", function(expression, element){ + return function(){ + this.$eval(expresssion); + } +}); + + +//translation of {{ }} to ng-bind is external to this +angular.directive("bind", function(expression, element){ + return function() { + this.$watch(expression, function(value){ + element.innerText = value; + }); + }; +}); + + +// translation of {{ }} to ng-bind-attr is external to this +// link +// becomes +// link +angular.directive("bind-attr", function(expression, element){ + var jElement = jQuery(element); + return function(){ + this.$watch(expression, _(jElement.attr).bind(jElement)); + }; +}); + +angular.directive("repeat", function(expression, element){ + var anchor = document.createComment(expression); + jQuery(element).replace(anchor); + var template = this.compile(element); + var lhs = "item"; + var rhs = "items"; + var children = []; + return function(){ + this.$watch(rhs, function(items){ + foreach(children, function(child){ + child.element.remove(); + }); + foreach(items, function(item){ + var child = template(item); // create scope + element.addChild(child.element, anchor); + children.push(child); + }); + }); + }; +}); + + +//ng-non-bindable +angular.directive("non-bindable", function(expression, element){ + return false; +}); + +//Styling +// +//ng-class +//ng-class-odd, ng-class-even +//ng-style +//ng-show, ng-hide + + +angular.directive("action", function(expression, element){ + return function(){ + var self = this; + jQuery(element).click(function(){ + self.$eval(expression); + }); + }; +}); + +//ng-eval +angular.directive("eval", function(expression, element){ + return function(){ + this.$onUpdate( expression); + } +}); +//ng-watch +//
      +angular.directive("watch", function(expression, element){ + var watches = { + 'lhs':'rhs' + }; // parse + return function(){ + this.$watch(watches); + } +}); + +//widget related +//ng-validate, ng-required, ng-formatter +//ng-error -- cgit v1.2.3 From b6060d88eb9a1f8ffd549aebd482eff0c12c1115 Mon Sep 17 00:00:00 2001 From: Adam Abrons Date: Tue, 16 Mar 2010 10:38:06 -0700 Subject: ignore --- .idea/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .idea/.gitignore diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..a7c382ed --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1 @@ +workspace.xml -- cgit v1.2.3 From 5119c8a86fa2b8ce9d0f0c343b57dd96aa88ce8c Mon Sep 17 00:00:00 2001 From: Adam Abrons Date: Tue, 16 Mar 2010 13:50:47 -0700 Subject: spike widgets in new style --- src/directives.js | 2 ++ src/widgets2.js | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 src/widgets2.js diff --git a/src/directives.js b/src/directives.js index 0e99d633..13cd4b59 100644 --- a/src/directives.js +++ b/src/directives.js @@ -119,3 +119,5 @@ angular.directive("watch", function(expression, element){ //widget related //ng-validate, ng-required, ng-formatter //ng-error + +//ng-scope ng-controller???? diff --git a/src/widgets2.js b/src/widgets2.js new file mode 100644 index 00000000..0d7bbd49 --- /dev/null +++ b/src/widgets2.js @@ -0,0 +1,85 @@ +// -> +angular.widget("inputtext", function(element) { + var expression = element.attr('name'); + var formatter = this.formatter(element.attr('formatter')); + var validator = this.validator(element.attr('validator')); + + function validate(value) { + var error = validator(element); + if (error) { + element.addClass("ng-error"); + scope.markInvalid(this); //move out of scope + } else { + scope.clearInvalid(this); + } + } + + + element.keyup(this.withScope(function(){ + this.$evalSet(expression, formatter.parse(element.val())); + validate(element.val()); + })); + + return {watch: expression, apply: function(newValue){ + element.val(formatter.format(newValue)); + validate(element.val()); + }}; + +}); + +angular.widget("inputfile", function(element) { + +}); + +angular.widget("inputradio", function(element) { + +}); + + +// +angular.widget("colorpicker", function(element) { + var name = element.attr('datasource'); + var formatter = this.formatter(element.attr('ng-formatter')); + + element.colorPicker(this.withScope(function(selectedColor){ + this.$evalSet(name, formatter.parse(selectedColor)); + })); + + return function(){ + this.$watch(expression, function(cmyk){ + element.setColor(formatter.format(cmyk)); + }); + } +}); + +angular.widget("template", function(element) { + var srcExpression = element.attr('src'); + var self = this; + return {watch:srcExpression, apply:function(src){ + $.load(src, function(html){ + self.destroy(element); + element.html(html); + self.compile(element); + }); + }}; +}); + + +/** + * + * { + * withScope: //safely executes, with a try/catch. applies scope + * compile: + * widget: + * directive: + * validator: + * formatter: + * + * + * config: + * loadCSS: + * loadScript: + * loadTemplate: + * } + * + **/ -- cgit v1.2.3 From 2df072e3f89e8998b06b5a9e5ffb10fa32155136 Mon Sep 17 00:00:00 2001 From: Adam Abrons Date: Tue, 16 Mar 2010 14:38:56 -0700 Subject: twitter using resources --- .idea/.gitignore | 1 + Rakefile | 1 + example/tweeter/tweeter_addressbook.html | 2 +- example/tweeter/tweeterclient.js | 34 +++++++++++++++----------------- src/Resource.js | 2 +- src/angular-bootstrap.js | 1 + 6 files changed, 21 insertions(+), 20 deletions(-) create mode 100644 .idea/.gitignore diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..a7c382ed --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1 @@ +workspace.xml diff --git a/Rakefile b/Rakefile index 883198dc..5e0bf008 100644 --- a/Rakefile +++ b/Rakefile @@ -46,6 +46,7 @@ task :compile do src/JSON.js \ src/Model.js \ src/Parser.js \ + src/Resource.js \ src/Scope.js \ src/Server.js \ src/Users.js \ diff --git a/example/tweeter/tweeter_addressbook.html b/example/tweeter/tweeter_addressbook.html index 64a3f95e..92121d5f 100644 --- a/example/tweeter/tweeter_addressbook.html +++ b/example/tweeter/tweeter_addressbook.html @@ -5,7 +5,7 @@ - + diff --git a/example/tweeter/tweeterclient.js b/example/tweeter/tweeterclient.js index 8c68fac0..612f9dee 100644 --- a/example/tweeter/tweeterclient.js +++ b/example/tweeter/tweeterclient.js @@ -1,28 +1,26 @@ function noop(){} $(document).ready(function(){ + function xhr(method, url, data, callback){ + jQuery.getJSON(url, function(){ + callback.apply(this, arguments); + scope.updateView(); + }) + } + + var resourceFactory = new ResourceFactory({method: xhr}); + + var Tweeter = resourceFactory.route("http://twitter.com/statuses/:service:username.json", {}, { + home: {method:'GET', params: {service:'home_timeline'}, isArray:true }, + user: {method:'GET', params: {service:'user_timeline/'}, isArray:true } + }); + + var scope = window.scope = angular.compile(document, { location:angular.startUrlWatcher() }); - scope.getJSON = function(url, callback) { - var list = []; - var self = this; - self.set('status', 'fetching'); - $.getJSON(url, function(response, code){ - _(response).forEach(function(v,k){ - list[k] = v; - }); - (callback||noop)(response); - self.set('status', ''); - self.updateView(); - }); - return list; - }; function fetchTweets(username){ - return scope.getJSON( - username ? - "http://twitter.com/statuses/user_timeline/"+username+".json" : - "http://twitter.com/statuses/home_timeline.json"); + return username ? Tweeter.user({username: username}) : Tweeter.home(); } scope.set('fetchTweets', fetchTweets); diff --git a/src/Resource.js b/src/Resource.js index c0c7934c..0ff46726 100644 --- a/src/Resource.js +++ b/src/Resource.js @@ -49,7 +49,7 @@ ResourceFactory.prototype = { actions = $.extend({}, ResourceFactory.DEFAULT_ACTIONS, actions); function extractParams(data){ var ids = {}; - foreach(paramDefaults, function(value, key){ + foreach(paramDefaults || {}, function(value, key){ ids[key] = value.charAt && value.charAt(0) == '@' ? Scope.getter(data, value.substr(1)) : value; }); return ids; diff --git a/src/angular-bootstrap.js b/src/angular-bootstrap.js index 0f7cd2ea..d2b2ff9c 100644 --- a/src/angular-bootstrap.js +++ b/src/angular-bootstrap.js @@ -46,6 +46,7 @@ addScript("/JSON.js"); addScript("/Model.js"); addScript("/Parser.js"); + addScript("/Resource.js"); addScript("/Scope.js"); addScript("/Server.js"); addScript("/Users.js"); -- cgit v1.2.3 From c9aba8b442adce496f0600c88764f7ffcc166879 Mon Sep 17 00:00:00 2001 From: Adam Abrons Date: Tue, 16 Mar 2010 14:48:11 -0700 Subject: make xhr just a method --- example/tweeter/tweeterclient.js | 2 +- jsTestDriver.conf | 6 +++--- src/Resource.js | 2 +- test/ResourceSpec.js | 7 ++++++- test/scenario/StepsTest.js | 2 +- 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/example/tweeter/tweeterclient.js b/example/tweeter/tweeterclient.js index 612f9dee..d6fdd084 100644 --- a/example/tweeter/tweeterclient.js +++ b/example/tweeter/tweeterclient.js @@ -7,7 +7,7 @@ $(document).ready(function(){ }) } - var resourceFactory = new ResourceFactory({method: xhr}); + var resourceFactory = new ResourceFactory(xhr); var Tweeter = resourceFactory.route("http://twitter.com/statuses/:service:username.json", {}, { home: {method:'GET', params: {service:'home_timeline'}, isArray:true }, diff --git a/jsTestDriver.conf b/jsTestDriver.conf index c048a294..da821ad1 100644 --- a/jsTestDriver.conf +++ b/jsTestDriver.conf @@ -9,10 +9,10 @@ load: - lib/underscore/underscore.js - src/Angular.js - src/*.js - - src/test/_namespace.js - - src/test/*.js + - src/scenario/_namespace.js + - src/scenario/*.js - test/testabilityPatch.js - - test/test/*.js + - test/scenario/*.js - test/*.js exclude: diff --git a/src/Resource.js b/src/Resource.js index 0ff46726..587c331e 100644 --- a/src/Resource.js +++ b/src/Resource.js @@ -83,7 +83,7 @@ ResourceFactory.prototype = { } var value = action.isArray ? [] : new Resource(data); - self.xhr.method(action.method, route.url($.extend({}, action.params || {}, extractParams(data), params)), data, function(response) { + self.xhr(action.method, route.url($.extend({}, action.params || {}, extractParams(data), params)), data, function(response) { if (action.isArray) { foreach(response, function(item){ value.push(new Resource(item)); diff --git a/test/ResourceSpec.js b/test/ResourceSpec.js index 799c7378..0c7af00a 100644 --- a/test/ResourceSpec.js +++ b/test/ResourceSpec.js @@ -61,7 +61,7 @@ describe("resource", function() { beforeEach(function(){ xhr = new MockXHR(); - resource = new ResourceFactory(xhr); + resource = new ResourceFactory(_(xhr.method).bind(xhr)); CreditCard = resource.route('/CreditCard/:id:verb', {id:'@id.key'}, { charge:{ method:'POST', @@ -80,6 +80,11 @@ describe("resource", function() { expect(typeof CreditCard.query).toBe('function'); }); + it('should default to empty parameters', function(){ + xhr.expectGET('URL').respond({}); + resource.route('URL').query(); + }); + it("should build resource with default param", function(){ xhr.expectGET('/Order/123/Line/456.visa?minimum=0.05').respond({id:'abc'}); var LineItem = resource.route('/Order/:orderId/Line/:id:verb', {orderId: '123', id: '@id.key', verb:'.visa', minimum:0.05}); diff --git a/test/scenario/StepsTest.js b/test/scenario/StepsTest.js index 9d64d0a9..32ef637d 100644 --- a/test/scenario/StepsTest.js +++ b/test/scenario/StepsTest.js @@ -2,6 +2,6 @@ StepsTest = TestCase("StepsTest"); StepsTest.prototype.testGivenDataset=function(){ var self = {frame:{}, dataset:[]}; - angular.test.GIVEN.dataset.call(self); + angular.scenario.GIVEN.dataset.call(self); assertEquals('$DATASET:{"dataset":[]}', self.frame.name); }; -- cgit v1.2.3 From 7634a3ed5227f8bc2a2ba83752d0e2c78adb6051 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Thu, 18 Mar 2010 12:20:06 -0700 Subject: initial revision of new plugable compiler --- src/Angular.js | 10 ++- src/DataStore.js | 32 ++++----- src/Resource.js | 4 +- src/Scope.js | 5 ++ src/directives.js | 14 ++-- test/CompilerSpec.js | 186 +++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 224 insertions(+), 27 deletions(-) create mode 100644 test/CompilerSpec.js diff --git a/src/Angular.js b/src/Angular.js index 9b3634be..39a6e91d 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -24,9 +24,14 @@ var consoleNode, msie, jQuery = window['jQuery'] || window['$'], // weirdness to make IE happy foreach = _.each, extend = _.extend, + slice = Array.prototype.slice, identity = _.identity, angular = window['angular'] || (window['angular'] = {}), angularValidator = angular['validator'] || (angular['validator'] = {}), + angularDirective = angular['directive'] || (angular['directive'] = function(name, fn){ + if (fn) {angularDirective[name] = fn;}; + return angularDirective[name]; + }), angularFilter = angular['filter'] || (angular['filter'] = {}), angularFormatter = angular['formatter'] || (angular['formatter'] = {}), angularCallbacks = angular['callbacks'] || (angular['callbacks'] = {}), @@ -37,7 +42,7 @@ angular['copy'] = copy; var isVisible = isVisible || function (element) { return jQuery(element).is(":visible"); -} +}; function log(a, b, c){ var console = window['console']; @@ -154,12 +159,13 @@ function escapeAttr(html) { } function bind(_this, _function) { + var curryArgs = slice.call(arguments, 2, arguments.length); if (!_this) throw "Missing this"; if (!_.isFunction(_function)) throw "Missing function"; return function() { - return _function.apply(_this, arguments); + return _function.apply(_this, curryArgs.concat(slice.call(arguments, 0, arguments.length))); }; } diff --git a/src/DataStore.js b/src/DataStore.js index 789b8f71..70bcc623 100644 --- a/src/DataStore.js +++ b/src/DataStore.js @@ -29,7 +29,7 @@ DataStore.prototype = { } return cachedDocument; }, - + load: function(instance, id, callback, failure) { if (id && id !== '*') { var self = this; @@ -43,7 +43,7 @@ DataStore.prototype = { } return instance; }, - + loadMany: function(entity, ids, callback) { var self=this; var list = []; @@ -58,7 +58,7 @@ DataStore.prototype = { }); return list; }, - + loadOrCreate: function(instance, id, callback) { var self=this; return this.load(instance, id, callback, function(response){ @@ -70,7 +70,7 @@ DataStore.prototype = { } }); }, - + loadAll: function(entity, callback) { var self = this; var list = []; @@ -89,7 +89,7 @@ DataStore.prototype = { }); return list; }, - + save: function(document, callback) { var self = this; var data = {}; @@ -109,7 +109,7 @@ DataStore.prototype = { callback(document); }); }, - + remove: function(document, callback) { var self = this; var data = {}; @@ -127,7 +127,7 @@ DataStore.prototype = { (callback||noop)(response); }); }, - + _jsonRequest: function(request, callback, failure) { request['$$callback'] = callback; request['$$failure'] = failure||function(response){ @@ -135,7 +135,7 @@ DataStore.prototype = { }; this.bulkRequest.push(request); }, - + flush: function() { if (this.bulkRequest.length === 0) return; var self = this; @@ -169,7 +169,7 @@ DataStore.prototype = { } this.post(bulkRequest, callback); }, - + saveScope: function(scope, callback) { var saveCounter = 1; function onSaveDone() { @@ -186,7 +186,7 @@ DataStore.prototype = { } onSaveDone(); }, - + query: function(type, query, arg, callback){ var self = this; var queryList = []; @@ -205,7 +205,7 @@ DataStore.prototype = { }); return queryList; }, - + entities: function(callback) { var entities = []; var self = this; @@ -218,7 +218,7 @@ DataStore.prototype = { }); return entities; }, - + documentCountsByUser: function(){ var counts = {}; var self = this; @@ -227,7 +227,7 @@ DataStore.prototype = { }); return counts; }, - + userDocumentIdsByEntity: function(user){ var ids = {}; var self = this; @@ -236,7 +236,7 @@ DataStore.prototype = { }); return ids; }, - + entity: function(name, defaults){ if (!name) { return DataStore.NullEntity; @@ -271,7 +271,7 @@ DataStore.prototype = { }); return entity; }, - + join: function(join){ function fn(){ throw "Joined entities can not be instantiated into a document."; @@ -327,4 +327,4 @@ DataStore.prototype = { }; return fn; } -}; \ No newline at end of file +}; diff --git a/src/Resource.js b/src/Resource.js index 587c331e..971ad6e5 100644 --- a/src/Resource.js +++ b/src/Resource.js @@ -79,7 +79,7 @@ ResourceFactory.prototype = { case 1: if (isPost) data = a1; else params = a1; break; case 0: break; default: - throw "Expected between 0-3 arguments [params, data, callback], got " + arguments.length + " arguments." + throw "Expected between 0-3 arguments [params, data, callback], got " + arguments.length + " arguments."; } var value = action.isArray ? [] : new Resource(data); @@ -109,7 +109,7 @@ ResourceFactory.prototype = { case 1: if (typeof a1 == 'function') callback = a1; else params = a1; case 0: break; default: - throw "Expected between 1-3 arguments [params, data, callback], got " + arguments.length + " arguments." + throw "Expected between 1-3 arguments [params, data, callback], got " + arguments.length + " arguments."; } var self = this; Resource[name](params, this, function(response){ diff --git a/src/Scope.js b/src/Scope.js index daf4b36c..442477b4 100644 --- a/src/Scope.js +++ b/src/Scope.js @@ -10,6 +10,7 @@ function Scope(initialState, name) { if (name == "ROOT") { this.state['$root'] = this.state; } + this.set('$watch', bind(this, this.addWatchListener)); }; Scope.expressionCache = {}; @@ -202,5 +203,9 @@ Scope.prototype = { } }); return fired; + }, + + apply: function(fn) { + fn.apply(this.state, slice(arguments, 0, arguments.length)); } }; diff --git a/src/directives.js b/src/directives.js index 0e99d633..7c5cc257 100644 --- a/src/directives.js +++ b/src/directives.js @@ -4,7 +4,7 @@ angular.directive("auth", function(expression, element){ if(expression == "eager") { this.$users.fetchCurrent(); } - } + }; }); @@ -30,7 +30,7 @@ angular.directive("entity", function(expression, element){ angular.directive("init", function(expression, element){ return function(){ this.$eval(expresssion); - } + }; }); @@ -49,8 +49,8 @@ angular.directive("bind", function(expression, element){ // becomes // link angular.directive("bind-attr", function(expression, element){ - var jElement = jQuery(element); - return function(){ + return function(expression, element){ + var jElement = jQuery(element); this.$watch(expression, _(jElement.attr).bind(jElement)); }; }); @@ -58,7 +58,7 @@ angular.directive("bind-attr", function(expression, element){ angular.directive("repeat", function(expression, element){ var anchor = document.createComment(expression); jQuery(element).replace(anchor); - var template = this.compile(element); + var template = this.templetize(element); var lhs = "item"; var rhs = "items"; var children = []; @@ -103,7 +103,7 @@ angular.directive("action", function(expression, element){ angular.directive("eval", function(expression, element){ return function(){ this.$onUpdate( expression); - } + }; }); //ng-watch //
      @@ -113,7 +113,7 @@ angular.directive("watch", function(expression, element){ }; // parse return function(){ this.$watch(watches); - } + }; }); //widget related diff --git a/test/CompilerSpec.js b/test/CompilerSpec.js new file mode 100644 index 00000000..35e0e605 --- /dev/null +++ b/test/CompilerSpec.js @@ -0,0 +1,186 @@ +function Template() { + this.paths = []; + this.children = []; + this.inits = []; +} + +Template.prototype = { + init: function(element, scope) { + foreach(this.inits, function(fn) { + scope.apply(fn, element); + }); + + var i, + childNodes = element.childNodes, + children = this.children, + paths = this.paths, + length = paths.length; + for (i = 0; i < length; i++) { + children[i].init(childNodes[paths[i]], scope); + } + }, + + addInit:function(init) { + if (init) { + this.inits.push(init); + } + }, + + + addChild: function(index, template) { + this.paths.push(index); + this.children.push(template); + } +}; + +function Compiler(directives){ + this.directives = directives; +} + +DIRECTIVE = /^ng-(.*)$/; + +/** + * return { + * element: + * init: function(element){...} + * } + * + * internal data structure: { + * paths: [4, 5, 6], + * directive: name, + * init: function(expression, element){} + * } + * + * template : { + * inits: [fn(), fn()} + * paths: [1, 5], + * templates: [ + * inits: [] + * paths: [] + * templates: + * ] + * } + */ +Compiler.prototype = { + compile: function(element) { + var template = this.templetize(element); + return function(){ + var scope = new Scope(); + return { + scope: scope, + element:element, + init: bind(template, template.init, element, scope) + }; + }; + }, + + templetize: function(element){ + var items, item, length, i, directive, init, template, + childTemplate, recurse = true; + + // Process attributes/directives + for (i = 0, items = element.attributes, length = items.length; + i < length; i++) { + item = items[i]; + var match = item.name.match(DIRECTIVE); + if (match) { + directive = this.directives[match[1]]; + if (directive) { + init = directive.call({}, item.value, element); + template = template || new Template(); + template.addInit(init); + recurse = recurse && init; + } + } + } + + // Process children + if (recurse) { + for (i = 0, items = element.childNodes, length = items.length; + i < length; i++) { + if(childTemplate = this.templetize(items[i])) { + template = template || new Template(); + template.addChild(i, childTemplate); + } + } + } + return template; + } +}; + +describe('compiler', function(){ + function element(html) { + return jQuery(html)[0]; + } + + var compiler, directives, compile, log; + + beforeEach(function(){ + log = ""; + directives = { + hello: function(expression, element){ + log += "hello "; + return function() { + log += expression; + }; + }, + + watch: function(expression, element){ + return function() { + this.$watch(expression, function(val){ + log += ":" + val; + }); + }; + } + + }; + compiler = new Compiler(directives); + compile = function(html){ + var e = element(html); + var view = compiler.compile(e)(e); + view.init(); + return view.scope; + }; + }); + + it('should recognize a directive', function(){ + var e = element('
      '); + directives.directive = function(expression, element){ + log += "found"; + expect(expression).toEqual("expr"); + expect(element).toEqual(e); + return function initFn() { + log += ":init"; + }; + }; + var template = compiler.compile(e); + var init = template(e).init; + expect(log).toEqual("found"); + init(); + expect(log).toEqual("found:init"); + }); + + it('should recurse to children', function(){ + var scope = compile('
      '); + expect(log).toEqual("hello misko"); + }); + + it('should watch scope', function(){ + var scope = compile(''); + expect(log).toEqual(""); + scope.updateView(); + scope.set('name', 'misko'); + scope.updateView(); + scope.updateView(); + scope.set('name', 'adam'); + scope.updateView(); + scope.updateView(); + expect(log).toEqual(":misko:adam"); + }); + + it('should prevent recursion', function(){ + directives.stop = function(){ return false; }; + var scope = compile(''); + expect(log).toEqual("hello misko"); + }); +}); -- cgit v1.2.3 From df607da0d1b9726bce6584238fe3ad7e9b65a966 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Thu, 18 Mar 2010 14:43:49 -0700 Subject: support for templates --- lib/jstestdriver/JsTestDriver.jar | Bin 3090800 -> 3092033 bytes src/Scope.js | 15 ++++-- src/directives.js | 8 ++-- test/CompilerSpec.js | 95 +++++++++++++++++++++++++++----------- 4 files changed, 82 insertions(+), 36 deletions(-) diff --git a/lib/jstestdriver/JsTestDriver.jar b/lib/jstestdriver/JsTestDriver.jar index 1a37d230..2c7a5154 100644 Binary files a/lib/jstestdriver/JsTestDriver.jar and b/lib/jstestdriver/JsTestDriver.jar differ diff --git a/src/Scope.js b/src/Scope.js index 442477b4..3633f960 100644 --- a/src/Scope.js +++ b/src/Scope.js @@ -1,5 +1,6 @@ function Scope(initialState, name) { this.widgets = []; + this.evals = []; this.watchListeners = {}; this.name = name; initialState = initialState || {}; @@ -11,6 +12,7 @@ function Scope(initialState, name) { this.state['$root'] = this.state; } this.set('$watch', bind(this, this.addWatchListener)); + this.set('$eval', bind(this, this.addEval)); }; Scope.expressionCache = {}; @@ -48,17 +50,23 @@ Scope.prototype = { updateView: function() { var self = this; this.fireWatchers(); - _.each(this.widgets, function(widget){ + foreach(this.widgets, function(widget){ self.evalWidget(widget, "", {}, function(){ this.updateView(self); }); }); + foreach(this.evals, bind(this, this.apply)); }, addWidget: function(controller) { if (controller) this.widgets.push(controller); }, + addEval: function(fn, listener) { + // todo: this should take a function/string and a listener + this.evals.push(fn); + }, + isProperty: function(exp) { for ( var i = 0; i < exp.length; i++) { var ch = exp.charAt(i); @@ -190,8 +198,7 @@ Scope.prototype = { }, fireWatchers: function() { - var self = this; - var fired = false; + var self = this, fired = false; foreach(this.watchListeners, function(watcher) { var value = self.eval(watcher.expression); if (value !== watcher.lastValue) { @@ -206,6 +213,6 @@ Scope.prototype = { }, apply: function(fn) { - fn.apply(this.state, slice(arguments, 0, arguments.length)); + fn.apply(this.state, slice.call(arguments, 1, arguments.length)); } }; diff --git a/src/directives.js b/src/directives.js index 7c5cc257..26cbfe2c 100644 --- a/src/directives.js +++ b/src/directives.js @@ -58,12 +58,12 @@ angular.directive("bind-attr", function(expression, element){ angular.directive("repeat", function(expression, element){ var anchor = document.createComment(expression); jQuery(element).replace(anchor); - var template = this.templetize(element); + var template = this.compile(element); var lhs = "item"; var rhs = "items"; - var children = []; return function(){ - this.$watch(rhs, function(items){ + var children = []; + this.$eval(rhs, function(items){ foreach(children, function(child){ child.element.remove(); }); @@ -102,7 +102,7 @@ angular.directive("action", function(expression, element){ //ng-eval angular.directive("eval", function(expression, element){ return function(){ - this.$onUpdate( expression); + this.$eval(expression); }; }); //ng-watch diff --git a/test/CompilerSpec.js b/test/CompilerSpec.js index 35e0e605..4ffdf7f5 100644 --- a/test/CompilerSpec.js +++ b/test/CompilerSpec.js @@ -1,3 +1,9 @@ +/** + * Template provides directions an how to bind to a given element. + * It contains a list of init functions which need to be called to + * bind to a new instance of elements. It also provides a list + * of child paths which contain child templates + */ function Template() { this.paths = []; this.children = []; @@ -26,6 +32,11 @@ Template.prototype = { } }, + setExclusiveInit: function(init) { + this.inits = [init]; + this.addInit = noop; + }, + addChild: function(index, template) { this.paths.push(index); @@ -33,39 +44,22 @@ Template.prototype = { } }; +/////////////////////////////////// +// Compiler +////////////////////////////////// + function Compiler(directives){ this.directives = directives; } DIRECTIVE = /^ng-(.*)$/; -/** - * return { - * element: - * init: function(element){...} - * } - * - * internal data structure: { - * paths: [4, 5, 6], - * directive: name, - * init: function(expression, element){} - * } - * - * template : { - * inits: [fn(), fn()} - * paths: [1, 5], - * templates: [ - * inits: [] - * paths: [] - * templates: - * ] - * } - */ Compiler.prototype = { compile: function(element) { - var template = this.templetize(element); - return function(){ + var template = this.templetize(element) || new Template(); + return function(element){ var scope = new Scope(); + scope.element = element; return { scope: scope, element:element, @@ -79,17 +73,24 @@ Compiler.prototype = { childTemplate, recurse = true; // Process attributes/directives - for (i = 0, items = element.attributes, length = items.length; + for (i = 0, items = element.attributes || [], length = items.length; i < length; i++) { item = items[i]; var match = item.name.match(DIRECTIVE); if (match) { directive = this.directives[match[1]]; if (directive) { - init = directive.call({}, item.value, element); + init = directive.call(this, item.value, element); template = template || new Template(); - template.addInit(init); + if (directive.exclusive) { + template.setExclusiveInit(init); + i = length; // quit iterations + } else { + template.addInit(init); + } recurse = recurse && init; + } else { + error("Directive '" + match[0] + "' is not recognized."); } } } @@ -136,7 +137,7 @@ describe('compiler', function(){ }; compiler = new Compiler(directives); compile = function(html){ - var e = element(html); + var e = element("
      " + html + "
      "); var view = compiler.compile(e)(e); view.init(); return view.scope; @@ -183,4 +184,42 @@ describe('compiler', function(){ var scope = compile(''); expect(log).toEqual("hello misko"); }); + + it('should allow creation of templates', function(){ + directives.duplicate = function(expr, element){ + var template, + marker = document.createComment("marker"), + parentNode = element.parentNode; + parentNode.insertBefore(marker, element); + parentNode.removeChild(element); + element.removeAttribute("ng-duplicate"); + template = this.compile(element); + return function(marker) { + var parentNode = marker.parentNode; + this.$eval(function() { + parentNode.insertBefore( + template(element.cloneNode(true)).element, + marker.nextSibling); + }); + }; + }; + var scope = compile('beforexafter'); + expect($(scope.element).html()).toEqual('beforeafter'); + scope.updateView(); + expect($(scope.element).html()).toEqual('beforexafter'); + scope.updateView(); + expect($(scope.element).html()).toEqual('beforexxafter'); + }); + + it('should allow for exculsive tags which suppress others', function(){ + directives.exclusive = function(){ + return function() { + log += ('exclusive'); + }; + }; + directives.exclusive.exclusive = true; + + compile(''); + expect(log).toEqual('exclusive'); + }); }); -- cgit v1.2.3 From 509b0320899c019a60b8f397a0f1d3a8ea7dd032 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Thu, 18 Mar 2010 15:50:14 -0700 Subject: markup now works --- test/CompilerSpec.js | 46 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/test/CompilerSpec.js b/test/CompilerSpec.js index 4ffdf7f5..f428b04c 100644 --- a/test/CompilerSpec.js +++ b/test/CompilerSpec.js @@ -48,7 +48,8 @@ Template.prototype = { // Compiler ////////////////////////////////// -function Compiler(directives){ +function Compiler(markup, directives){ + this.markup = markup; this.directives = directives; } @@ -69,16 +70,26 @@ Compiler.prototype = { }, templetize: function(element){ - var items, item, length, i, directive, init, template, - childTemplate, recurse = true; + var chldrn, item, child, length, i, j, directive, init, template, + childTemplate, recurse = true, directives = this.directives, + markup = this.markup, markupLength = markup.length; + + for (i = 0, chldrn = element.childNodes, length = chldrn.length; + i < length; i++) { + if ((child = chldrn[i]).nodeType == Node.TEXT_NODE) { + for (j = 0; j < markupLength; j++) { + markup[j].call(this, child.nodeValue, child, element); + } + } + } // Process attributes/directives - for (i = 0, items = element.attributes || [], length = items.length; + for (i = 0, chldrn = element.attributes || [], length = chldrn.length; i < length; i++) { - item = items[i]; + item = chldrn[i]; var match = item.name.match(DIRECTIVE); if (match) { - directive = this.directives[match[1]]; + directive = directives[match[1]]; if (directive) { init = directive.call(this, item.value, element); template = template || new Template(); @@ -97,9 +108,10 @@ Compiler.prototype = { // Process children if (recurse) { - for (i = 0, items = element.childNodes, length = items.length; + for (i = 0, chldrn = element.childNodes, length = chldrn.length; i < length; i++) { - if(childTemplate = this.templetize(items[i])) { + if((child = chldrn[i]).nodeType != Node.TEXT_NODE && + (childTemplate = this.templetize(child))) { template = template || new Template(); template.addChild(i, childTemplate); } @@ -114,7 +126,7 @@ describe('compiler', function(){ return jQuery(html)[0]; } - var compiler, directives, compile, log; + var compiler, markup, directives, compile, log; beforeEach(function(){ log = ""; @@ -135,7 +147,8 @@ describe('compiler', function(){ } }; - compiler = new Compiler(directives); + markup = []; + compiler = new Compiler(markup, directives); compile = function(html){ var e = element("
      " + html + "
      "); var view = compiler.compile(e)(e); @@ -222,4 +235,17 @@ describe('compiler', function(){ compile(''); expect(log).toEqual('exclusive'); }); + + it('should process markup before directives', function(){ + markup.push(function(text, textNode, parentNode) { + if (text == 'middle') { + expect(textNode.nodeValue).toEqual(text); + parentNode.setAttribute('ng-hello', text); + textNode.nodeValue = 'replaced'; + } + }); + var scope = compile('beforemiddleafter'); + expect(scope.element.innerHTML).toEqual('beforereplacedafter'); + expect(log).toEqual("hello middle"); + }); }); -- cgit v1.2.3 From 79f868cbca4a14030447e321ba59348cf1eb8a02 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Thu, 18 Mar 2010 16:20:02 -0700 Subject: cleanup work --- test/CompilerSpec.js | 123 --------------------------------------------------- 1 file changed, 123 deletions(-) diff --git a/test/CompilerSpec.js b/test/CompilerSpec.js index f428b04c..4033ebdc 100644 --- a/test/CompilerSpec.js +++ b/test/CompilerSpec.js @@ -1,126 +1,3 @@ -/** - * Template provides directions an how to bind to a given element. - * It contains a list of init functions which need to be called to - * bind to a new instance of elements. It also provides a list - * of child paths which contain child templates - */ -function Template() { - this.paths = []; - this.children = []; - this.inits = []; -} - -Template.prototype = { - init: function(element, scope) { - foreach(this.inits, function(fn) { - scope.apply(fn, element); - }); - - var i, - childNodes = element.childNodes, - children = this.children, - paths = this.paths, - length = paths.length; - for (i = 0; i < length; i++) { - children[i].init(childNodes[paths[i]], scope); - } - }, - - addInit:function(init) { - if (init) { - this.inits.push(init); - } - }, - - setExclusiveInit: function(init) { - this.inits = [init]; - this.addInit = noop; - }, - - - addChild: function(index, template) { - this.paths.push(index); - this.children.push(template); - } -}; - -/////////////////////////////////// -// Compiler -////////////////////////////////// - -function Compiler(markup, directives){ - this.markup = markup; - this.directives = directives; -} - -DIRECTIVE = /^ng-(.*)$/; - -Compiler.prototype = { - compile: function(element) { - var template = this.templetize(element) || new Template(); - return function(element){ - var scope = new Scope(); - scope.element = element; - return { - scope: scope, - element:element, - init: bind(template, template.init, element, scope) - }; - }; - }, - - templetize: function(element){ - var chldrn, item, child, length, i, j, directive, init, template, - childTemplate, recurse = true, directives = this.directives, - markup = this.markup, markupLength = markup.length; - - for (i = 0, chldrn = element.childNodes, length = chldrn.length; - i < length; i++) { - if ((child = chldrn[i]).nodeType == Node.TEXT_NODE) { - for (j = 0; j < markupLength; j++) { - markup[j].call(this, child.nodeValue, child, element); - } - } - } - - // Process attributes/directives - for (i = 0, chldrn = element.attributes || [], length = chldrn.length; - i < length; i++) { - item = chldrn[i]; - var match = item.name.match(DIRECTIVE); - if (match) { - directive = directives[match[1]]; - if (directive) { - init = directive.call(this, item.value, element); - template = template || new Template(); - if (directive.exclusive) { - template.setExclusiveInit(init); - i = length; // quit iterations - } else { - template.addInit(init); - } - recurse = recurse && init; - } else { - error("Directive '" + match[0] + "' is not recognized."); - } - } - } - - // Process children - if (recurse) { - for (i = 0, chldrn = element.childNodes, length = chldrn.length; - i < length; i++) { - if((child = chldrn[i]).nodeType != Node.TEXT_NODE && - (childTemplate = this.templetize(child))) { - template = template || new Template(); - template.addChild(i, childTemplate); - } - } - } - return template; - } -}; - describe('compiler', function(){ function element(html) { return jQuery(html)[0]; -- cgit v1.2.3 From be3c7a66709952ffd21e4e59268ba6370e09d7ed Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Thu, 18 Mar 2010 17:12:38 -0700 Subject: cleanup work --- test/CompilerSpec.js | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/test/CompilerSpec.js b/test/CompilerSpec.js index 4033ebdc..2c156cc4 100644 --- a/test/CompilerSpec.js +++ b/test/CompilerSpec.js @@ -3,7 +3,7 @@ describe('compiler', function(){ return jQuery(html)[0]; } - var compiler, markup, directives, compile, log; + var compiler, markup, directives, widgets, compile, log; beforeEach(function(){ log = ""; @@ -25,7 +25,8 @@ describe('compiler', function(){ }; markup = []; - compiler = new Compiler(markup, directives); + widgets = {}; + compiler = new Compiler(markup, directives, widgets); compile = function(html){ var e = element("
      " + html + "
      "); var view = compiler.compile(e)(e); @@ -39,7 +40,7 @@ describe('compiler', function(){ directives.directive = function(expression, element){ log += "found"; expect(expression).toEqual("expr"); - expect(element).toEqual(e); + expect(element.element).toEqual(e); return function initFn() { log += ":init"; }; @@ -78,17 +79,15 @@ describe('compiler', function(){ it('should allow creation of templates', function(){ directives.duplicate = function(expr, element){ var template, - marker = document.createComment("marker"), - parentNode = element.parentNode; - parentNode.insertBefore(marker, element); - parentNode.removeChild(element); + marker = document.createComment("marker"); + element.replaceWith(marker); element.removeAttribute("ng-duplicate"); template = this.compile(element); return function(marker) { var parentNode = marker.parentNode; this.$eval(function() { parentNode.insertBefore( - template(element.cloneNode(true)).element, + template(element.clone()).element, marker.nextSibling); }); }; @@ -116,8 +115,8 @@ describe('compiler', function(){ it('should process markup before directives', function(){ markup.push(function(text, textNode, parentNode) { if (text == 'middle') { - expect(textNode.nodeValue).toEqual(text); - parentNode.setAttribute('ng-hello', text); + expect(textNode.text()).toEqual(text); + parentNode.attr('ng-hello', text); textNode.nodeValue = 'replaced'; } }); @@ -125,4 +124,15 @@ describe('compiler', function(){ expect(scope.element.innerHTML).toEqual('beforereplacedafter'); expect(log).toEqual("hello middle"); }); + + it('should replace widgets', function(){ + widgets.button = function(element) { + element.parentNode.replaceChild(button, element); + return function(element) { + log += 'init'; + }; + }; + var scope = compile('push me'); + expect(scope.element.innerHTML).toEqual('beforereplacedafter'); + }); }); -- cgit v1.2.3 From c3eac13aa7106d099e8f09c39518051ccf939060 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Fri, 19 Mar 2010 16:41:02 -0700 Subject: showing off problem to corry --- src/Compiler.js | 188 +++++++++++++++++++++++++++++++++++++++++++++++++++ test/CompilerSpec.js | 14 ++-- 2 files changed, 194 insertions(+), 8 deletions(-) create mode 100644 src/Compiler.js diff --git a/src/Compiler.js b/src/Compiler.js new file mode 100644 index 00000000..f4d901fb --- /dev/null +++ b/src/Compiler.js @@ -0,0 +1,188 @@ +/** + * Template provides directions an how to bind to a given element. + * It contains a list of init functions which need to be called to + * bind to a new instance of elements. It also provides a list + * of child paths which contain child templates + */ +function Template() { + this.paths = []; + this.children = []; + this.inits = []; +} + +Template.prototype = { + init: function(element, scope) { + foreach(this.inits, function(fn) { + scope.apply(fn, nodeLite(element)); + }); + + var i, + childNodes = element.childNodes, + children = this.children, + paths = this.paths, + length = paths.length; + for (i = 0; i < length; i++) { + children[i].init(childNodes[paths[i]], scope); + } + }, + + addInit:function(init) { + if (init) { + this.inits.push(init); + } + }, + + setExclusiveInit: function(init) { + this.inits = [init]; + this.addInit = noop; + }, + + + addChild: function(index, template) { + this.paths.push(index); + this.children.push(template); + } +}; + +/////////////////////////////////// +//NodeLite +////////////////////////////////// + +function NodeLite(element) { + this.element = element; +} + +function nodeLite(element) { + return element instanceof NodeLite ? element : new NodeLite(element); +} + +NodeLite.prototype = { + eachTextNode: function(fn){ + var i, chldNodes = this.element.childNodes || [], size = chldNodes.length, chld; + for (i = 0; i < size; i++) { + if((chld = new NodeLite(chldNodes[i])).isText()) { + fn(chld, i); + } + } + }, + + eachNode: function(fn){ + var i, chldNodes = this.element.childNodes || [], size = chldNodes.length, chld; + for (i = 0; i < size; i++) { + if(!(chld = new NodeLite(chldNodes[i])).isText()) { + fn(chld, i); + } + } + }, + + eachAttribute: function(fn){ + var i, attrs = this.element.attributes || [], size = attrs.length, chld, attr; + for (i = 0; i < size; i++) { + var attr = attrs[i]; + fn(attr.name, attr.value); + } + }, + + replaceWith: function(replaceNode) { + this.element.parentNode.replaceChild(nodeLite(replaceNode).element, this.element); + }, + + removeAttribute: function(name) { + this.element.removeAttribute(name); + }, + + after: function(element) { + this.element.parentNode.insertBefore(element, this.element.nextSibling); + }, + + attr: function(name, value){ + if (typeof value == 'undefined') { + return this.element.getAttribute(name); + } else { + this.element.setAttribute(name); + } + }, + + isText: function() { return this.element.nodeType == Node.TEXT_NODE; }, + text: function() { return this.element.nodeValue; }, + clone: function() { return nodeLite(this.element.cloneNode(true)); } +}; + +/////////////////////////////////// +//Compiler +////////////////////////////////// + +function Compiler(markup, directives, widgets){ + this.markup = markup; + this.directives = directives; + this.widgets = widgets; +} + +DIRECTIVE = /^ng-(.*)$/; + +Compiler.prototype = { + compile: function(element) { + var template = this.templetize(nodeLite(element)) || new Template(); + return function(element){ + var scope = new Scope(); + scope.element = element; + return { + scope: scope, + element:element, + init: bind(template, template.init, element, scope) + }; + }; + }, + + templetize: function(element){ + var self = this, + markup = self.markup, + markupSize = markup.length, + directives = self.directives, + widgets = self.widgets, + recurse = true, + exclusive = false, + template; + + // process markup for text nodes only + element.eachTextNode(function(textNode){ + for (var i = 0, text = textNode.text(); i < markupSize; i++) { + markup[i].call(self, text, textNode, element); + } + }); + + // Process attributes/directives + element.eachAttribute(function(name, value){ + var match = name.match(DIRECTIVE), + directive, init; + if (!exclusive && match) { + directive = directives[match[1]]; + if (directive) { + init = directive.call(self, value, element); + template = template || new Template(); + if (directive.exclusive) { + template.setExclusiveInit(init); + exclusive = true; + } else { + template.addInit(init); + } + recurse = recurse && init; + } else { + error("Directive '" + match[0] + "' is not recognized."); + } + } + }); + + // Process non text child nodes + if (recurse) { + element.eachNode(function(child, i){ + var childTemplate = self.templetize(child); + if(childTemplate) { + template = template || new Template(); + template.addChild(i, childTemplate); + } + }); + } + return template; + } +}; diff --git a/test/CompilerSpec.js b/test/CompilerSpec.js index 2c156cc4..7bf48d18 100644 --- a/test/CompilerSpec.js +++ b/test/CompilerSpec.js @@ -78,17 +78,14 @@ describe('compiler', function(){ it('should allow creation of templates', function(){ directives.duplicate = function(expr, element){ - var template, - marker = document.createComment("marker"); - element.replaceWith(marker); + element.replaceWith(document.createComment("marker")); element.removeAttribute("ng-duplicate"); - template = this.compile(element); + var template = this.compile(element); return function(marker) { - var parentNode = marker.parentNode; this.$eval(function() { - parentNode.insertBefore( - template(element.clone()).element, - marker.nextSibling); + dump("A"); + marker.after(template(element.clone()).element); + dump("B"); }); }; }; @@ -135,4 +132,5 @@ describe('compiler', function(){ var scope = compile('push me'); expect(scope.element.innerHTML).toEqual('beforereplacedafter'); }); + }); -- cgit v1.2.3 From f6664ed7f6f6dd1f4f9756f57611a316089149cb Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Fri, 19 Mar 2010 22:18:39 -0700 Subject: tests fixed, still missing widgets --- src/Angular.js | 4 ++++ src/Compiler.js | 49 +++++++++++++++++++++++++++++-------------------- test/CompilerSpec.js | 6 ++---- 3 files changed, 35 insertions(+), 24 deletions(-) diff --git a/src/Angular.js b/src/Angular.js index 39a6e91d..8793274c 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -44,6 +44,10 @@ var isVisible = isVisible || function (element) { return jQuery(element).is(":visible"); }; +function isDefined(value){ + return typeof value !== 'undefined'; +} + function log(a, b, c){ var console = window['console']; switch(arguments.length) { diff --git a/src/Compiler.js b/src/Compiler.js index f4d901fb..3c757dd0 100644 --- a/src/Compiler.js +++ b/src/Compiler.js @@ -26,21 +26,21 @@ Template.prototype = { } }, + addInit:function(init) { if (init) { this.inits.push(init); } }, - setExclusiveInit: function(init) { - this.inits = [init]; - this.addInit = noop; - }, - addChild: function(index, template) { this.paths.push(index); this.children.push(template); + }, + + empty: function() { + return this.inits.length == 0 && this.paths.length == 0; } }; @@ -92,19 +92,25 @@ NodeLite.prototype = { }, after: function(element) { - this.element.parentNode.insertBefore(element, this.element.nextSibling); + this.element.parentNode.insertBefore(nodeLite(element).element, this.element.nextSibling); }, attr: function(name, value){ - if (typeof value == 'undefined') { - return this.element.getAttribute(name); + if (isDefined(value)) { + this.element.setAttribute(name, value); } else { - this.element.setAttribute(name); + return this.element.getAttribute(name); + } + }, + + text: function(value) { + if (isDefined(value)) { + this.element.nodeValue = value; } + return this.element.nodeValue; }, isText: function() { return this.element.nodeType == Node.TEXT_NODE; }, - text: function() { return this.element.nodeValue; }, clone: function() { return nodeLite(this.element.cloneNode(true)); } }; @@ -142,7 +148,8 @@ Compiler.prototype = { widgets = self.widgets, recurse = true, exclusive = false, - template; + directiveQueue = [], + template = new Template(); // process markup for text nodes only element.eachTextNode(function(textNode){ @@ -154,35 +161,37 @@ Compiler.prototype = { // Process attributes/directives element.eachAttribute(function(name, value){ var match = name.match(DIRECTIVE), - directive, init; + directive; if (!exclusive && match) { directive = directives[match[1]]; if (directive) { - init = directive.call(self, value, element); - template = template || new Template(); if (directive.exclusive) { - template.setExclusiveInit(init); exclusive = true; - } else { - template.addInit(init); + directiveQueue = []; } - recurse = recurse && init; + directiveQueue.push(bind(self, directive, value, element)); } else { error("Directive '" + match[0] + "' is not recognized."); } } }); + // Execute directives + foreach(directiveQueue, function(directive){ + var init = directive(); + template.addInit(init); + recurse = recurse && init; + }); + // Process non text child nodes if (recurse) { element.eachNode(function(child, i){ var childTemplate = self.templetize(child); if(childTemplate) { - template = template || new Template(); template.addChild(i, childTemplate); } }); } - return template; + return template.empty() ? null : template; } }; diff --git a/test/CompilerSpec.js b/test/CompilerSpec.js index 7bf48d18..9f02262d 100644 --- a/test/CompilerSpec.js +++ b/test/CompilerSpec.js @@ -83,9 +83,7 @@ describe('compiler', function(){ var template = this.compile(element); return function(marker) { this.$eval(function() { - dump("A"); marker.after(template(element.clone()).element); - dump("B"); }); }; }; @@ -114,7 +112,7 @@ describe('compiler', function(){ if (text == 'middle') { expect(textNode.text()).toEqual(text); parentNode.attr('ng-hello', text); - textNode.nodeValue = 'replaced'; + textNode.text('replaced'); } }); var scope = compile('beforemiddleafter'); @@ -122,7 +120,7 @@ describe('compiler', function(){ expect(log).toEqual("hello middle"); }); - it('should replace widgets', function(){ + xit('should replace widgets', function(){ widgets.button = function(element) { element.parentNode.replaceChild(button, element); return function(element) { -- cgit v1.2.3 From 84552f7f8ac3f39c4dbd7d946ae2938d63302840 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Mon, 22 Mar 2010 13:58:04 -0700 Subject: got few directives working --- src/Angular.js | 37 ++++++++--- src/Compiler.js | 149 ++++++++++++++++++++++++++------------------ src/Parser.js | 107 +++++++++++++++---------------- src/Scope.js | 52 +++++++++++----- src/directives.js | 124 +++++++++++++++++------------------- src/directivesAngularCom.js | 29 +++++++++ test.sh | 2 +- test/CompilerSpec.js | 33 +++++----- test/ParserTest.js | 19 +++--- test/directivesSpec.js | 71 +++++++++++++++++++++ 10 files changed, 391 insertions(+), 232 deletions(-) create mode 100644 src/directivesAngularCom.js create mode 100644 test/directivesSpec.js diff --git a/src/Angular.js b/src/Angular.js index 8793274c..cfffab04 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -18,23 +18,32 @@ if (typeof Node == 'undefined') { } function noop() {} +function identity($) {return $;} if (!window['console']) window['console']={'log':noop, 'error':noop}; +function extension(angular, name) { + var extPoint; + return angular[name] || (extPoint = angular[name] = function (name, fn, prop){ + if (isDefined(fn)) { + extPoint[name] = extend(fn, prop || {}); + } + return extPoint[name]; + }); +} + var consoleNode, msie, jQuery = window['jQuery'] || window['$'], // weirdness to make IE happy foreach = _.each, extend = _.extend, slice = Array.prototype.slice, - identity = _.identity, angular = window['angular'] || (window['angular'] = {}), - angularValidator = angular['validator'] || (angular['validator'] = {}), - angularDirective = angular['directive'] || (angular['directive'] = function(name, fn){ - if (fn) {angularDirective[name] = fn;}; - return angularDirective[name]; - }), - angularFilter = angular['filter'] || (angular['filter'] = {}), - angularFormatter = angular['formatter'] || (angular['formatter'] = {}), - angularCallbacks = angular['callbacks'] || (angular['callbacks'] = {}), + angularDirective = extension(angular, 'directive'), + angularMarkup = extension(angular, 'markup'), + angularWidget = extension(angular, 'widget'), + angularValidator = extension(angular, 'validator'), + angularFilter = extension(angular, 'filter'), + angularFormatter = extension(angular, 'formatter'), + angularCallbacks = extension(angular, 'callbacks'), angularAlert = angular['alert'] || (angular['alert'] = function(){ log(arguments); window.alert.apply(window, arguments); }); @@ -45,7 +54,15 @@ var isVisible = isVisible || function (element) { }; function isDefined(value){ - return typeof value !== 'undefined'; + return typeof value != 'undefined'; +} + +function isObject(value){ + return typeof value == 'object'; +} + +function isFunction(value){ + return typeof value == 'function'; } function log(a, b, c){ diff --git a/src/Compiler.js b/src/Compiler.js index 3c757dd0..5c650204 100644 --- a/src/Compiler.js +++ b/src/Compiler.js @@ -13,7 +13,7 @@ function Template() { Template.prototype = { init: function(element, scope) { foreach(this.inits, function(fn) { - scope.apply(fn, nodeLite(element)); + scope.apply(fn, jqLite(element)); }); var i, @@ -35,8 +35,10 @@ Template.prototype = { addChild: function(index, template) { - this.paths.push(index); - this.children.push(template); + if (template) { + this.paths.push(index); + this.children.push(template); + } }, empty: function() { @@ -45,31 +47,37 @@ Template.prototype = { }; /////////////////////////////////// -//NodeLite +//JQLite ////////////////////////////////// -function NodeLite(element) { +function JQLite(element) { this.element = element; } -function nodeLite(element) { - return element instanceof NodeLite ? element : new NodeLite(element); +function jqLite(element) { + if (typeof element == 'string') { + var div = document.createElement('div'); + div.innerHTML = element; + element = div.childNodes[0]; + } + return element instanceof JQLite ? element : new JQLite(element); } -NodeLite.prototype = { +JQLite.prototype = { eachTextNode: function(fn){ var i, chldNodes = this.element.childNodes || [], size = chldNodes.length, chld; for (i = 0; i < size; i++) { - if((chld = new NodeLite(chldNodes[i])).isText()) { + if((chld = new JQLite(chldNodes[i])).isText()) { fn(chld, i); } } }, + eachNode: function(fn){ var i, chldNodes = this.element.childNodes || [], size = chldNodes.length, chld; for (i = 0; i < size; i++) { - if(!(chld = new NodeLite(chldNodes[i])).isText()) { + if(!(chld = new JQLite(chldNodes[i])).isText()) { fn(chld, i); } } @@ -84,34 +92,51 @@ NodeLite.prototype = { }, replaceWith: function(replaceNode) { - this.element.parentNode.replaceChild(nodeLite(replaceNode).element, this.element); + this.element.parentNode.replaceChild(jqLite(replaceNode).element, this.element); }, - removeAttribute: function(name) { + remove: function() { + this.element.parentNode.removeChild(this.element); + }, + + removeAttr: function(name) { this.element.removeAttribute(name); }, after: function(element) { - this.element.parentNode.insertBefore(nodeLite(element).element, this.element.nextSibling); + this.element.parentNode.insertBefore(jqLite(element).element, this.element.nextSibling); }, attr: function(name, value){ - if (isDefined(value)) { - this.element.setAttribute(name, value); + var e = this.element; + if (isObject(name)) { + foreach(name, function(value, name){ + e.setAttribute(name, value); + }); + } else if (isDefined(value)) { + e.setAttribute(name, value); } else { - return this.element.getAttribute(name); + return e.getAttribute(name); } }, text: function(value) { if (isDefined(value)) { - this.element.nodeValue = value; + this.element.textContent = value; } - return this.element.nodeValue; + return this.element.textContent; }, + html: function(value) { + if (isDefined(value)) { + this.element.innerHTML = value; + } + return this.element.innerHTML; + }, + + parent: function() { return jqLite(this.element.parentNode);}, isText: function() { return this.element.nodeType == Node.TEXT_NODE; }, - clone: function() { return nodeLite(this.element.cloneNode(true)); } + clone: function() { return jqLite(this.element.cloneNode(true)); } }; /////////////////////////////////// @@ -124,14 +149,14 @@ function Compiler(markup, directives, widgets){ this.widgets = widgets; } -DIRECTIVE = /^ng-(.*)$/; - Compiler.prototype = { - compile: function(element) { - var template = this.templetize(nodeLite(element)) || new Template(); - return function(element){ - var scope = new Scope(); + compile: function(rawElement) { + rawElement = jqLite(rawElement); + var template = this.templatize(rawElement) || new Template(); + return function(element, parentScope){ + var scope = new Scope(parentScope); scope.element = element; + // todo return should be a scope with everything already set on it as element return { scope: scope, element:element, @@ -140,57 +165,57 @@ Compiler.prototype = { }; }, - templetize: function(element){ + templatize: function(element){ var self = this, + elementName = element.element.nodeName, + widgets = self.widgets, + widget = widgets[elementName], markup = self.markup, markupSize = markup.length, directives = self.directives, - widgets = self.widgets, - recurse = true, + descend = true, exclusive = false, directiveQueue = [], - template = new Template(); - - // process markup for text nodes only - element.eachTextNode(function(textNode){ - for (var i = 0, text = textNode.text(); i < markupSize; i++) { - markup[i].call(self, text, textNode, element); - } - }); + template = new Template(), + selfApi = { + compile: bind(self, self.compile), + reference:function(name) {return jqLite(document.createComment(name));}, + descend: function(value){ if(isDefined(value)) descend = value; return descend;} + }; + + if (widget) { + template.addInit(widget.call(selfApi, element)); + } else { + // process markup for text nodes only + element.eachTextNode(function(textNode){ + for (var i = 0, text = textNode.text(); i < markupSize; i++) { + markup[i].call(selfApi, text, textNode, element); + } + }); - // Process attributes/directives - element.eachAttribute(function(name, value){ - var match = name.match(DIRECTIVE), - directive; - if (!exclusive && match) { - directive = directives[match[1]]; - if (directive) { + // Process attributes/directives + element.eachAttribute(function(name, value){ + var directive = directives[name]; + if (!exclusive && directive) { if (directive.exclusive) { exclusive = true; directiveQueue = []; } - directiveQueue.push(bind(self, directive, value, element)); - } else { - error("Directive '" + match[0] + "' is not recognized."); + directiveQueue.push(bind(selfApi, directive, value, element)); } - } - }); - - // Execute directives - foreach(directiveQueue, function(directive){ - var init = directive(); - template.addInit(init); - recurse = recurse && init; - }); + }); - // Process non text child nodes - if (recurse) { - element.eachNode(function(child, i){ - var childTemplate = self.templetize(child); - if(childTemplate) { - template.addChild(i, childTemplate); - } + // Execute directives + foreach(directiveQueue, function(directive){ + template.addInit(directive()); }); + + // Process non text child nodes + if (descend) { + element.eachNode(function(child, i){ + template.addChild(i, self.templatize(child)); + }); + } } return template.empty() ? null : template; } diff --git a/src/Parser.js b/src/Parser.js index b59b21a7..c18a6250 100644 --- a/src/Parser.js +++ b/src/Parser.js @@ -40,7 +40,7 @@ Lexer.prototype = { return false; } }, - + parse: function() { var tokens = this.tokens; var OPERATORS = Lexer.OPERATORS; @@ -103,22 +103,22 @@ Lexer.prototype = { } return tokens; }, - + isNumber: function(ch) { return '0' <= ch && ch <= '9'; }, - + isWhitespace: function(ch) { return ch == ' ' || ch == '\r' || ch == '\t' || ch == '\n' || ch == '\v'; }, - + isIdent: function(ch) { return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || '_' == ch || ch == '$'; }, - + readNumber: function() { var number = ""; var start = this.index; @@ -135,7 +135,7 @@ Lexer.prototype = { this.tokens.push({index:start, text:number, fn:function(){return number;}}); }, - + readIdent: function() { var ident = ""; var start = this.index; @@ -157,15 +157,17 @@ Lexer.prototype = { } this.tokens.push({index:start, text:ident, fn:fn}); }, - + readString: function(quote) { var start = this.index; var dateParseLength = this.dateParseLength; this.index++; var string = ""; + var rawString = quote; var escape = false; while (this.index < this.text.length) { var ch = this.text.charAt(this.index); + rawString += ch; if (escape) { if (ch == 'u') { var hex = this.text.substring(this.index + 1, this.index + 5); @@ -184,7 +186,7 @@ Lexer.prototype = { escape = true; } else if (ch == quote) { this.index++; - this.tokens.push({index:start, text:string, + this.tokens.push({index:start, text:rawString, string:string, fn:function(){ return (string.length == dateParseLength) ? angular['String']['toDate'](string) : string; @@ -199,7 +201,7 @@ Lexer.prototype = { this.text.substring(start) + "] starting at column '" + (start+1) + "' in expression '" + this.text + "'."; }, - + readRegexp: function(quote) { var start = this.index; this.index++; @@ -249,18 +251,18 @@ Parser.ZERO = function(){ Parser.prototype = { error: function(msg, token) { - throw "Token '" + token.text + - "' is " + msg + " at column='" + - (token.index + 1) + "' of expression '" + + throw "Token '" + token.text + + "' is " + msg + " at column='" + + (token.index + 1) + "' of expression '" + this.text + "' starting at '" + this.text.substring(token.index) + "'."; }, - + peekToken: function() { - if (this.tokens.length === 0) + if (this.tokens.length === 0) throw "Unexpected end of expression: " + this.text; return this.tokens[0]; }, - + peek: function(e1, e2, e3, e4) { var tokens = this.tokens; if (tokens.length > 0) { @@ -273,7 +275,7 @@ Parser.prototype = { } return false; }, - + expect: function(e1, e2, e3, e4){ var token = this.peek(e1, e2, e3, e4); if (token) { @@ -283,7 +285,7 @@ Parser.prototype = { } return false; }, - + consume: function(e1){ if (!this.expect(e1)) { var token = this.peek(); @@ -293,30 +295,30 @@ Parser.prototype = { this.text.substring(token.index) + "'."; } }, - + _unary: function(fn, right) { return function(self) { return fn(self, right(self)); }; }, - + _binary: function(left, fn, right) { return function(self) { return fn(self, left(self), right(self)); }; }, - + hasTokens: function () { return this.tokens.length > 0; }, - + assertAllConsumed: function(){ if (this.tokens.length !== 0) { throw "Did not understand '" + this.text.substring(this.tokens[0].index) + "' while evaluating '" + this.text + "'."; } }, - + statements: function(){ var statements = []; while(true) { @@ -335,7 +337,7 @@ Parser.prototype = { } } }, - + filterChain: function(){ var left = this.expression(); var token; @@ -347,15 +349,15 @@ Parser.prototype = { } } }, - + filter: function(){ return this._pipeFunction(angularFilter); }, - + validator: function(){ return this._pipeFunction(angularValidator); }, - + _pipeFunction: function(fnScope){ var fn = this.functionIdent(fnScope); var argsFn = []; @@ -373,7 +375,7 @@ Parser.prototype = { var _this = this; foreach(self, function(v, k) { if (k.charAt(0) == '$') { - _this[k] = v; + _this[k] = v; } }); }; @@ -386,11 +388,11 @@ Parser.prototype = { } } }, - + expression: function(){ return this.throwStmt(); }, - + throwStmt: function(){ if (this.expect('throw')) { var throwExp = this.assignment(); @@ -401,7 +403,7 @@ Parser.prototype = { return this.assignment(); } }, - + assignment: function(){ var left = this.logicalOR(); var token; @@ -417,7 +419,7 @@ Parser.prototype = { return left; } }, - + logicalOR: function(){ var left = this.logicalAND(); var token; @@ -429,7 +431,7 @@ Parser.prototype = { } } }, - + logicalAND: function(){ var left = this.equality(); var token; @@ -438,7 +440,7 @@ Parser.prototype = { } return left; }, - + equality: function(){ var left = this.relational(); var token; @@ -447,7 +449,7 @@ Parser.prototype = { } return left; }, - + relational: function(){ var left = this.additive(); var token; @@ -456,7 +458,7 @@ Parser.prototype = { } return left; }, - + additive: function(){ var left = this.multiplicative(); var token; @@ -465,7 +467,7 @@ Parser.prototype = { } return left; }, - + multiplicative: function(){ var left = this.unary(); var token; @@ -474,7 +476,7 @@ Parser.prototype = { } return left; }, - + unary: function(){ var token; if (this.expect('+')) { @@ -487,7 +489,7 @@ Parser.prototype = { return this.primary(); } }, - + functionIdent: function(fnScope) { var token = this.expect(); var element = token.text.split('.'); @@ -504,7 +506,7 @@ Parser.prototype = { } return instance; }, - + primary: function() { var primary; if (this.expect('(')) { @@ -540,7 +542,7 @@ Parser.prototype = { } return primary; }, - + closure: function(hasArgs) { var args = []; if (hasArgs) { @@ -566,7 +568,7 @@ Parser.prototype = { }; }; }, - + fieldAccess: function(object) { var field = this.expect().text; var fn = function (self){ @@ -575,7 +577,7 @@ Parser.prototype = { fn.isAssignable = field; return fn; }, - + objectIndex: function(obj) { var indexFn = this.expression(); this.consume(']'); @@ -592,7 +594,7 @@ Parser.prototype = { }; } }, - + functionCall: function(fn) { var argsFn = []; if (this.peekToken().text != ')') { @@ -614,7 +616,7 @@ Parser.prototype = { } }; }, - + // This is used with json array declaration arrayDeclaration: function () { var elementFns = []; @@ -632,12 +634,13 @@ Parser.prototype = { return array; }; }, - + object: function () { var keyValues = []; if (this.peekToken().text != '}') { do { - var key = this.expect().text; + var token = this.expect(), + key = token.string || token.text; this.consume(":"); var value = this.expression(); keyValues.push({key:key, value:value}); @@ -654,7 +657,7 @@ Parser.prototype = { return object; }; }, - + entityDeclaration: function () { var decl = []; while(this.hasTokens()) { @@ -671,7 +674,7 @@ Parser.prototype = { return code; }; }, - + entityDecl: function () { var entity = this.expect().text; var instance; @@ -690,16 +693,16 @@ Parser.prototype = { var document = Entity(); document['$$anchor'] = instance; self.scope.set(instance, document); - return "$anchor." + instance + ":{" + + return "$anchor." + instance + ":{" + instance + "=" + entity + ".load($anchor." + instance + ");" + - instance + ".$$anchor=" + angular['String']['quote'](instance) + ";" + + instance + ".$$anchor=" + angular['String']['quote'](instance) + ";" + "};"; } else { return ""; } }; }, - + watch: function () { var decl = []; while(this.hasTokens()) { @@ -716,7 +719,7 @@ Parser.prototype = { } }; }, - + watchDecl: function () { var anchorName = this.expect().text; this.consume(":"); diff --git a/src/Scope.js b/src/Scope.js index 3633f960..d22604fd 100644 --- a/src/Scope.js +++ b/src/Scope.js @@ -1,18 +1,23 @@ function Scope(initialState, name) { - this.widgets = []; - this.evals = []; - this.watchListeners = {}; - this.name = name; + var self = this; + self.widgets = []; + self.evals = []; + self.watchListeners = {}; + self.name = name; initialState = initialState || {}; var State = function(){}; State.prototype = initialState; - this.state = new State(); - this.state['$parent'] = initialState; + self.state = new State(); + extend(self.state, { + '$parent': initialState, + '$watch': bind(self, self.addWatchListener), + '$eval': bind(self, self.eval), + // change name to onEval? + '$addEval': bind(self, self.addEval) + }); if (name == "ROOT") { - this.state['$root'] = this.state; + self.state['$root'] = self.state; } - this.set('$watch', bind(this, this.addWatchListener)); - this.set('$eval', bind(this, this.addEval)); }; Scope.expressionCache = {}; @@ -47,6 +52,7 @@ Scope.getter = function(instance, path) { }; Scope.prototype = { + // TODO: rename to update? or eval? updateView: function() { var self = this; this.fireWatchers(); @@ -64,7 +70,13 @@ Scope.prototype = { addEval: function(fn, listener) { // todo: this should take a function/string and a listener - this.evals.push(fn); + // todo: this is a hack, which will need to be cleaned up. + var self = this, + listenFn = listener || noop, + expr = bind(self, self.compile(fn), {scope: self, self: self.state}); + this.evals.push(function(){ + self.apply(listenFn, expr()); + }); }, isProperty: function(exp) { @@ -103,15 +115,21 @@ Scope.prototype = { this.eval(expressionText + "=" + toJson(value)); }, - eval: function(expressionText, context) { -// log('Scope.eval', expressionText); - var expression = Scope.expressionCache[expressionText]; - if (!expression) { - var parser = new Parser(expressionText); - expression = parser.statements(); + compile: function(exp) { + if (isFunction(exp)) return exp; + var expFn = Scope.expressionCache[exp]; + if (!expFn) { + var parser = new Parser(exp); + expFn = parser.statements(); parser.assertAllConsumed(); - Scope.expressionCache[expressionText] = expression; + Scope.expressionCache[exp] = expFn; } + return expFn; + }, + + eval: function(expressionText, context) { +// log('Scope.eval', expressionText); + var expression = this.compile(expressionText); context = context || {}; context.scope = this; context.self = this.state; diff --git a/src/directives.js b/src/directives.js index 26cbfe2c..66a5e864 100644 --- a/src/directives.js +++ b/src/directives.js @@ -1,86 +1,82 @@ - -angular.directive("auth", function(expression, element){ +angularDirective("ng-init", function(expression){ return function(){ - if(expression == "eager") { - this.$users.fetchCurrent(); - } - }; -}); - - -//expression = "book=Book:{year=2000}" -angular.directive("entity", function(expression, element){ - //parse expression, ignore element - var entityName; // "Book"; - var instanceName; // "book"; - var defaults; // {year: 2000}; - - parse(expression); - - return function(){ - this[entityName] = this.$datastore.entity(entityName, defaults); - this[instanceName] = this[entityName](); - this.$watch("$anchor."+instanceName, function(newAnchor){ - this[instanceName] = this[entityName].get(this.$anchor[instanceName]); - }); + this.$eval(expression); }; }); - -angular.directive("init", function(expression, element){ +angularDirective("ng-eval", function(expression){ return function(){ - this.$eval(expresssion); + this.$addEval(expression); }; }); - -//translation of {{ }} to ng-bind is external to this -angular.directive("bind", function(expression, element){ - return function() { +angular.directive("ng-bind", function(expression){ + return function(element) { this.$watch(expression, function(value){ - element.innerText = value; + element.text(value); }); }; }); - -// translation of {{ }} to ng-bind-attr is external to this -// link -// becomes -// link -angular.directive("bind-attr", function(expression, element){ - return function(expression, element){ - var jElement = jQuery(element); - this.$watch(expression, _(jElement.attr).bind(jElement)); +angular.directive("ng-bind-attr", function(expression){ + return function(element){ + this.$watch(expression, bind(element, element.attr)); }; }); -angular.directive("repeat", function(expression, element){ - var anchor = document.createComment(expression); - jQuery(element).replace(anchor); - var template = this.compile(element); - var lhs = "item"; - var rhs = "items"; +angular.directive("ng-non-bindable", function(){ + this.descend(false); +}); + +angular.directive("ng-repeat", function(expression, element){ + var reference = this.reference("ng-repeat: " + expression), + r = element.removeAttr('ng-repeat'), + template = this.compile(element), + path = expression.split(' in '), + lhs = path[0], + rhs = path[1]; + var parent = element.parent(); + element.replaceWith(reference); return function(){ - var children = []; - this.$eval(rhs, function(items){ - foreach(children, function(child){ - child.element.remove(); - }); - foreach(items, function(item){ - var child = template(item); // create scope - element.addChild(child.element, anchor); - children.push(child); + var children = [], + currentScope = this; + this.$addEval(rhs, function(items){ + var index = 0, childCount = children.length, childScope, lastElement = reference; + foreach(items, function(value, key){ + if (index < childCount) { + // reuse existing child + childScope = children[index]; + } else { + // grow children + childScope = template(element.clone(), currentScope); + childScope.init(); + childScope.scope.set('$index', index); + childScope.element.attr('ng-index', index); + lastElement.after(childScope.element); + children.push(childScope); + } + childScope.scope.set(lhs, value); + childScope.scope.updateView(); + lastElement = childScope.element; + index ++; }); + // shrink children + while(children.length > index) { + children.pop().element.remove(); + } }); }; -}); +}, {exclusive: true}); + + +///////////////////////////////////////// +///////////////////////////////////////// +///////////////////////////////////////// +///////////////////////////////////////// +///////////////////////////////////////// + -//ng-non-bindable -angular.directive("non-bindable", function(expression, element){ - return false; -}); //Styling // @@ -99,12 +95,6 @@ angular.directive("action", function(expression, element){ }; }); -//ng-eval -angular.directive("eval", function(expression, element){ - return function(){ - this.$eval(expression); - }; -}); //ng-watch //
      angular.directive("watch", function(expression, element){ diff --git a/src/directivesAngularCom.js b/src/directivesAngularCom.js new file mode 100644 index 00000000..84032bdd --- /dev/null +++ b/src/directivesAngularCom.js @@ -0,0 +1,29 @@ + +angular.directive("auth", function(expression, element){ + return function(){ + if(expression == "eager") { + this.$users.fetchCurrent(); + } + }; +}); + + +//expression = "book=Book:{year=2000}" +angular.directive("entity", function(expression, element){ + //parse expression, ignore element + var entityName; // "Book"; + var instanceName; // "book"; + var defaults; // {year: 2000}; + + parse(expression); + + return function(){ + this[entityName] = this.$datastore.entity(entityName, defaults); + this[instanceName] = this[entityName](); + this.$watch("$anchor."+instanceName, function(newAnchor){ + this[instanceName] = this[entityName].get(this.$anchor[instanceName]); + }); + }; +}); + + diff --git a/test.sh b/test.sh index 6b1cac89..c0d1e7ac 100755 --- a/test.sh +++ b/test.sh @@ -1,2 +1,2 @@ -java -jar lib/jstestdriver/JsTestDriver.jar --tests all | grep -v lib/jasmine +java -jar lib/jstestdriver/JsTestDriver.jar --tests all | grep -v lib/jasmine diff --git a/test/CompilerSpec.js b/test/CompilerSpec.js index 9f02262d..3ea2e473 100644 --- a/test/CompilerSpec.js +++ b/test/CompilerSpec.js @@ -36,7 +36,7 @@ describe('compiler', function(){ }); it('should recognize a directive', function(){ - var e = element('
      '); + var e = element('
      '); directives.directive = function(expression, element){ log += "found"; expect(expression).toEqual("expr"); @@ -53,12 +53,12 @@ describe('compiler', function(){ }); it('should recurse to children', function(){ - var scope = compile('
      '); + var scope = compile('
      '); expect(log).toEqual("hello misko"); }); it('should watch scope', function(){ - var scope = compile(''); + var scope = compile(''); expect(log).toEqual(""); scope.updateView(); scope.set('name', 'misko'); @@ -70,24 +70,24 @@ describe('compiler', function(){ expect(log).toEqual(":misko:adam"); }); - it('should prevent recursion', function(){ - directives.stop = function(){ return false; }; - var scope = compile(''); + it('should prevent descend', function(){ + directives.stop = function(){ this.descend(false); }; + var scope = compile(''); expect(log).toEqual("hello misko"); }); it('should allow creation of templates', function(){ directives.duplicate = function(expr, element){ element.replaceWith(document.createComment("marker")); - element.removeAttribute("ng-duplicate"); + element.removeAttr("duplicate"); var template = this.compile(element); return function(marker) { - this.$eval(function() { + this.$addEval(function() { marker.after(template(element.clone()).element); }); }; }; - var scope = compile('beforexafter'); + var scope = compile('beforexafter'); expect($(scope.element).html()).toEqual('beforeafter'); scope.updateView(); expect($(scope.element).html()).toEqual('beforexafter'); @@ -103,7 +103,7 @@ describe('compiler', function(){ }; directives.exclusive.exclusive = true; - compile(''); + compile(''); expect(log).toEqual('exclusive'); }); @@ -111,24 +111,25 @@ describe('compiler', function(){ markup.push(function(text, textNode, parentNode) { if (text == 'middle') { expect(textNode.text()).toEqual(text); - parentNode.attr('ng-hello', text); + parentNode.attr('hello', text); textNode.text('replaced'); } }); var scope = compile('beforemiddleafter'); - expect(scope.element.innerHTML).toEqual('beforereplacedafter'); + expect(scope.element.innerHTML).toEqual('beforereplacedafter'); expect(log).toEqual("hello middle"); }); - xit('should replace widgets', function(){ - widgets.button = function(element) { - element.parentNode.replaceChild(button, element); + it('should replace widgets', function(){ + widgets['NG:BUTTON'] = function(element) { + element.replaceWith('
      button
      ', element); return function(element) { log += 'init'; }; }; var scope = compile('push me'); - expect(scope.element.innerHTML).toEqual('beforereplacedafter'); + expect(scope.element.innerHTML).toEqual('
      button
      '); + expect(log).toEqual('init'); }); }); diff --git a/test/ParserTest.js b/test/ParserTest.js index 09c3b8de..53ca9eda 100644 --- a/test/ParserTest.js +++ b/test/ParserTest.js @@ -41,7 +41,7 @@ LexerTest.prototype.testTokenizeAString = function(){ i++; assertEquals(tokens[i].index, 15); - assertEquals(tokens[i].text, "a'c"); + assertEquals(tokens[i].string, "a'c"); i++; assertEquals(tokens[i].index, 21); @@ -49,7 +49,7 @@ LexerTest.prototype.testTokenizeAString = function(){ i++; assertEquals(tokens[i].index, 22); - assertEquals(tokens[i].text, 'd"e'); + assertEquals(tokens[i].string, 'd"e'); }; @@ -68,10 +68,10 @@ LexerTest.prototype.testQuotedString = function(){ var tokens = lexer.parse(); assertEquals(1, tokens[1].index); - assertEquals("'", tokens[1].text); + assertEquals("'", tokens[1].string); assertEquals(7, tokens[3].index); - assertEquals('"', tokens[3].text); + assertEquals('"', tokens[3].string); }; @@ -80,14 +80,14 @@ LexerTest.prototype.testQuotedStringEscape = function(){ var lexer = new Lexer(str); var tokens = lexer.parse(); - assertEquals('"\n\f\r\t\v\u00A0', tokens[0].text); + assertEquals('"\n\f\r\t\v\u00A0', tokens[0].string); }; LexerTest.prototype.testTokenizeUnicode = function(){ var lexer = new Lexer('"\\u00A0"'); var tokens = lexer.parse(); assertEquals(1, tokens.length); - assertEquals('\u00a0', tokens[0].text); + assertEquals('\u00a0', tokens[0].string); }; LexerTest.prototype.testTokenizeRegExpWithOptions = function(){ @@ -408,7 +408,7 @@ ParserTest.prototype.testItShouldParseOnChangeIntoHashSet = function () { ParserTest.prototype.testItShouldParseOnChangeBlockIntoHashSet = function () { var scope = new Scope({count:0}); var listeners = {a:[], b:[]}; - scope.watch("a:{count=count+1;count=count+20;};b:count=count+300", + scope.watch("a:{count=count+1;count=count+20;};b:count=count+300", function(n, fn){listeners[n].push(fn);}); assertEquals(1, scope.watchListeners.a.listeners.length); @@ -477,3 +477,8 @@ ParserTest.prototype.testNegationBug = function () { assertEquals(12/6/2, scope.eval("12/6/2")); }; +ParserTest.prototype.testBugStringConfusesParser = function() { + var scope = new Scope(); + assertEquals('!', scope.eval('suffix = "!"')); +}; + diff --git a/test/directivesSpec.js b/test/directivesSpec.js new file mode 100644 index 00000000..176f9e70 --- /dev/null +++ b/test/directivesSpec.js @@ -0,0 +1,71 @@ +describe("directives", function(){ + + var compile, element; + + beforeEach(function() { + var compiler = new Compiler(angularMarkup, angularDirective, angularWidget); + compile = function(html) { + element = jqLite(html); + var view = compiler.compile(element.element)(element.element); + view.init(); + return view.scope; + }; + }); + + it("should ng-init", function() { + var scope = compile('
      '); + expect(scope.get('a')).toEqual(123); + }); + + it("should ng-eval", function() { + var scope = compile('
      '); + expect(scope.get('a')).toEqual(0); + scope.updateView(); + expect(scope.get('a')).toEqual(1); + scope.updateView(); + expect(scope.get('a')).toEqual(2); + }); + + it('should ng-bind', function() { + var scope = compile('
      '); + expect(element.text()).toEqual(''); + scope.set('a', 'misko'); + scope.updateView(); + expect(element.text()).toEqual('misko'); + }); + + it('should ng-bind-attr', function(){ + var scope = compile(''); + expect(element.attr('src')).toEqual(null); + expect(element.attr('alt')).toEqual(null); + scope.updateView(); + expect(element.attr('src')).toEqual('mysrc'); + expect(element.attr('alt')).toEqual('myalt'); + }); + + it('should ng-non-bindable', function(){ + var scope = compile('
      '); + scope.set('name', 'misko'); + scope.updateView(); + expect(element.text()).toEqual(''); + }); + + it('should ng-repeat over array', function(){ + var scope = compile('
      '); + + scope.set('items', ['misko', 'shyam']); + scope.updateView(); + expect(element.text()).toEqual('misko;shyam;'); + + scope.set('items', ['adam', 'kai', 'brad']); + scope.updateView(); + expect(element.text()).toEqual('adam;kai;brad;'); + + scope.set('items', ['brad']); + scope.updateView(); + expect(element.text()).toEqual('brad;'); + }); + + it('should ng-repeat over object', function(){}); + it('should error on wrong parsing of ng-repeat', function(){}); +}); -- cgit v1.2.3 From b4561ff951ff452e55e820f6f8344dc2668cfd90 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Mon, 22 Mar 2010 15:46:34 -0700 Subject: ng-repeat works --- src/Angular.js | 25 +++++++++++++++++++++---- src/Compiler.js | 16 +++++++++++++++- src/directives.js | 36 +++++++++++++++++++++++++----------- test/directivesSpec.js | 18 ++++++++++++++++-- 4 files changed, 77 insertions(+), 18 deletions(-) diff --git a/src/Angular.js b/src/Angular.js index cfffab04..1549e7a7 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -181,15 +181,32 @@ function escapeAttr(html) { function bind(_this, _function) { var curryArgs = slice.call(arguments, 2, arguments.length); - if (!_this) - throw "Missing this"; - if (!_.isFunction(_function)) - throw "Missing function"; return function() { return _function.apply(_this, curryArgs.concat(slice.call(arguments, 0, arguments.length))); }; } +function bindTry(_this, _function) { + var args = arguments, + last = args.length - 1, + curryArgs = slice.call(args, 2, last), + exceptionHandler = args[last]; + return function() { + try { + return _function.apply(_this, curryArgs.concat(slice.call(arguments, 0, arguments.length))); + } catch (e) { + if (e = exceptionHandler(e)) throw e; + } + }; +} + +function errorHandlerFor(element) { + return function(error){ + element.attr('ng-error', angular.toJson(error)); + element.addClass('ng-exception'); + }; +} + function outerHTML(node) { var temp = document.createElement('div'); temp.appendChild(node); diff --git a/src/Compiler.js b/src/Compiler.js index 5c650204..ece44805 100644 --- a/src/Compiler.js +++ b/src/Compiler.js @@ -107,6 +107,20 @@ JQLite.prototype = { this.element.parentNode.insertBefore(jqLite(element).element, this.element.nextSibling); }, + hasClass: function(selector) { + var className = " " + selector + " "; + if ( (" " + this.element.className + " ").replace(/[\n\t]/g, " ").indexOf( className ) > -1 ) { + return true; + } + return false; + }, + + addClass: function( selector ) { + if (!this.hasClass(selector)) { + this.element.className += ' ' + selector; + } + }, + attr: function(name, value){ var e = this.element; if (isObject(name)) { @@ -201,7 +215,7 @@ Compiler.prototype = { exclusive = true; directiveQueue = []; } - directiveQueue.push(bind(selfApi, directive, value, element)); + directiveQueue.push(bindTry(selfApi, directive, value, element, errorHandlerFor(element))); } }); diff --git a/src/directives.js b/src/directives.js index 66a5e864..8047cdbd 100644 --- a/src/directives.js +++ b/src/directives.js @@ -10,7 +10,7 @@ angularDirective("ng-eval", function(expression){ }; }); -angular.directive("ng-bind", function(expression){ +angularDirective("ng-bind", function(expression){ return function(element) { this.$watch(expression, function(value){ element.text(value); @@ -18,23 +18,36 @@ angular.directive("ng-bind", function(expression){ }; }); -angular.directive("ng-bind-attr", function(expression){ +angularDirective("ng-bind-attr", function(expression){ return function(element){ this.$watch(expression, bind(element, element.attr)); }; }); -angular.directive("ng-non-bindable", function(){ +angularDirective("ng-non-bindable", function(){ this.descend(false); }); -angular.directive("ng-repeat", function(expression, element){ +angularDirective("ng-repeat", function(expression, element){ var reference = this.reference("ng-repeat: " + expression), r = element.removeAttr('ng-repeat'), template = this.compile(element), - path = expression.split(' in '), - lhs = path[0], - rhs = path[1]; + match = expression.match(/^\s*(.+)\s+in\s+(.*)\s*$/), + lhs, rhs, valueIdent, keyIdent; + if (! match) { + throw "Expected ng-repeat in form of 'item in collection' but got '" + + expression + "'."; + } + lhs = match[1]; + rhs = match[2]; + match = lhs.match(/^([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\)$/); + if (!match) { + throw "'item' in 'item in collection' should be identifier or (key, value) but got '" + + keyValue + "'."; + } + valueIdent = match[3] || match[1]; + keyIdent = match[2]; + var parent = element.parent(); element.replaceWith(reference); return function(){ @@ -42,7 +55,7 @@ angular.directive("ng-repeat", function(expression, element){ currentScope = this; this.$addEval(rhs, function(items){ var index = 0, childCount = children.length, childScope, lastElement = reference; - foreach(items, function(value, key){ + foreach(items || [], function(value, key){ if (index < childCount) { // reuse existing child childScope = children[index]; @@ -55,7 +68,8 @@ angular.directive("ng-repeat", function(expression, element){ lastElement.after(childScope.element); children.push(childScope); } - childScope.scope.set(lhs, value); + childScope.scope.set(valueIdent, value); + if (keyIdent) childScope.scope.set(keyIdent, key); childScope.scope.updateView(); lastElement = childScope.element; index ++; @@ -86,7 +100,7 @@ angular.directive("ng-repeat", function(expression, element){ //ng-show, ng-hide -angular.directive("action", function(expression, element){ +angularDirective("action", function(expression, element){ return function(){ var self = this; jQuery(element).click(function(){ @@ -97,7 +111,7 @@ angular.directive("action", function(expression, element){ //ng-watch //
      -angular.directive("watch", function(expression, element){ +angularDirective("watch", function(expression, element){ var watches = { 'lhs':'rhs' }; // parse diff --git a/test/directivesSpec.js b/test/directivesSpec.js index 176f9e70..2cee20d1 100644 --- a/test/directivesSpec.js +++ b/test/directivesSpec.js @@ -66,6 +66,20 @@ describe("directives", function(){ expect(element.text()).toEqual('brad;'); }); - it('should ng-repeat over object', function(){}); - it('should error on wrong parsing of ng-repeat', function(){}); + it('should ng-repeat over object', function(){ + var scope = compile('
      '); + scope.set('items', {misko:'swe', shyam:'set'}); + scope.updateView(); + expect(element.text()).toEqual('misko:swe;shyam:set;'); + }); + + it('should error on wrong parsing of ng-repeat', function(){ + var scope = compile('
      '); + var log = ""; + element.eachNode(function(li){ + log += li.attr('ng-error') + ';'; + log += li.hasClass('ng-exception') + ';'; + }); + expect(log).toEqual("\"Expected ng-repeat in form of 'item in collection' but got 'i dont parse'.\";true;"); + }); }); -- cgit v1.2.3 From 6f8276a8e3735396999bd158005ca86bb1bb0978 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Mon, 22 Mar 2010 16:07:42 -0700 Subject: ng-watch directive --- src/Scope.js | 4 ++++ src/directives.js | 22 +++++++++++----------- test/directivesSpec.js | 12 ++++++++++++ 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/Scope.js b/src/Scope.js index d22604fd..7b1d2673 100644 --- a/src/Scope.js +++ b/src/Scope.js @@ -207,6 +207,10 @@ Scope.prototype = { }, addWatchListener: function(watchExpression, listener) { + // TODO: clean me up! + if (!isFunction(listener)) { + listener = bind(this, this.compile(listener), {scope: this, self: this.state}); + } var watcher = this.watchListeners[watchExpression]; if (!watcher) { watcher = {listeners:[], expression:watchExpression}; diff --git a/src/directives.js b/src/directives.js index 861805fe..e081d179 100644 --- a/src/directives.js +++ b/src/directives.js @@ -82,6 +82,17 @@ angularDirective("ng-repeat", function(expression, element){ }; }, {exclusive: true}); +angularDirective("ng-watch", function(expression, element){ + var match = expression.match(/^([^.]*):(.*)$/); + if (!match) { + throw "Expecting watch expression 'ident_to_watch: watch_statement' got '" + + expression + "'"; + } + return function(){ + this.$watch(match[1], match[2]); + }; +}); + ///////////////////////////////////////// ///////////////////////////////////////// @@ -109,17 +120,6 @@ angularDirective("action", function(expression, element){ }; }); -//ng-watch -//
      -angularDirective("watch", function(expression, element){ - var watches = { - 'lhs':'rhs' - }; // parse - return function(){ - this.$watch(watches); - }; -}); - //widget related //ng-validate, ng-required, ng-formatter //ng-error diff --git a/test/directivesSpec.js b/test/directivesSpec.js index 2cee20d1..e0e53eeb 100644 --- a/test/directivesSpec.js +++ b/test/directivesSpec.js @@ -82,4 +82,16 @@ describe("directives", function(){ }); expect(log).toEqual("\"Expected ng-repeat in form of 'item in collection' but got 'i dont parse'.\";true;"); }); + + it('should ng-watch', function(){ + var scope = compile('
      '); + scope.updateView(); + scope.updateView(); + expect(scope.get('count')).toEqual(0); + + scope.set('i', 0); + scope.updateView(); + scope.updateView(); + expect(scope.get('count')).toEqual(1); + }); }); -- cgit v1.2.3 From 7c87c17d08dbba318af1a149c0bbedb696b03458 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Mon, 22 Mar 2010 18:20:49 -0700 Subject: upgraded jquery to 1.4.2 and made ng-action work with jquery --- jsTestDriver.conf | 2 +- lib/jquery/jquery-1.3.2.js | 4376 ----------------------------- lib/jquery/jquery-1.4.2.min.js | 154 ++ lib/jquery/jquery-1.4.js | 5999 ---------------------------------------- lib/jquery/jquery-1.4.min.js | 151 - src/directives.js | 31 +- test/BinderTest.js | 1 - test/directivesSpec.js | 9 + 8 files changed, 179 insertions(+), 10544 deletions(-) delete mode 100644 lib/jquery/jquery-1.3.2.js create mode 100644 lib/jquery/jquery-1.4.2.min.js delete mode 100644 lib/jquery/jquery-1.4.js delete mode 100644 lib/jquery/jquery-1.4.min.js diff --git a/jsTestDriver.conf b/jsTestDriver.conf index da821ad1..245140d7 100644 --- a/jsTestDriver.conf +++ b/jsTestDriver.conf @@ -4,7 +4,7 @@ load: - lib/jasmine/jasmine-0.10.1.js - lib/jasmine-jstd-adapter/JasmineAdapter.js - lib/webtoolkit/webtoolkit.base64.js - - lib/jquery/jquery-1.4.js + - lib/jquery/jquery-1.4.2.min.js - lib/jquery/jquery-ui-1.7.1.custom.min.js - lib/underscore/underscore.js - src/Angular.js diff --git a/lib/jquery/jquery-1.3.2.js b/lib/jquery/jquery-1.3.2.js deleted file mode 100644 index d7093948..00000000 --- a/lib/jquery/jquery-1.3.2.js +++ /dev/null @@ -1,4376 +0,0 @@ -/*! - * jQuery JavaScript Library v1.3.2 - * http://jquery.com/ - * - * Copyright (c) 2009 John Resig - * Dual licensed under the MIT and GPL licenses. - * http://docs.jquery.com/License - * - * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009) - * Revision: 6246 - */ -(function(){ - -var - // Will speed up references to window, and allows munging its name. - window = this, - // Will speed up references to undefined, and allows munging its name. - undefined, - // Map over jQuery in case of overwrite - _jQuery = window.jQuery, - // Map over the $ in case of overwrite - _$ = window.$, - - jQuery = window.jQuery = window.$ = function( selector, context ) { - // The jQuery object is actually just the init constructor 'enhanced' - return new jQuery.fn.init( selector, context ); - }, - - // A simple way to check for HTML strings or ID strings - // (both of which we optimize for) - quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/, - // Is it a simple selector - isSimple = /^.[^:#\[\.,]*$/; - -jQuery.fn = jQuery.prototype = { - init: function( selector, context ) { - // Make sure that a selection was provided - selector = selector || document; - - // Handle $(DOMElement) - if ( selector.nodeType ) { - this[0] = selector; - this.length = 1; - this.context = selector; - return this; - } - // Handle HTML strings - if ( typeof selector === "string" ) { - // Are we dealing with HTML string or an ID? - var match = quickExpr.exec( selector ); - - // Verify a match, and that no context was specified for #id - if ( match && (match[1] || !context) ) { - - // HANDLE: $(html) -> $(array) - if ( match[1] ) - selector = jQuery.clean( [ match[1] ], context ); - - // HANDLE: $("#id") - else { - var elem = document.getElementById( match[3] ); - - // Handle the case where IE and Opera return items - // by name instead of ID - if ( elem && elem.id != match[3] ) - return jQuery().find( selector ); - - // Otherwise, we inject the element directly into the jQuery object - var ret = jQuery( elem || [] ); - ret.context = document; - ret.selector = selector; - return ret; - } - - // HANDLE: $(expr, [context]) - // (which is just equivalent to: $(content).find(expr) - } else - return jQuery( context ).find( selector ); - - // HANDLE: $(function) - // Shortcut for document ready - } else if ( jQuery.isFunction( selector ) ) - return jQuery( document ).ready( selector ); - - // Make sure that old selector state is passed along - if ( selector.selector && selector.context ) { - this.selector = selector.selector; - this.context = selector.context; - } - - return this.setArray(jQuery.isArray( selector ) ? - selector : - jQuery.makeArray(selector)); - }, - - // Start with an empty selector - selector: "", - - // The current version of jQuery being used - jquery: "1.3.2", - - // The number of elements contained in the matched element set - size: function() { - return this.length; - }, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - return num === undefined ? - - // Return a 'clean' array - Array.prototype.slice.call( this ) : - - // Return just the object - this[ num ]; - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems, name, selector ) { - // Build a new jQuery matched element set - var ret = jQuery( elems ); - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - - ret.context = this.context; - - if ( name === "find" ) - ret.selector = this.selector + (this.selector ? " " : "") + selector; - else if ( name ) - ret.selector = this.selector + "." + name + "(" + selector + ")"; - - // Return the newly-formed element set - return ret; - }, - - // Force the current matched set of elements to become - // the specified array of elements (destroying the stack in the process) - // You should use pushStack() in order to do this, but maintain the stack - setArray: function( elems ) { - // Resetting the length to 0, then using the native Array push - // is a super-fast way to populate an object with array-like properties - this.length = 0; - Array.prototype.push.apply( this, elems ); - - return this; - }, - - // Execute a callback for every element in the matched set. - // (You can seed the arguments with an array of args, but this is - // only used internally.) - each: function( callback, args ) { - return jQuery.each( this, callback, args ); - }, - - // Determine the position of an element within - // the matched set of elements - index: function( elem ) { - // Locate the position of the desired element - return jQuery.inArray( - // If it receives a jQuery object, the first element is used - elem && elem.jquery ? elem[0] : elem - , this ); - }, - - attr: function( name, value, type ) { - var options = name; - - // Look for the case where we're accessing a style value - if ( typeof name === "string" ) - if ( value === undefined ) - return this[0] && jQuery[ type || "attr" ]( this[0], name ); - - else { - options = {}; - options[ name ] = value; - } - - // Check to see if we're setting style values - return this.each(function(i){ - // Set all the styles - for ( name in options ) - jQuery.attr( - type ? - this.style : - this, - name, jQuery.prop( this, options[ name ], type, i, name ) - ); - }); - }, - - css: function( key, value ) { - // ignore negative width and height values - if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 ) - value = undefined; - return this.attr( key, value, "curCSS" ); - }, - - text: function( text ) { - if ( typeof text !== "object" && text != null ) - return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) ); - - var ret = ""; - - jQuery.each( text || this, function(){ - jQuery.each( this.childNodes, function(){ - if ( this.nodeType != 8 ) - ret += this.nodeType != 1 ? - this.nodeValue : - jQuery.fn.text( [ this ] ); - }); - }); - - return ret; - }, - - wrapAll: function( html ) { - if ( this[0] ) { - // The elements to wrap the target around - var wrap = jQuery( html, this[0].ownerDocument ).clone(); - - if ( this[0].parentNode ) - wrap.insertBefore( this[0] ); - - wrap.map(function(){ - var elem = this; - - while ( elem.firstChild ) - elem = elem.firstChild; - - return elem; - }).append(this); - } - - return this; - }, - - wrapInner: function( html ) { - return this.each(function(){ - jQuery( this ).contents().wrapAll( html ); - }); - }, - - wrap: function( html ) { - return this.each(function(){ - jQuery( this ).wrapAll( html ); - }); - }, - - append: function() { - return this.domManip(arguments, true, function(elem){ - if (this.nodeType == 1) - this.appendChild( elem ); - }); - }, - - prepend: function() { - return this.domManip(arguments, true, function(elem){ - if (this.nodeType == 1) - this.insertBefore( elem, this.firstChild ); - }); - }, - - before: function() { - return this.domManip(arguments, false, function(elem){ - this.parentNode.insertBefore( elem, this ); - }); - }, - - after: function() { - return this.domManip(arguments, false, function(elem){ - this.parentNode.insertBefore( elem, this.nextSibling ); - }); - }, - - end: function() { - return this.prevObject || jQuery( [] ); - }, - - // For internal use only. - // Behaves like an Array's method, not like a jQuery method. - push: [].push, - sort: [].sort, - splice: [].splice, - - find: function( selector ) { - if ( this.length === 1 ) { - var ret = this.pushStack( [], "find", selector ); - ret.length = 0; - jQuery.find( selector, this[0], ret ); - return ret; - } else { - return this.pushStack( jQuery.unique(jQuery.map(this, function(elem){ - return jQuery.find( selector, elem ); - })), "find", selector ); - } - }, - - clone: function( events ) { - // Do the clone - var ret = this.map(function(){ - if ( !jQuery.support.noCloneEvent && !jQuery.isXMLDoc(this) ) { - // IE copies events bound via attachEvent when - // using cloneNode. Calling detachEvent on the - // clone will also remove the events from the orignal - // In order to get around this, we use innerHTML. - // Unfortunately, this means some modifications to - // attributes in IE that are actually only stored - // as properties will not be copied (such as the - // the name attribute on an input). - var html = this.outerHTML; - if ( !html ) { - var div = this.ownerDocument.createElement("div"); - div.appendChild( this.cloneNode(true) ); - html = div.innerHTML; - } - - return jQuery.clean([html.replace(/ jQuery\d+="(?:\d+|null)"/g, "").replace(/^\s*/, "")])[0]; - } else - return this.cloneNode(true); - }); - - // Copy the events from the original to the clone - if ( events === true ) { - var orig = this.find("*").andSelf(), i = 0; - - ret.find("*").andSelf().each(function(){ - if ( this.nodeName !== orig[i].nodeName ) - return; - - var events = jQuery.data( orig[i], "events" ); - - for ( var type in events ) { - for ( var handler in events[ type ] ) { - jQuery.event.add( this, type, events[ type ][ handler ], events[ type ][ handler ].data ); - } - } - - i++; - }); - } - - // Return the cloned set - return ret; - }, - - filter: function( selector ) { - return this.pushStack( - jQuery.isFunction( selector ) && - jQuery.grep(this, function(elem, i){ - return selector.call( elem, i ); - }) || - - jQuery.multiFilter( selector, jQuery.grep(this, function(elem){ - return elem.nodeType === 1; - }) ), "filter", selector ); - }, - - closest: function( selector ) { - var pos = jQuery.expr.match.POS.test( selector ) ? jQuery(selector) : null, - closer = 0; - - return this.map(function(){ - var cur = this; - while ( cur && cur.ownerDocument ) { - if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selector) ) { - jQuery.data(cur, "closest", closer); - return cur; - } - cur = cur.parentNode; - closer++; - } - }); - }, - - not: function( selector ) { - if ( typeof selector === "string" ) - // test special case where just one selector is passed in - if ( isSimple.test( selector ) ) - return this.pushStack( jQuery.multiFilter( selector, this, true ), "not", selector ); - else - selector = jQuery.multiFilter( selector, this ); - - var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType; - return this.filter(function() { - return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector; - }); - }, - - add: function( selector ) { - return this.pushStack( jQuery.unique( jQuery.merge( - this.get(), - typeof selector === "string" ? - jQuery( selector ) : - jQuery.makeArray( selector ) - ))); - }, - - is: function( selector ) { - return !!selector && jQuery.multiFilter( selector, this ).length > 0; - }, - - hasClass: function( selector ) { - return !!selector && this.is( "." + selector ); - }, - - val: function( value ) { - if ( value === undefined ) { - var elem = this[0]; - - if ( elem ) { - if( jQuery.nodeName( elem, 'option' ) ) - return (elem.attributes.value || {}).specified ? elem.value : elem.text; - - // We need to handle select boxes special - if ( jQuery.nodeName( elem, "select" ) ) { - var index = elem.selectedIndex, - values = [], - options = elem.options, - one = elem.type == "select-one"; - - // Nothing was selected - if ( index < 0 ) - return null; - - // Loop through all the selected options - for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { - var option = options[ i ]; - - if ( option.selected ) { - // Get the specifc value for the option - value = jQuery(option).val(); - - // We don't need an array for one selects - if ( one ) - return value; - - // Multi-Selects return an array - values.push( value ); - } - } - - return values; - } - - // Everything else, we just grab the value - return (elem.value || "").replace(/\r/g, ""); - - } - - return undefined; - } - - if ( typeof value === "number" ) - value += ''; - - return this.each(function(){ - if ( this.nodeType != 1 ) - return; - - if ( jQuery.isArray(value) && /radio|checkbox/.test( this.type ) ) - this.checked = (jQuery.inArray(this.value, value) >= 0 || - jQuery.inArray(this.name, value) >= 0); - - else if ( jQuery.nodeName( this, "select" ) ) { - var values = jQuery.makeArray(value); - - jQuery( "option", this ).each(function(){ - this.selected = (jQuery.inArray( this.value, values ) >= 0 || - jQuery.inArray( this.text, values ) >= 0); - }); - - if ( !values.length ) - this.selectedIndex = -1; - - } else - this.value = value; - }); - }, - - html: function( value ) { - return value === undefined ? - (this[0] ? - this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g, "") : - null) : - this.empty().append( value ); - }, - - replaceWith: function( value ) { - return this.after( value ).remove(); - }, - - eq: function( i ) { - return this.slice( i, +i + 1 ); - }, - - slice: function() { - return this.pushStack( Array.prototype.slice.apply( this, arguments ), - "slice", Array.prototype.slice.call(arguments).join(",") ); - }, - - map: function( callback ) { - return this.pushStack( jQuery.map(this, function(elem, i){ - return callback.call( elem, i, elem ); - })); - }, - - andSelf: function() { - return this.add( this.prevObject ); - }, - - domManip: function( args, table, callback ) { - if ( this[0] ) { - var fragment = (this[0].ownerDocument || this[0]).createDocumentFragment(), - scripts = jQuery.clean( args, (this[0].ownerDocument || this[0]), fragment ), - first = fragment.firstChild; - - if ( first ) - for ( var i = 0, l = this.length; i < l; i++ ) - callback.call( root(this[i], first), this.length > 1 || i > 0 ? - fragment.cloneNode(true) : fragment ); - - if ( scripts ) - jQuery.each( scripts, evalScript ); - } - - return this; - - function root( elem, cur ) { - return table && jQuery.nodeName(elem, "table") && jQuery.nodeName(cur, "tr") ? - (elem.getElementsByTagName("tbody")[0] || - elem.appendChild(elem.ownerDocument.createElement("tbody"))) : - elem; - } - } -}; - -// Give the init function the jQuery prototype for later instantiation -jQuery.fn.init.prototype = jQuery.fn; - -function evalScript( i, elem ) { - if ( elem.src ) - jQuery.ajax({ - url: elem.src, - async: false, - dataType: "script" - }); - - else - jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); - - if ( elem.parentNode ) - elem.parentNode.removeChild( elem ); -} - -function now(){ - return +new Date; -} - -jQuery.extend = jQuery.fn.extend = function() { - // copy reference to target object - var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options; - - // Handle a deep copy situation - if ( typeof target === "boolean" ) { - deep = target; - target = arguments[1] || {}; - // skip the boolean and the target - i = 2; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !jQuery.isFunction(target) ) - target = {}; - - // extend jQuery itself if only one argument is passed - if ( length == i ) { - target = this; - --i; - } - - for ( ; i < length; i++ ) - // Only deal with non-null/undefined values - if ( (options = arguments[ i ]) != null ) - // Extend the base object - for ( var name in options ) { - var src = target[ name ], copy = options[ name ]; - - // Prevent never-ending loop - if ( target === copy ) - continue; - - // Recurse if we're merging object values - if ( deep && copy && typeof copy === "object" && !copy.nodeType ) - target[ name ] = jQuery.extend( deep, - // Never move original objects, clone them - src || ( copy.length != null ? [ ] : { } ) - , copy ); - - // Don't bring in undefined values - else if ( copy !== undefined ) - target[ name ] = copy; - - } - - // Return the modified object - return target; -}; - -// exclude the following css properties to add px -var exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i, - // cache defaultView - defaultView = document.defaultView || {}, - toString = Object.prototype.toString; - -jQuery.extend({ - noConflict: function( deep ) { - window.$ = _$; - - if ( deep ) - window.jQuery = _jQuery; - - return jQuery; - }, - - // See test/unit/core.js for details concerning isFunction. - // Since version 1.3, DOM methods and functions like alert - // aren't supported. They return false on IE (#2968). - isFunction: function( obj ) { - return toString.call(obj) === "[object Function]"; - }, - - isArray: function( obj ) { - return toString.call(obj) === "[object Array]"; - }, - - // check if an element is in a (or is an) XML document - isXMLDoc: function( elem ) { - return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || - !!elem.ownerDocument && jQuery.isXMLDoc( elem.ownerDocument ); - }, - - // Evalulates a script in a global context - globalEval: function( data ) { - if ( data && /\S/.test(data) ) { - // Inspired by code by Andrea Giammarchi - // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html - var head = document.getElementsByTagName("head")[0] || document.documentElement, - script = document.createElement("script"); - - script.type = "text/javascript"; - if ( jQuery.support.scriptEval ) - script.appendChild( document.createTextNode( data ) ); - else - script.text = data; - - // Use insertBefore instead of appendChild to circumvent an IE6 bug. - // This arises when a base node is used (#2709). - head.insertBefore( script, head.firstChild ); - head.removeChild( script ); - } - }, - - nodeName: function( elem, name ) { - return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase(); - }, - - // args is for internal usage only - each: function( object, callback, args ) { - var name, i = 0, length = object.length; - - if ( args ) { - if ( length === undefined ) { - for ( name in object ) - if ( callback.apply( object[ name ], args ) === false ) - break; - } else - for ( ; i < length; ) - if ( callback.apply( object[ i++ ], args ) === false ) - break; - - // A special, fast, case for the most common use of each - } else { - if ( length === undefined ) { - for ( name in object ) - if ( callback.call( object[ name ], name, object[ name ] ) === false ) - break; - } else - for ( var value = object[0]; - i < length && callback.call( value, i, value ) !== false; value = object[++i] ){} - } - - return object; - }, - - prop: function( elem, value, type, i, name ) { - // Handle executable functions - if ( jQuery.isFunction( value ) ) - value = value.call( elem, i ); - - // Handle passing in a number to a CSS property - return typeof value === "number" && type == "curCSS" && !exclude.test( name ) ? - value + "px" : - value; - }, - - className: { - // internal only, use addClass("class") - add: function( elem, classNames ) { - jQuery.each((classNames || "").split(/\s+/), function(i, className){ - if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) ) - elem.className += (elem.className ? " " : "") + className; - }); - }, - - // internal only, use removeClass("class") - remove: function( elem, classNames ) { - if (elem.nodeType == 1) - elem.className = classNames !== undefined ? - jQuery.grep(elem.className.split(/\s+/), function(className){ - return !jQuery.className.has( classNames, className ); - }).join(" ") : - ""; - }, - - // internal only, use hasClass("class") - has: function( elem, className ) { - return elem && jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1; - } - }, - - // A method for quickly swapping in/out CSS properties to get correct calculations - swap: function( elem, options, callback ) { - var old = {}; - // Remember the old values, and insert the new ones - for ( var name in options ) { - old[ name ] = elem.style[ name ]; - elem.style[ name ] = options[ name ]; - } - - callback.call( elem ); - - // Revert the old values - for ( var name in options ) - elem.style[ name ] = old[ name ]; - }, - - css: function( elem, name, force, extra ) { - if ( name == "width" || name == "height" ) { - var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ]; - - function getWH() { - val = name == "width" ? elem.offsetWidth : elem.offsetHeight; - - if ( extra === "border" ) - return; - - jQuery.each( which, function() { - if ( !extra ) - val -= parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0; - if ( extra === "margin" ) - val += parseFloat(jQuery.curCSS( elem, "margin" + this, true)) || 0; - else - val -= parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0; - }); - } - - if ( elem.offsetWidth !== 0 ) - getWH(); - else - jQuery.swap( elem, props, getWH ); - - return Math.max(0, Math.round(val)); - } - - return jQuery.curCSS( elem, name, force ); - }, - - curCSS: function( elem, name, force ) { - var ret, style = elem.style; - - // We need to handle opacity special in IE - if ( name == "opacity" && !jQuery.support.opacity ) { - ret = jQuery.attr( style, "opacity" ); - - return ret == "" ? - "1" : - ret; - } - - // Make sure we're using the right name for getting the float value - if ( name.match( /float/i ) ) - name = styleFloat; - - if ( !force && style && style[ name ] ) - ret = style[ name ]; - - else if ( defaultView.getComputedStyle ) { - - // Only "float" is needed here - if ( name.match( /float/i ) ) - name = "float"; - - name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase(); - - var computedStyle = defaultView.getComputedStyle( elem, null ); - - if ( computedStyle ) - ret = computedStyle.getPropertyValue( name ); - - // We should always get a number back from opacity - if ( name == "opacity" && ret == "" ) - ret = "1"; - - } else if ( elem.currentStyle ) { - var camelCase = name.replace(/\-(\w)/g, function(all, letter){ - return letter.toUpperCase(); - }); - - ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ]; - - // From the awesome hack by Dean Edwards - // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 - - // If we're not dealing with a regular pixel number - // but a number that has a weird ending, we need to convert it to pixels - if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) { - // Remember the original values - var left = style.left, rsLeft = elem.runtimeStyle.left; - - // Put in the new values to get a computed value out - elem.runtimeStyle.left = elem.currentStyle.left; - style.left = ret || 0; - ret = style.pixelLeft + "px"; - - // Revert the changed values - style.left = left; - elem.runtimeStyle.left = rsLeft; - } - } - - return ret; - }, - - clean: function( elems, context, fragment ) { - context = context || document; - - // !context.createElement fails in IE with an error but returns typeof 'object' - if ( typeof context.createElement === "undefined" ) - context = context.ownerDocument || context[0] && context[0].ownerDocument || document; - - // If a single string is passed in and it's a single tag - // just do a createElement and skip the rest - if ( !fragment && elems.length === 1 && typeof elems[0] === "string" ) { - var match = /^<(\w+)\s*\/?>$/.exec(elems[0]); - if ( match ) - return [ context.createElement( match[1] ) ]; - } - - var ret = [], scripts = [], div = context.createElement("div"); - - jQuery.each(elems, function(i, elem){ - if ( typeof elem === "number" ) - elem += ''; - - if ( !elem ) - return; - - // Convert html string into DOM nodes - if ( typeof elem === "string" ) { - // Fix "XHTML"-style tags in all browsers - elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){ - return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ? - all : - front + ">"; - }); - - // Trim whitespace, otherwise indexOf won't work as expected - var tags = elem.replace(/^\s+/, "").substring(0, 10).toLowerCase(); - - var wrap = - // option or optgroup - !tags.indexOf("", "" ] || - - !tags.indexOf("", "" ] || - - tags.match(/^<(thead|tbody|tfoot|colg|cap)/) && - [ 1, "", "
      " ] || - - !tags.indexOf("", "" ] || - - // matched above - (!tags.indexOf("", "" ] || - - !tags.indexOf("", "" ] || - - // IE can't serialize and - + @@ -15,7 +15,7 @@

      @@ -62,8 +62,8 @@ tweets={{tweets}} ]
      Loading...
        -
      • [ {{user.nickname || user.name || user.screen_name }} diff --git a/example/tweeter/tweeter_demo.html b/example/tweeter/tweeter_demo.html new file mode 100644 index 00000000..e3c4f739 --- /dev/null +++ b/example/tweeter/tweeter_demo.html @@ -0,0 +1,34 @@ + + + + + + + + + + + + (TODO: I should fetch current tweets) +
        +

        Tweets: {{$anchor.user}}

        + [ Filter: (TODO: this should act as search box) + | << All + ] +
        Loading...
        + +
        +
        tweets=(TODO: display me!!!)
        + + diff --git a/example/tweeter/tweeterclient.js b/example/tweeter/tweeterclient.js index d6fdd084..84fc5ef7 100644 --- a/example/tweeter/tweeterclient.js +++ b/example/tweeter/tweeterclient.js @@ -4,7 +4,7 @@ $(document).ready(function(){ jQuery.getJSON(url, function(){ callback.apply(this, arguments); scope.updateView(); - }) + }); } var resourceFactory = new ResourceFactory(xhr); @@ -25,11 +25,11 @@ $(document).ready(function(){ scope.set('fetchTweets', fetchTweets); scope.set('users', [ - {screen_name:'mhevery', name:'Mi\u0161ko Hevery', - notes:'Author of http://www.getangular.com.', + {screen_name:'mhevery', name:'Mi\u0161ko Hevery', + notes:'Author of http://www.getangular.com.', profile_image_url:'http://a3.twimg.com/profile_images/54360179/Me_-_Small_Banner_normal.jpg'}, - {screen_name:'abrons', name:'Adam Abrons', - notes:'Author of & Ruby guru see: http://www.angularjs.org.', + {screen_name:'abrons', name:'Adam Abrons', + 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.init(); diff --git a/src/scenario/bootstrap.js b/src/scenario/bootstrap.js index 1d40b9d0..b49530df 100644 --- a/src/scenario/bootstrap.js +++ b/src/scenario/bootstrap.js @@ -8,7 +8,7 @@ var parts = src.match(filename); return parts[1]; } - } + } })(); function addScript(path) { document.write(''); @@ -17,7 +17,7 @@ document.write(''); }; window.onload = function(){ - if (!_.stepper) { + if (!_.stepper) { _.stepper = function(collection, iterator, done){ var keys = _.keys(collection); function next() { @@ -38,7 +38,7 @@ }; addCSS("../../css/angular-scenario.css"); addScript("../../lib/underscore/underscore.js"); - addScript("../../lib/jquery/jquery-1.3.2.js"); + addScript("../../lib/jquery/jquery-1.4.2.js"); addScript("../angular-bootstrap.js"); addScript("_namespace.js"); addScript("Steps.js"); -- cgit v1.2.3 From 03ddc4570b786a7b945e1b40a16f29d2349c68b8 Mon Sep 17 00:00:00 2001 From: Shyam Seshadri Date: Wed, 24 Mar 2010 10:35:01 -0700 Subject: Fix parsing bug with strings for - --- src/Parser.js | 108 ++++++++++++++++++++++++++------------------------- test/CompilerSpec.js | 2 +- test/ParserTest.js | 23 +++++++---- 3 files changed, 72 insertions(+), 61 deletions(-) diff --git a/src/Parser.js b/src/Parser.js index b59b21a7..941d37f7 100644 --- a/src/Parser.js +++ b/src/Parser.js @@ -40,7 +40,7 @@ Lexer.prototype = { return false; } }, - + parse: function() { var tokens = this.tokens; var OPERATORS = Lexer.OPERATORS; @@ -103,22 +103,22 @@ Lexer.prototype = { } return tokens; }, - + isNumber: function(ch) { return '0' <= ch && ch <= '9'; }, - + isWhitespace: function(ch) { return ch == ' ' || ch == '\r' || ch == '\t' || ch == '\n' || ch == '\v'; }, - + isIdent: function(ch) { return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || '_' == ch || ch == '$'; }, - + readNumber: function() { var number = ""; var start = this.index; @@ -135,7 +135,7 @@ Lexer.prototype = { this.tokens.push({index:start, text:number, fn:function(){return number;}}); }, - + readIdent: function() { var ident = ""; var start = this.index; @@ -157,15 +157,17 @@ Lexer.prototype = { } this.tokens.push({index:start, text:ident, fn:fn}); }, - + readString: function(quote) { var start = this.index; var dateParseLength = this.dateParseLength; this.index++; var string = ""; + var rawString = quote; var escape = false; while (this.index < this.text.length) { var ch = this.text.charAt(this.index); + rawString += ch; if (escape) { if (ch == 'u') { var hex = this.text.substring(this.index + 1, this.index + 5); @@ -184,7 +186,7 @@ Lexer.prototype = { escape = true; } else if (ch == quote) { this.index++; - this.tokens.push({index:start, text:string, + this.tokens.push({index:start, text:rawString, string:string, fn:function(){ return (string.length == dateParseLength) ? angular['String']['toDate'](string) : string; @@ -199,7 +201,7 @@ Lexer.prototype = { this.text.substring(start) + "] starting at column '" + (start+1) + "' in expression '" + this.text + "'."; }, - + readRegexp: function(quote) { var start = this.index; this.index++; @@ -249,18 +251,18 @@ Parser.ZERO = function(){ Parser.prototype = { error: function(msg, token) { - throw "Token '" + token.text + - "' is " + msg + " at column='" + - (token.index + 1) + "' of expression '" + + throw "Token '" + token.text + + "' is " + msg + " at column='" + + (token.index + 1) + "' of expression '" + this.text + "' starting at '" + this.text.substring(token.index) + "'."; }, - + peekToken: function() { - if (this.tokens.length === 0) + if (this.tokens.length === 0) throw "Unexpected end of expression: " + this.text; return this.tokens[0]; }, - + peek: function(e1, e2, e3, e4) { var tokens = this.tokens; if (tokens.length > 0) { @@ -273,7 +275,7 @@ Parser.prototype = { } return false; }, - + expect: function(e1, e2, e3, e4){ var token = this.peek(e1, e2, e3, e4); if (token) { @@ -283,7 +285,7 @@ Parser.prototype = { } return false; }, - + consume: function(e1){ if (!this.expect(e1)) { var token = this.peek(); @@ -293,30 +295,30 @@ Parser.prototype = { this.text.substring(token.index) + "'."; } }, - + _unary: function(fn, right) { return function(self) { return fn(self, right(self)); }; }, - + _binary: function(left, fn, right) { return function(self) { return fn(self, left(self), right(self)); }; }, - + hasTokens: function () { return this.tokens.length > 0; }, - + assertAllConsumed: function(){ if (this.tokens.length !== 0) { throw "Did not understand '" + this.text.substring(this.tokens[0].index) + "' while evaluating '" + this.text + "'."; } }, - + statements: function(){ var statements = []; while(true) { @@ -335,7 +337,7 @@ Parser.prototype = { } } }, - + filterChain: function(){ var left = this.expression(); var token; @@ -347,15 +349,15 @@ Parser.prototype = { } } }, - + filter: function(){ return this._pipeFunction(angularFilter); }, - + validator: function(){ return this._pipeFunction(angularValidator); }, - + _pipeFunction: function(fnScope){ var fn = this.functionIdent(fnScope); var argsFn = []; @@ -373,7 +375,7 @@ Parser.prototype = { var _this = this; foreach(self, function(v, k) { if (k.charAt(0) == '$') { - _this[k] = v; + _this[k] = v; } }); }; @@ -386,11 +388,11 @@ Parser.prototype = { } } }, - + expression: function(){ return this.throwStmt(); }, - + throwStmt: function(){ if (this.expect('throw')) { var throwExp = this.assignment(); @@ -401,7 +403,7 @@ Parser.prototype = { return this.assignment(); } }, - + assignment: function(){ var left = this.logicalOR(); var token; @@ -417,7 +419,7 @@ Parser.prototype = { return left; } }, - + logicalOR: function(){ var left = this.logicalAND(); var token; @@ -429,7 +431,7 @@ Parser.prototype = { } } }, - + logicalAND: function(){ var left = this.equality(); var token; @@ -438,7 +440,7 @@ Parser.prototype = { } return left; }, - + equality: function(){ var left = this.relational(); var token; @@ -447,7 +449,7 @@ Parser.prototype = { } return left; }, - + relational: function(){ var left = this.additive(); var token; @@ -456,7 +458,7 @@ Parser.prototype = { } return left; }, - + additive: function(){ var left = this.multiplicative(); var token; @@ -465,7 +467,7 @@ Parser.prototype = { } return left; }, - + multiplicative: function(){ var left = this.unary(); var token; @@ -474,7 +476,7 @@ Parser.prototype = { } return left; }, - + unary: function(){ var token; if (this.expect('+')) { @@ -487,7 +489,7 @@ Parser.prototype = { return this.primary(); } }, - + functionIdent: function(fnScope) { var token = this.expect(); var element = token.text.split('.'); @@ -504,7 +506,7 @@ Parser.prototype = { } return instance; }, - + primary: function() { var primary; if (this.expect('(')) { @@ -540,7 +542,7 @@ Parser.prototype = { } return primary; }, - + closure: function(hasArgs) { var args = []; if (hasArgs) { @@ -566,7 +568,7 @@ Parser.prototype = { }; }; }, - + fieldAccess: function(object) { var field = this.expect().text; var fn = function (self){ @@ -575,7 +577,7 @@ Parser.prototype = { fn.isAssignable = field; return fn; }, - + objectIndex: function(obj) { var indexFn = this.expression(); this.consume(']'); @@ -592,7 +594,7 @@ Parser.prototype = { }; } }, - + functionCall: function(fn) { var argsFn = []; if (this.peekToken().text != ')') { @@ -614,7 +616,7 @@ Parser.prototype = { } }; }, - + // This is used with json array declaration arrayDeclaration: function () { var elementFns = []; @@ -632,12 +634,13 @@ Parser.prototype = { return array; }; }, - + object: function () { var keyValues = []; if (this.peekToken().text != '}') { do { - var key = this.expect().text; + var token = this.expect(), + key = token.string || token.text; this.consume(":"); var value = this.expression(); keyValues.push({key:key, value:value}); @@ -654,7 +657,7 @@ Parser.prototype = { return object; }; }, - + entityDeclaration: function () { var decl = []; while(this.hasTokens()) { @@ -671,7 +674,7 @@ Parser.prototype = { return code; }; }, - + entityDecl: function () { var entity = this.expect().text; var instance; @@ -690,16 +693,16 @@ Parser.prototype = { var document = Entity(); document['$$anchor'] = instance; self.scope.set(instance, document); - return "$anchor." + instance + ":{" + + return "$anchor." + instance + ":{" + instance + "=" + entity + ".load($anchor." + instance + ");" + - instance + ".$$anchor=" + angular['String']['quote'](instance) + ";" + + instance + ".$$anchor=" + angular['String']['quote'](instance) + ";" + "};"; } else { return ""; } }; }, - + watch: function () { var decl = []; while(this.hasTokens()) { @@ -716,7 +719,7 @@ Parser.prototype = { } }; }, - + watchDecl: function () { var anchorName = this.expect().text; this.consume(":"); @@ -734,4 +737,3 @@ Parser.prototype = { } }; - diff --git a/test/CompilerSpec.js b/test/CompilerSpec.js index 7bf48d18..8487b139 100644 --- a/test/CompilerSpec.js +++ b/test/CompilerSpec.js @@ -1,4 +1,4 @@ -describe('compiler', function(){ +xdescribe('compiler', function(){ function element(html) { return jQuery(html)[0]; } diff --git a/test/ParserTest.js b/test/ParserTest.js index 09c3b8de..c8d323f2 100644 --- a/test/ParserTest.js +++ b/test/ParserTest.js @@ -41,7 +41,7 @@ LexerTest.prototype.testTokenizeAString = function(){ i++; assertEquals(tokens[i].index, 15); - assertEquals(tokens[i].text, "a'c"); + assertEquals(tokens[i].string, "a'c"); i++; assertEquals(tokens[i].index, 21); @@ -49,7 +49,7 @@ LexerTest.prototype.testTokenizeAString = function(){ i++; assertEquals(tokens[i].index, 22); - assertEquals(tokens[i].text, 'd"e'); + assertEquals(tokens[i].string, 'd"e'); }; @@ -68,10 +68,10 @@ LexerTest.prototype.testQuotedString = function(){ var tokens = lexer.parse(); assertEquals(1, tokens[1].index); - assertEquals("'", tokens[1].text); + assertEquals("'", tokens[1].string); assertEquals(7, tokens[3].index); - assertEquals('"', tokens[3].text); + assertEquals('"', tokens[3].string); }; @@ -80,14 +80,14 @@ LexerTest.prototype.testQuotedStringEscape = function(){ var lexer = new Lexer(str); var tokens = lexer.parse(); - assertEquals('"\n\f\r\t\v\u00A0', tokens[0].text); + assertEquals('"\n\f\r\t\v\u00A0', tokens[0].string); }; LexerTest.prototype.testTokenizeUnicode = function(){ var lexer = new Lexer('"\\u00A0"'); var tokens = lexer.parse(); assertEquals(1, tokens.length); - assertEquals('\u00a0', tokens[0].text); + assertEquals('\u00a0', tokens[0].string); }; LexerTest.prototype.testTokenizeRegExpWithOptions = function(){ @@ -408,7 +408,7 @@ ParserTest.prototype.testItShouldParseOnChangeIntoHashSet = function () { ParserTest.prototype.testItShouldParseOnChangeBlockIntoHashSet = function () { var scope = new Scope({count:0}); var listeners = {a:[], b:[]}; - scope.watch("a:{count=count+1;count=count+20;};b:count=count+300", + scope.watch("a:{count=count+1;count=count+20;};b:count=count+300", function(n, fn){listeners[n].push(fn);}); assertEquals(1, scope.watchListeners.a.listeners.length); @@ -477,3 +477,12 @@ ParserTest.prototype.testNegationBug = function () { assertEquals(12/6/2, scope.eval("12/6/2")); }; +ParserTest.prototype.testBugStringConfusesParser = function() { + var scope = new Scope(); + assertEquals('!', scope.eval('suffix = "!"')); +}; + +ParserTest.prototype.testParsingBug = function () { + var scope = new Scope(); + assertEquals({a: "-"}, scope.eval("{a:'-'}")); +}; -- cgit v1.2.3 From 563a98e4bd0fd9f2277befcfe45ff2d0476878d8 Mon Sep 17 00:00:00 2001 From: Shyam Seshadri Date: Wed, 24 Mar 2010 10:50:02 -0700 Subject: Fix rakefile to work with older versions of rake --- Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index 5e0bf008..c3189831 100644 --- a/Rakefile +++ b/Rakefile @@ -31,7 +31,7 @@ end desc 'Compile JavaScript' task :compile do - Rake::Task['compileexterns'].execute + Rake::Task['compileexterns'].execute 0 concat = %x(cat \ src/angular.prefix \ -- cgit v1.2.3 From fd72031e9626b574a4459747c2e0261f69102364 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Wed, 24 Mar 2010 12:08:09 -0700 Subject: work in progress --- Rakefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Rakefile b/Rakefile index 5e0bf008..a5edf04f 100644 --- a/Rakefile +++ b/Rakefile @@ -9,16 +9,16 @@ task :compileexterns do out.write("function _(){};\n") file = File.new("lib/underscore/underscore.js", "r") while (line = file.gets) - if line =~ /^\s*_\.(\w+)\s*=.*$/ + if line =~ /^\s*_\.(\w+)\s*=.*$/ out.write("_.#{$1}=function(){};\n") end end file.close out.write("function jQuery(){};\n") - file = File.new("lib/jquery/jquery-1.3.2.js", "r") + file = File.new("lib/jquery/jquery-1.4.2.js", "r") while (line = file.gets) - if line =~ /^\s*(\w+)\s*:\s*function.*$/ + if line =~ /^\s*(\w+)\s*:\s*function.*$/ out.write("jQuery.#{$1}=function(){};\n") end end -- cgit v1.2.3 From 0c42eb9909d554807549cd3394e0ea0c715cc2d1 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Wed, 24 Mar 2010 16:13:42 -0700 Subject: input[type=text] now works with binding, validation, formatter, required --- src/Angular.js | 9 ++++--- src/Compiler.js | 4 +-- src/Validators.js | 24 +++++++++-------- src/jqLite.js | 34 ++++++++++++++---------- src/widgets2.js | 71 ++++++++++++++++++++++++++++++++++++++++++++++++-- test/ValidatorsTest.js | 34 ++++++++++++------------ test/widgetsSpec.js | 68 ++++++++++++++++++++++++++++++++++++++++++----- 7 files changed, 187 insertions(+), 57 deletions(-) diff --git a/src/Angular.js b/src/Angular.js index 95f7325a..b76926b9 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -44,6 +44,8 @@ function extensionList(angular, name) { } var consoleNode, msie, + VALUE = 'value', + NOOP = 'noop', jQuery = window['jQuery'] || window['$'], // weirdness to make IE happy slice = Array.prototype.slice, angular = window['angular'] || (window['angular'] = {}), @@ -92,6 +94,9 @@ function isObject(value){ return typeof value == 'object';} function isString(value){ return typeof value == 'string';} function isArray(value) { return value instanceof Array; } function isFunction(value){ return typeof value == 'function';} +function lowercase(value){ return isString(value) ? value.toLowerCase() : value; } +function uppercase(value){ return isString(value) ? value.toUpperCase() : value; } +function trim(value) { return isString(value) ? value.replace(/^\s*/, '').replace(/\s*$/, '') : value; }; function log(a, b, c){ var console = window['console']; @@ -244,10 +249,6 @@ function outerHTML(node) { return outerHTML; } -function trim(str) { - return str.replace(/^ */, '').replace(/ *$/, ''); -} - function toBoolean(value) { var v = ("" + value).toLowerCase(); if (v == 'f' || v == '0' || v == 'false' || v == 'no') diff --git a/src/Compiler.js b/src/Compiler.js index ba598a43..4423fcef 100644 --- a/src/Compiler.js +++ b/src/Compiler.js @@ -105,9 +105,7 @@ Compiler.prototype = { templatize: function(element){ var self = this, - elementName = element[0].nodeName, - widgets = self.widgets, - widget = widgets[elementName], + widget = self.widgets[element[0].nodeName], directives = self.directives, descend = true, exclusive = false, diff --git a/src/Validators.js b/src/Validators.js index b7efcb4a..cdff5e1a 100644 --- a/src/Validators.js +++ b/src/Validators.js @@ -1,4 +1,6 @@ foreach({ + 'noop': noop, + 'regexp': function(value, regexp, msg) { if (!value.match(regexp)) { return msg || @@ -7,7 +9,7 @@ foreach({ return null; } }, - + 'number': function(value, min, max) { var num = 1 * value; if (num == value) { @@ -19,40 +21,40 @@ foreach({ } return null; } else { - return "Value is not a number."; + return "Not a number"; } }, - + 'integer': function(value, min, max) { var numberError = angularValidator['number'](value, min, max); if (numberError) return numberError; if (!("" + value).match(/^\s*[\d+]*\s*$/) || value != Math.round(value)) { - return "Value is not a whole number."; + return "Not a whole number"; } return null; }, - + 'date': function(value, min, max) { if (value.match(/^\d\d?\/\d\d?\/\d\d\d\d$/)) { return null; } return "Value is not a date. (Expecting format: 12/31/2009)."; }, - + 'ssn': function(value) { if (value.match(/^\d\d\d-\d\d-\d\d\d\d$/)) { return null; } return "SSN needs to be in 999-99-9999 format."; }, - + 'email': function(value) { if (value.match(/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/)) { return null; } return "Email needs to be in username@host.com format."; }, - + 'phone': function(value) { if (value.match(/^1\(\d\d\d\)\d\d\d-\d\d\d\d$/)) { return null; @@ -62,14 +64,14 @@ foreach({ } return "Phone number needs to be in 1(987)654-3210 format in North America or +999 (123) 45678 906 internationaly."; }, - + 'url': function(value) { if (value.match(/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/)) { return null; } return "URL needs to be in http://server[:port]/path format."; }, - + 'json': function(value) { try { fromJson(value); @@ -78,7 +80,7 @@ foreach({ return e.toString(); } }, - + 'asynchronous': function(text, asynchronousFn) { var stateKey = '$validateState'; var lastKey = '$lastKey'; diff --git a/src/jqLite.js b/src/jqLite.js index 035a7a1b..2ac3f6c9 100644 --- a/src/jqLite.js +++ b/src/jqLite.js @@ -77,22 +77,24 @@ JQLite.prototype = { }, bind: function(type, fn){ - var element = this[0], - bind = this.data('bind'), + var self = this, + element = self[0], + bind = self.data('bind'), eventHandler; if (!bind) this.data('bind', bind = {}); - eventHandler = bind[type]; - if (!eventHandler) { - bind[type] = eventHandler = function() { - var self = this; - foreach(eventHandler.fns, function(fn){ - fn.apply(self, arguments); - }); - }; - eventHandler.fns = []; - addEventListener(element, type, eventHandler); - } - eventHandler.fns.push(fn); + foreach(type.split(' '), function(type){ + eventHandler = bind[type]; + if (!eventHandler) { + bind[type] = eventHandler = function() { + foreach(eventHandler.fns, function(fn){ + fn.apply(self, arguments); + }); + }; + eventHandler.fns = []; + addEventListener(element, type, eventHandler); + } + eventHandler.fns.push(fn); + }); }, trigger: function(type) { @@ -134,6 +136,10 @@ JQLite.prototype = { return false; }, + removeClass: function(selector) { + this[0].className = trim((" " + this[0].className + " ").replace(/[\n\t]/g, " ").replace(" " + selector + " ", "")); + }, + addClass: function( selector ) { if (!this.hasClass(selector)) { this[0].className += ' ' + selector; diff --git a/src/widgets2.js b/src/widgets2.js index b0f467d4..cee0e49e 100644 --- a/src/widgets2.js +++ b/src/widgets2.js @@ -1,3 +1,72 @@ +function scopeAccessor(scope, element) { + var expr = element.attr('name'), + farmatterName = element.attr('ng-format') || NOOP, + formatter = angularFormatter(farmatterName); + if (!expr) throw "Required field 'name' not found."; + if (!formatter) throw "Formatter named '" + farmatterName + "' not found."; + return { + get: function() { + return formatter['format'](scope.$eval(expr)); + }, + set: function(value) { + scope.$eval(expr + '=' + toJson(formatter['parse'](value))); + } + }; +} + +function domAccessor(element) { + var validatorName = element.attr('ng-validate') || NOOP, + validator = angularValidator(validatorName), + required = element.attr('ng-required'), + lastError; + required = required || required == ''; + if (!validator) throw "Validator named '" + validatorName + "' not found."; + function validate(value) { + var error = required && !trim(value) ? "Required" : validator(value); + if (error !== lastError) { + if (error) { + element.addClass(NG_VALIDATION_ERROR); + element.attr(NG_ERROR, error); + } else { + element.removeClass(NG_VALIDATION_ERROR); + element.removeAttr(NG_ERROR); + } + lastError = error; + } + return value; + } + return { + get: function(){ + return validate(element.attr(VALUE)); + }, + set: function(value){ + element.attr(VALUE, validate(value)); + } + }; +} + +var NG_ERROR = 'ng-error', + NG_VALIDATION_ERROR = 'ng-validation-error', + INPUT_META = { + 'text': ["", 'keyup change'] +}; + +angularWidget('INPUT', function input(element){ + var meta = INPUT_META[lowercase(element.attr('type'))]; + return meta ? function(element) { + var scope = scopeAccessor(this, element), + dom = domAccessor(element); + scope.set(dom.get() || meta[0]); + element.bind(meta[1], function(){ + scope.set(dom.get()); + }); + this.$watch(scope.get, dom.set); + } : 0; +}); + + + + ///////////////////////////////////////// ///////////////////////////////////////// ///////////////////////////////////////// @@ -6,8 +75,6 @@ - - //widget related //ng-validate, ng-required, ng-formatter //ng-error diff --git a/test/ValidatorsTest.js b/test/ValidatorsTest.js index 5449ebb0..6b61c273 100644 --- a/test/ValidatorsTest.js +++ b/test/ValidatorsTest.js @@ -26,7 +26,7 @@ ValidatorTest.prototype.testRegexp = function() { }; ValidatorTest.prototype.testNumber = function() { - assertEquals(angular.validator.number("ab"), "Value is not a number."); + assertEquals(angular.validator.number("ab"), "Not a number"); assertEquals(angular.validator.number("-0.1",0), "Value can not be less than 0."); assertEquals(angular.validator.number("10.1",0,10), "Value can not be greater than 10."); assertEquals(angular.validator.number("1.2"), null); @@ -34,10 +34,10 @@ ValidatorTest.prototype.testNumber = function() { }; ValidatorTest.prototype.testInteger = function() { - assertEquals(angular.validator.integer("ab"), "Value is not a number."); - assertEquals(angular.validator.integer("1.1"), "Value is not a whole number."); - assertEquals(angular.validator.integer("1.0"), "Value is not a whole number."); - assertEquals(angular.validator.integer("1."), "Value is not a whole number."); + assertEquals(angular.validator.integer("ab"), "Not a number"); + assertEquals(angular.validator.integer("1.1"), "Not a whole number"); + assertEquals(angular.validator.integer("1.0"), "Not a whole number"); + assertEquals(angular.validator.integer("1."), "Not a whole number"); assertEquals(angular.validator.integer("-1",0), "Value can not be less than 0."); assertEquals(angular.validator.integer("11",0,10), "Value can not be greater than 10."); assertEquals(angular.validator.integer("1"), null); @@ -86,7 +86,7 @@ describe('Validator:asynchronous', function(){ var asynchronous = angular.validator.asynchronous; var self; var value, fn; - + beforeEach(function(){ value = null; fn = null; @@ -96,10 +96,10 @@ describe('Validator:asynchronous', function(){ $updateView: noop }; }); - + it('should make a request and show spinner', function(){ - var x = compile('') - var asyncFn = function(v,f){value=v; fn=f}; + var x = compile(''); + var asyncFn = function(v,f){value=v; fn=f;}; var input = x.node.find(":input"); x.scope.set("asyncFn", asyncFn); x.scope.set("name", "misko"); @@ -110,29 +110,29 @@ describe('Validator:asynchronous', function(){ expect(input.hasClass('ng-input-indicator-wait')).toBeFalsy(); expect(input.attr('ng-error')).toEqual("myError"); }); - + it("should not make second request to same value", function(){ asynchronous.call(self, "kai", function(v,f){value=v; fn=f;}); expect(value).toEqual('kai'); expect(self.$invalidWidgets).toEqual([self.$element]); - + var spy = jasmine.createSpy(); asynchronous.call(self, "kai", spy); expect(spy).wasNotCalled(); - + asynchronous.call(self, "misko", spy); - expect(spy).wasCalled(); + expect(spy).wasCalled(); }); - + it("should ignore old callbacks, and not remove spinner", function(){ var firstCb, secondCb; asynchronous.call(self, "first", function(v,f){value=v; firstCb=f;}); asynchronous.call(self, "second", function(v,f){value=v; secondCb=f;}); - + firstCb(); expect($(self.$element).hasClass('ng-input-indicator-wait')).toBeTruthy(); - + secondCb(); expect($(self.$element).hasClass('ng-input-indicator-wait')).toBeFalsy(); }); -}); \ No newline at end of file +}); diff --git a/test/widgetsSpec.js b/test/widgetsSpec.js index 3b6be8ec..da9d5f9b 100644 --- a/test/widgetsSpec.js +++ b/test/widgetsSpec.js @@ -1,4 +1,4 @@ -describe("widgets", function(){ +describe("input widget", function(){ var compile, element, scope; @@ -15,14 +15,70 @@ describe("widgets", function(){ }); afterEach(function(){ - if (element) { - element.remove(); - } + if (element) element.remove(); expect(_(jqCache).size()).toEqual(0); }); - it('should fail', function(){ - fail('iueoi'); + it('should input-text auto init and handle keyup/change events', function(){ + compile(''); + expect(scope.get('name')).toEqual("Misko"); + + scope.set('name', 'Adam'); + scope.updateView(); + expect(element.attr('value')).toEqual("Adam"); + + element.attr('value', 'Shyam'); + element.trigger('keyup'); + expect(scope.get('name')).toEqual('Shyam'); + + element.attr('value', 'Kai'); + element.trigger('change'); + expect(scope.get('name')).toEqual('Kai'); + }); + + it("should process ng-format", function(){ + compile(''); + expect(scope.get('list')).toEqual(['a', 'b', 'c']); + + scope.set('list', ['x', 'y', 'z']); + scope.updateView(); + expect(element.attr('value')).toEqual("x, y, z"); + + element.attr('value', '1, 2, 3'); + element.trigger('keyup'); + expect(scope.get('list')).toEqual(['1', '2', '3']); + }); + + it("should process ng-validation", function(){ + compile(''); + expect(element.hasClass('ng-validation-error')).toBeTruthy(); + expect(element.attr('ng-error')).toEqual('Not a number'); + + scope.set('price', '123'); + scope.updateView(); + expect(element.hasClass('ng-validation-error')).toBeFalsy(); + expect(element.attr('ng-error')).toBeFalsy(); + + element.attr('value', 'x'); + element.trigger('keyup'); + expect(element.hasClass('ng-validation-error')).toBeTruthy(); + expect(element.attr('ng-error')).toEqual('Not a number'); + }); + + it("should process ng-required", function(){ + compile(''); + expect(element.hasClass('ng-validation-error')).toBeTruthy(); + expect(element.attr('ng-error')).toEqual('Required'); + + scope.set('price', 'xxx'); + scope.updateView(); + expect(element.hasClass('ng-validation-error')).toBeFalsy(); + expect(element.attr('ng-error')).toBeFalsy(); + + element.attr('value', ''); + element.trigger('keyup'); + expect(element.hasClass('ng-validation-error')).toBeTruthy(); + expect(element.attr('ng-error')).toEqual('Required'); }); }); -- cgit v1.2.3 From f29f6a47c4d81c5b8e365a3dae307159f1b12968 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Wed, 24 Mar 2010 16:47:11 -0700 Subject: fixed .value vs attr(value) access --- src/Angular.js | 1 - src/jqLite.js | 7 +++++++ src/widgets2.js | 27 +++++++++++++++++---------- test/widgetsSpec.js | 32 +++++++++++++++++++++++++------- 4 files changed, 49 insertions(+), 18 deletions(-) diff --git a/src/Angular.js b/src/Angular.js index b76926b9..dc530921 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -44,7 +44,6 @@ function extensionList(angular, name) { } var consoleNode, msie, - VALUE = 'value', NOOP = 'noop', jQuery = window['jQuery'] || window['$'], // weirdness to make IE happy slice = Array.prototype.slice, diff --git a/src/jqLite.js b/src/jqLite.js index 2ac3f6c9..2132bfc1 100644 --- a/src/jqLite.js +++ b/src/jqLite.js @@ -179,6 +179,13 @@ JQLite.prototype = { return this[0].textContent; }, + val: function(value) { + if (isDefined(value)) { + this[0].value = value; + } + return this[0].value; + }, + html: function(value) { if (isDefined(value)) { this[0].innerHTML = value; diff --git a/src/widgets2.js b/src/widgets2.js index cee0e49e..5425b5a4 100644 --- a/src/widgets2.js +++ b/src/widgets2.js @@ -36,23 +36,22 @@ function domAccessor(element) { return value; } return { - get: function(){ - return validate(element.attr(VALUE)); - }, - set: function(value){ - element.attr(VALUE, validate(value)); - } + get: function(){ return validate(element.val()); }, + set: function(value){ element.val(validate(value)); } }; } var NG_ERROR = 'ng-error', NG_VALIDATION_ERROR = 'ng-validation-error', + TEXT_META = ['', 'keyup change'], INPUT_META = { - 'text': ["", 'keyup change'] -}; + 'text': TEXT_META, + 'textarea': TEXT_META, + 'hidden': TEXT_META, + 'password': TEXT_META + }; -angularWidget('INPUT', function input(element){ - var meta = INPUT_META[lowercase(element.attr('type'))]; +function inputWidget(meta) { return meta ? function(element) { var scope = scopeAccessor(this, element), dom = domAccessor(element); @@ -62,6 +61,14 @@ angularWidget('INPUT', function input(element){ }); this.$watch(scope.get, dom.set); } : 0; +} + +angularWidget('INPUT', function input(element){ + return inputWidget(INPUT_META[lowercase(element[0].type)]); +}); + +angularWidget('TEXTAREA', function(){ + return inputWidget(INPUT_META['text']); }); diff --git a/test/widgetsSpec.js b/test/widgetsSpec.js index da9d5f9b..aeb7a613 100644 --- a/test/widgetsSpec.js +++ b/test/widgetsSpec.js @@ -25,13 +25,13 @@ describe("input widget", function(){ scope.set('name', 'Adam'); scope.updateView(); - expect(element.attr('value')).toEqual("Adam"); + expect(element.val()).toEqual("Adam"); - element.attr('value', 'Shyam'); + element.val('Shyam'); element.trigger('keyup'); expect(scope.get('name')).toEqual('Shyam'); - element.attr('value', 'Kai'); + element.val('Kai'); element.trigger('change'); expect(scope.get('name')).toEqual('Kai'); }); @@ -42,9 +42,9 @@ describe("input widget", function(){ scope.set('list', ['x', 'y', 'z']); scope.updateView(); - expect(element.attr('value')).toEqual("x, y, z"); + expect(element.val()).toEqual("x, y, z"); - element.attr('value', '1, 2, 3'); + element.val('1, 2, 3'); element.trigger('keyup'); expect(scope.get('list')).toEqual(['1', '2', '3']); }); @@ -59,7 +59,7 @@ describe("input widget", function(){ expect(element.hasClass('ng-validation-error')).toBeFalsy(); expect(element.attr('ng-error')).toBeFalsy(); - element.attr('value', 'x'); + element.val('x'); element.trigger('keyup'); expect(element.hasClass('ng-validation-error')).toBeTruthy(); expect(element.attr('ng-error')).toEqual('Not a number'); @@ -75,10 +75,28 @@ describe("input widget", function(){ expect(element.hasClass('ng-validation-error')).toBeFalsy(); expect(element.attr('ng-error')).toBeFalsy(); - element.attr('value', ''); + element.val(''); element.trigger('keyup'); expect(element.hasClass('ng-validation-error')).toBeTruthy(); expect(element.attr('ng-error')).toEqual('Required'); }); + it("should process ng-required", function() { + compile(''); + expect(scope.get('name')).toEqual("Misko"); + + scope.set('name', 'Adam'); + scope.updateView(); + expect(element.val()).toEqual("Adam"); + + element.val('Shyam'); + element.trigger('keyup'); + expect(scope.get('name')).toEqual('Shyam'); + + element.val('Kai'); + element.trigger('change'); + expect(scope.get('name')).toEqual('Kai'); + }); + + }); -- cgit v1.2.3 From b814c79b58deeeeaa12b03261399ef80c0d6cc9f Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Thu, 25 Mar 2010 13:01:08 -0700 Subject: checkbox and radio now working --- src/Parser.js | 1 + src/Scope.js | 6 +- src/jqLite.js | 13 ++-- src/widgets2.js | 176 +++++++++++++++---------------------------------- test/ParserTest.js | 17 +++++ test/directivesSpec.js | 2 +- test/widgetsSpec.js | 60 ++++++++++++++++- 7 files changed, 142 insertions(+), 133 deletions(-) diff --git a/src/Parser.js b/src/Parser.js index 941d37f7..81a2afdc 100644 --- a/src/Parser.js +++ b/src/Parser.js @@ -10,6 +10,7 @@ Lexer.OPERATORS = { 'null':function(self){return null;}, 'true':function(self){return true;}, 'false':function(self){return false;}, + 'undefined':noop, '+':function(self, a,b){return (a||0)+(b||0);}, '-':function(self, a,b){return (a||0)-(b||0);}, '*':function(self, a,b){return a*b;}, diff --git a/src/Scope.js b/src/Scope.js index daafabb0..5f1cfdda 100644 --- a/src/Scope.js +++ b/src/Scope.js @@ -12,8 +12,10 @@ function Scope(initialState, name) { '$parent': initialState, '$watch': bind(self, self.addWatchListener), '$eval': bind(self, self.eval), - // change name to onEval? - '$addEval': bind(self, self.addEval) + '$bind': bind(self, bind, self), + // change name to autoEval? + '$addEval': bind(self, self.addEval), + '$updateView': bind(self, self.updateView) }); if (name == "ROOT") { self.state['$root'] = self.state; diff --git a/src/jqLite.js b/src/jqLite.js index 2132bfc1..7646bf98 100644 --- a/src/jqLite.js +++ b/src/jqLite.js @@ -71,7 +71,7 @@ JQLite.prototype = { (function dealoc(element){ jqClearData(element); for ( var i = 0, children = element.childNodes; i < children.length; i++) { - dealoc(children[0]); + dealoc(children[i]); } })(this[0]); }, @@ -86,9 +86,11 @@ JQLite.prototype = { eventHandler = bind[type]; if (!eventHandler) { bind[type] = eventHandler = function() { + var value = false; foreach(eventHandler.fns, function(fn){ - fn.apply(self, arguments); + value = value || fn.apply(self, arguments); }); + return value; }; eventHandler.fns = []; addEventListener(element, type, eventHandler); @@ -98,10 +100,9 @@ JQLite.prototype = { }, trigger: function(type) { - var cache = this.data('bind'); - if (cache) { - (cache[type] || noop)(); - } + var evnt = document.createEvent('MouseEvent'); + evnt.initMouseEvent(type, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); + this[0].dispatchEvent(evnt); }, click: function(fn) { diff --git a/src/widgets2.js b/src/widgets2.js index 5425b5a4..b67694b1 100644 --- a/src/widgets2.js +++ b/src/widgets2.js @@ -1,4 +1,4 @@ -function scopeAccessor(scope, element) { +function modelAccessor(scope, element) { var expr = element.attr('name'), farmatterName = element.attr('ng-format') || NOOP, formatter = angularFormatter(farmatterName); @@ -14,7 +14,7 @@ function scopeAccessor(scope, element) { }; } -function domAccessor(element) { +function valueAccessor(element) { var validatorName = element.attr('ng-validate') || NOOP, validator = angularValidator(validatorName), required = element.attr('ng-required'), @@ -41,135 +41,67 @@ function domAccessor(element) { }; } +function checkedAccessor(element) { + var domElement = element[0]; + return { + get: function(){ return !!domElement.checked; }, + set: function(value){ domElement.checked = !!value; } + }; +} + +function radioAccessor(element) { + var domElement = element[0]; + return { + get: function(){ return domElement.checked ? domElement.value : null; }, + set: function(value){ domElement.checked = value == domElement.value; } + }; +} + +function noopAccessor() { return { get: noop, set: noop }; } + var NG_ERROR = 'ng-error', NG_VALIDATION_ERROR = 'ng-validation-error', - TEXT_META = ['', 'keyup change'], - INPUT_META = { - 'text': TEXT_META, - 'textarea': TEXT_META, - 'hidden': TEXT_META, - 'password': TEXT_META + textWidget = inputWidget('keyup change', modelAccessor, valueAccessor, ''), + buttonWidget = inputWidget('click', noopAccessor, noopAccessor, undefined), + INPUT_TYPE = { + 'text': textWidget, + 'textarea': textWidget, + 'hidden': textWidget, + 'password': textWidget, + 'button': buttonWidget, + 'submit': buttonWidget, + 'reset': buttonWidget, + 'image': buttonWidget, + 'checkbox': inputWidget('click', modelAccessor, checkedAccessor, false), + 'radio': inputWidget('click', modelAccessor, radioAccessor, undefined) +// 'select-one': [null, 'change'], +// 'select-multiple': [[], 'change'], +// 'file': [{}, 'click'] }; -function inputWidget(meta) { - return meta ? function(element) { - var scope = scopeAccessor(this, element), - dom = domAccessor(element); - scope.set(dom.get() || meta[0]); - element.bind(meta[1], function(){ - scope.set(dom.get()); +function inputWidget(events, modelAccessor, viewAccessor, initValue) { + return function(element) { + var scope = this, + model = modelAccessor(scope, element), + view = viewAccessor(element), + action = element.attr('ng-action') || ''; + var value = view.get() || initValue; + if (isDefined(value)) model.set(value); + element.bind(events, function(){ + model.set(view.get()); + scope.$eval(action); }); - this.$watch(scope.get, dom.set); - } : 0; + scope.$watch(model.get, view.set); + }; } angularWidget('INPUT', function input(element){ - return inputWidget(INPUT_META[lowercase(element[0].type)]); -}); - -angularWidget('TEXTAREA', function(){ - return inputWidget(INPUT_META['text']); -}); - - - - -///////////////////////////////////////// -///////////////////////////////////////// -///////////////////////////////////////// -///////////////////////////////////////// -///////////////////////////////////////// - - - -//widget related -//ng-validate, ng-required, ng-formatter -//ng-error - -//ng-scope ng-controller???? - -// -> -angular.widget("inputtext", function(element) { - var expression = element.attr('name'); - var formatter = this.formatter(element.attr('formatter')); - var validator = this.validator(element.attr('validator')); - - function validate(value) { - var error = validator(element); - if (error) { - element.addClass("ng-error"); - scope.markInvalid(this); //move out of scope - } else { - scope.clearInvalid(this); - } - } - - - element.keyup(this.withScope(function(){ - this.$evalSet(expression, formatter.parse(element.val())); - validate(element.val()); - })); - - return {watch: expression, apply: function(newValue){ - element.val(formatter.format(newValue)); - validate(element.val()); - }}; - -}); - -angular.widget("inputfile", function(element) { - -}); - -angular.widget("inputradio", function(element) { - -}); - - -// -angular.widget("colorpicker", function(element) { - var name = element.attr('datasource'); - var formatter = this.formatter(element.attr('ng-formatter')); - - element.colorPicker(this.withScope(function(selectedColor){ - this.$evalSet(name, formatter.parse(selectedColor)); - })); - - return function(){ - this.$watch(expression, function(cmyk){ - element.setColor(formatter.format(cmyk)); - }); + return function(element) { + this.$eval(element.attr('ng-init')||''); + (INPUT_TYPE[lowercase(element[0].type)] || noop).call(this, element); }; }); -angular.widget("template", function(element) { - var srcExpression = element.attr('src'); - var self = this; - return {watch:srcExpression, apply:function(src){ - $.load(src, function(html){ - self.destroy(element); - element.html(html); - self.compile(element); - }); - }}; +angularWidget('TEXTAREA', function(){ + return textWidget; }); - - -/** - * - * { - * withScope: //safely executes, with a try/catch. applies scope - * compile: - * widget: - * directive: - * validator: - * formatter: - * - * - * config: - * loadCSS: - * loadScript: - * loadTemplate: - * } - * - **/ diff --git a/test/ParserTest.js b/test/ParserTest.js index c8d323f2..d3813812 100644 --- a/test/ParserTest.js +++ b/test/ParserTest.js @@ -52,6 +52,16 @@ LexerTest.prototype.testTokenizeAString = function(){ assertEquals(tokens[i].string, 'd"e'); }; +LexerTest.prototype.testTokenizeUndefined = function(){ + var lexer = new Lexer("undefined"); + var tokens = lexer.parse(); + var i = 0; + assertEquals(tokens[i].index, 0); + assertEquals(tokens[i].text, 'undefined'); + assertEquals(undefined, tokens[i].fn()); +}; + + LexerTest.prototype.testTokenizeRegExp = function(){ var lexer = new Lexer("/r 1/"); @@ -486,3 +496,10 @@ ParserTest.prototype.testParsingBug = function () { var scope = new Scope(); assertEquals({a: "-"}, scope.eval("{a:'-'}")); }; + +ParserTest.prototype.testUndefined = function () { + var scope = new Scope(); + assertEquals(undefined, scope.eval("undefined")); + assertEquals(undefined, scope.eval("a=undefined")); + assertEquals(undefined, scope.get("a")); +}; diff --git a/test/directivesSpec.js b/test/directivesSpec.js index 18bedb64..83a270c1 100644 --- a/test/directivesSpec.js +++ b/test/directivesSpec.js @@ -12,7 +12,7 @@ describe("directives", function(){ }; }); - afterEach(function(){ + afterEach(function() { element.remove(); expect(_(jqCache).size()).toEqual(0); }); diff --git a/test/widgetsSpec.js b/test/widgetsSpec.js index aeb7a613..44a3d225 100644 --- a/test/widgetsSpec.js +++ b/test/widgetsSpec.js @@ -1,6 +1,6 @@ describe("input widget", function(){ - var compile, element, scope; + var compile, element, scope, model; beforeEach(function() { scope = null; @@ -11,6 +11,7 @@ describe("input widget", function(){ var view = compiler.compile(element)(element); view.init(); scope = view.scope; + model = scope.state; }; }); @@ -20,8 +21,9 @@ describe("input widget", function(){ }); it('should input-text auto init and handle keyup/change events', function(){ - compile(''); + compile(''); expect(scope.get('name')).toEqual("Misko"); + expect(scope.get('count')).toEqual(0); scope.set('name', 'Adam'); scope.updateView(); @@ -30,10 +32,12 @@ describe("input widget", function(){ element.val('Shyam'); element.trigger('keyup'); expect(scope.get('name')).toEqual('Shyam'); + expect(scope.get('count')).toEqual(1); element.val('Kai'); element.trigger('change'); expect(scope.get('name')).toEqual('Kai'); + expect(scope.get('count')).toEqual(2); }); it("should process ng-format", function(){ @@ -98,5 +102,57 @@ describe("input widget", function(){ expect(scope.get('name')).toEqual('Kai'); }); + it('should call ng-action on button click', function(){ + compile(''); + element.click(); + expect(scope.get('clicked')).toEqual(true); + }); + + it('should type="checkbox"', function(){ + compile(''); + expect(scope.get('checkbox')).toEqual(true); + element.click(); + expect(scope.get('checkbox')).toEqual(false); + expect(scope.get('action')).toEqual(true); + element.click(); + expect(scope.get('checkbox')).toEqual(true); + }); + + it('should type="radio"', function(){ + compile('
        ' + + '' + + '' + + '
        '); + var a = element[0].childNodes[0]; + var b = element[0].childNodes[1]; + expect(model.chose).toEqual('B'); + expect(model.clicked).not.toBeDefined(); + model.chose = 'A'; + model.$updateView(); + expect(a.checked).toEqual(true); + + model.chose = 'B'; + model.$updateView(); + expect(a.checked).toEqual(false); + expect(b.checked).toEqual(true); + expect(model.clicked).not.toBeDefined(); + + jqLite(a).click(); + expect(model.chose).toEqual('A'); + expect(model.clicked).toEqual(1); + }); + + it('should report error on missing field', function(){ + + }); + + it('should report error on assignment error', function(){ + + }); + + it('should report error on ng-action exception', function(){ + + }); + }); -- cgit v1.2.3 From 4fa166866b97d4f4dbd21514dbd674347da0a109 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Thu, 25 Mar 2010 14:43:05 -0700 Subject: input select-one now works --- example/widgets.html | 17 +++++++++++------ src/Angular.js | 15 ++------------- src/Compiler.js | 13 +++++++------ src/markup.js | 48 ++++++++++++++++++++++++++++++------------------ src/widgets2.js | 26 ++++++++++++++------------ test/markupSpec.js | 11 ++++++++--- test/widgetsSpec.js | 21 +++++++++++++++++++++ 7 files changed, 93 insertions(+), 58 deletions(-) diff --git a/example/widgets.html b/example/widgets.html index d3e980a1..6a8214e7 100644 --- a/example/widgets.html +++ b/example/widgets.html @@ -2,7 +2,7 @@ - + - - + +
        @@ -26,8 +22,6 @@
        - -
        name={{name}}
        -

        -

        - Female - Male - gender={{gender}} -

        -

        - tea={{tea}}
        - coffee={{coffee}}
        -

        -

        -

        - - - - action - count={{count}} -
        -

        -

        - - select={{select}} -

        -

        - - multiple={{multiple}} -

        -

        - - Hidden field = {{hidden}} -

        -

        - - Password field = {{password}} -

        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        DescriptionTestResult
        Input text field
        basictext.basic={{text.basic}}
        passwordtext.password={{text.password}}
        hiddenhidden={{hidden}}
        Input selection field
        radio + Female
        + Male +
        gender={{gender}}
        checkbox + Tea
        + Coffe +
        checkbox={{checkbox}}
        select + + select={{select}}
        multiselect + + multiselect={{multiselect}}
        Buttons
        ng-action +
        +
        +
        +
        + action +
        +
        button={{button}}
        diff --git a/src/API.js b/src/API.js deleted file mode 100644 index ce690ad1..00000000 --- a/src/API.js +++ /dev/null @@ -1,325 +0,0 @@ -var angularGlobal = { - 'typeOf':function(obj){ - if (obj === null) return "null"; - var type = typeof obj; - if (type == "object") { - if (obj instanceof Array) return "array"; - if (obj instanceof Date) return "date"; - if (obj.nodeType == 1) return "element"; - } - return type; - } -}; - -var angularCollection = {}; -var angularObject = {}; -var angularArray = { - 'includeIf':function(array, value, condition) { - var index = _.indexOf(array, value); - if (condition) { - if (index == -1) - array.push(value); - } else { - array.splice(index, 1); - } - return array; - }, - 'sum':function(array, expression) { - var fn = angular['Function']['compile'](expression); - var sum = 0; - for (var i = 0; i < array.length; i++) { - var value = 1 * fn(array[i]); - if (!isNaN(value)){ - sum += value; - } - } - return sum; - }, - 'remove':function(array, value) { - var index = _.indexOf(array, value); - if (index >=0) - array.splice(index, 1); - return value; - }, - 'find':function(array, condition, defaultValue) { - if (!condition) return undefined; - var fn = angular['Function']['compile'](condition); - _.detect(array, function($){ - if (fn($)){ - defaultValue = $; - return true; - } - }); - return defaultValue; - }, - 'findById':function(array, id) { - return angular.Array.find(array, function($){return $.$id == id;}, null); - }, - 'filter':function(array, expression) { - var predicates = []; - predicates.check = function(value) { - for (var j = 0; j < predicates.length; j++) { - if(!predicates[j](value)) { - return false; - } - } - return true; - }; - var getter = Scope.getter; - var search = function(obj, text){ - if (text.charAt(0) === '!') { - return !search(obj, text.substr(1)); - } - switch (typeof obj) { - case "boolean": - case "number": - case "string": - return ('' + obj).toLowerCase().indexOf(text) > -1; - case "object": - for ( var objKey in obj) { - if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) { - return true; - } - } - return false; - case "array": - for ( var i = 0; i < obj.length; i++) { - if (search(obj[i], text)) { - return true; - } - } - return false; - default: - return false; - } - }; - switch (typeof expression) { - case "boolean": - case "number": - case "string": - expression = {$:expression}; - case "object": - for (var key in expression) { - if (key == '$') { - (function(){ - var text = (''+expression[key]).toLowerCase(); - if (!text) return; - predicates.push(function(value) { - return search(value, text); - }); - })(); - } else { - (function(){ - var path = key; - var text = (''+expression[key]).toLowerCase(); - if (!text) return; - predicates.push(function(value) { - return search(getter(value, path), text); - }); - })(); - } - } - break; - case "function": - predicates.push(expression); - break; - default: - return array; - } - var filtered = []; - for ( var j = 0; j < array.length; j++) { - var value = array[j]; - if (predicates.check(value)) { - filtered.push(value); - } - } - return filtered; - }, - 'add':function(array, value) { - array.push(_.isUndefined(value)? {} : value); - return array; - }, - 'count':function(array, condition) { - if (!condition) return array.length; - var fn = angular['Function']['compile'](condition); - return _.reduce(array, 0, function(count, $){return count + (fn($)?1:0);}); - }, - 'orderBy':function(array, expression, descend) { - function reverse(comp, descending) { - return toBoolean(descending) ? - function(a,b){return comp(b,a);} : comp; - } - function compare(v1, v2){ - var t1 = typeof v1; - var t2 = typeof v2; - if (t1 == t2) { - if (t1 == "string") v1 = v1.toLowerCase(); - if (t1 == "string") v2 = v2.toLowerCase(); - if (v1 === v2) return 0; - return v1 < v2 ? -1 : 1; - } else { - return t1 < t2 ? -1 : 1; - } - } - expression = _.isArray(expression) ? expression: [expression]; - expression = _.map(expression, function($){ - var descending = false; - if (typeof $ == "string" && ($.charAt(0) == '+' || $.charAt(0) == '-')) { - descending = $.charAt(0) == '-'; - $ = $.substring(1); - } - var get = $ ? angular['Function']['compile']($) : _.identity; - return reverse(function(a,b){ - return compare(get(a),get(b)); - }, descending); - }); - var comparator = function(o1, o2){ - for ( var i = 0; i < expression.length; i++) { - var comp = expression[i](o1, o2); - if (comp !== 0) return comp; - } - return 0; - }; - return _.clone(array).sort(reverse(comparator, descend)); - }, - 'orderByToggle':function(predicate, attribute) { - var STRIP = /^([+|-])?(.*)/; - var ascending = false; - var index = -1; - _.detect(predicate, function($, i){ - if ($ == attribute) { - ascending = true; - index = i; - return true; - } - if (($.charAt(0)=='+'||$.charAt(0)=='-') && $.substring(1) == attribute) { - ascending = $.charAt(0) == '+'; - index = i; - return true; - } - }); - if (index >= 0) { - predicate.splice(index, 1); - } - predicate.unshift((ascending ? "-" : "+") + attribute); - return predicate; - }, - 'orderByDirection':function(predicate, attribute, ascend, descend) { - ascend = ascend || 'ng-ascend'; - descend = descend || 'ng-descend'; - var att = predicate[0] || ''; - var direction = true; - if (att.charAt(0) == '-') { - att = att.substring(1); - direction = false; - } else if(att.charAt(0) == '+') { - att = att.substring(1); - } - return att == attribute ? (direction ? ascend : descend) : ""; - }, - 'merge':function(array, index, mergeValue) { - var value = array[index]; - if (!value) { - value = {}; - array[index] = value; - } - merge(mergeValue, value); - return array; - } -}; - -var angularString = { - 'quote':function(string) { - return '"' + string.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(string) { - var str = angular['String']['quote'](string); - var chars = []; - for ( var i = 0; i < str.length; i++) { - var ch = str.charCodeAt(i); - if (ch < 128) { - chars.push(str.charAt(i)); - } else { - var encode = "000" + ch.toString(16); - chars.push("\\u" + encode.substring(encode.length - 4)); - } - } - return chars.join(''); - }, - 'toDate':function(string){ - var match; - if (typeof string == 'string' && - (match = string.match(/^(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)Z$/))){ - var date = new Date(0); - date.setUTCFullYear(match[1], match[2] - 1, match[3]); - date.setUTCHours(match[4], match[5], match[6], 0); - return date; - } - return string; - } -}; - -var angularDate = { - 'toString':function(date){ - function pad(n) { return n < 10 ? "0" + n : n; } - return (date.getUTCFullYear()) + '-' + - pad(date.getUTCMonth() + 1) + '-' + - pad(date.getUTCDate()) + 'T' + - pad(date.getUTCHours()) + ':' + - pad(date.getUTCMinutes()) + ':' + - pad(date.getUTCSeconds()) + 'Z'; - } - }; - -var angularFunction = { - 'compile':function(expression) { - if (_.isFunction(expression)){ - return expression; - } else if (expression){ - var scope = new Scope(); - return function($) { - scope.state = $; - return scope.eval(expression); - }; - } else { - return function($){return $;}; - } - } -}; - -function defineApi(dst, chain, underscoreNames){ - var lastChain = _.last(chain); - foreach(underscoreNames, function(name){ - lastChain[name] = _[name]; - }); - angular[dst] = angular[dst] || {}; - foreach(chain, function(parent){ - extend(angular[dst], parent); - }); -} -defineApi('Global', [angularGlobal], - ['extend', 'clone','isEqual', - 'isElement', 'isArray', 'isFunction', 'isUndefined']); -defineApi('Collection', [angularGlobal, angularCollection], - ['each', 'map', 'reduce', 'reduceRight', 'detect', - 'select', 'reject', 'all', 'any', 'include', - 'invoke', 'pluck', 'max', 'min', 'sortBy', - 'sortedIndex', 'toArray', 'size']); -defineApi('Array', [angularGlobal, angularCollection, angularArray], - ['first', 'last', 'compact', 'flatten', 'without', - 'uniq', 'intersect', 'zip', 'indexOf', 'lastIndexOf']); -defineApi('Object', [angularGlobal, angularCollection, angularObject], - ['keys', 'values']); -defineApi('String', [angularGlobal, angularString], []); -defineApi('Date', [angularGlobal, angularDate], []); -//IE bug -angular['Date']['toString'] = angularDate['toString']; -defineApi('Function', [angularGlobal, angularCollection, angularFunction], - ['bind', 'bindAll', 'delay', 'defer', 'wrap', 'compose']); diff --git a/src/Angular.js b/src/Angular.js index 0cb89bbe..c3562e84 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -1,22 +1,5 @@ if (typeof document.getAttribute == 'undefined') document.getAttribute = function() {}; -if (typeof Node == 'undefined') { - //TODO: can we get rid of this? - 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 - }; -} function noop() {} function identity($) {return $;} @@ -32,9 +15,11 @@ function extensionMap(angular, name) { }); } -var consoleNode, msie, +var consoleNode, NOOP = 'noop', jQuery = window['jQuery'] || window['$'], // weirdness to make IE happy + _ = window['_'], + jqLite = jQuery, slice = Array.prototype.slice, angular = window['angular'] || (window['angular'] = {}), angularTextMarkup = extensionMap(angular, 'textMarkup'), @@ -77,6 +62,7 @@ function extend(dst, obj) { return dst; } +function isUndefined(value){ return typeof value == 'undefined'; } function isDefined(value){ return typeof value != 'undefined'; } function isObject(value){ return typeof value == 'object';} function isString(value){ return typeof value == 'string';} @@ -85,6 +71,12 @@ function isFunction(value){ return typeof value == 'function';} function lowercase(value){ return isString(value) ? value.toLowerCase() : value; } function uppercase(value){ return isString(value) ? value.toUpperCase() : value; } function trim(value) { return isString(value) ? value.replace(/^\s*/, '').replace(/\s*$/, '') : value; }; +function includes(array, obj) { + for ( var i = 0; i < array.length; i++) { + if (obj === array[i]) return true; + } + return false; +} function log(a, b, c){ var console = window['console']; @@ -154,18 +146,18 @@ function copy(source, destination){ if (!destination) { if (!source) { return source; - } else if (_.isArray(source)) { + } else if (isArray(source)) { return copy(source, []); } else { return copy(source, {}); } } else { - if (_.isArray(source)) { + if (isArray(source)) { while(destination.length) { destination.pop(); } } else { - _(destination).each(function(value, key){ + foreach(function(value, key){ delete destination[key]; }); } @@ -236,201 +228,19 @@ function merge(src, dst) { } } -// //////////////////////////// -// UrlWatcher -// //////////////////////////// - -function UrlWatcher(location) { - this.location = location; - this.delay = 25; - this.setTimeout = function(fn, delay) { - window.setTimeout(fn, delay); - }; - this.listener = function(url) { - return url; - }; - this.expectedUrl = location.href; -} - -UrlWatcher.prototype = { - listen: function(fn){ - this.listener = fn; - }, - watch: function() { - var self = this; - var pull = function() { - if (self.expectedUrl !== self.location.href) { - var notify = self.location.hash.match(/^#\$iframe_notify=(.*)$/); - if (notify) { - if (!self.expectedUrl.match(/#/)) { - self.expectedUrl += "#"; - } - self.location.href = self.expectedUrl; - var id = '_iframe_notify_' + notify[1]; - var notifyFn = angularCallbacks[id]; - delete angularCallbacks[id]; - try { - (notifyFn||noop)(); - } catch (e) { - alert(e); - } - } else { - self.listener(self.location.href); - self.expectedUrl = self.location.href; - } - } - self.setTimeout(pull, self.delay); - }; - pull(); - }, - - set: function(url) { - var existingURL = this.location.href; - if (!existingURL.match(/#/)) - existingURL += '#'; - if (existingURL != url) - this.location.href = url; - this.existingURL = url; - }, - - get: function() { - return window.location.href; - } -}; - ///////////////////////////////////////////////// -function 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, datastore, config['location'], config); - binder.updateListeners.push(config.onUpdateView); - var controlBar = new ControlBar(element.find('body'), config['server'], config['database']); - var onUpdate = function(){binder.updateView();}; - var server = config['database'] =="$MEMORY" ? - new FrameServer(window) : - new Server(config['server'], jQuery['getScript']); - server = new VisualServer(server, new NullStatus(element.find('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.datastore = datastore; - binder.updateListeners.push(function(){datastore.flush();}); - var scope = new Scope({ - '$anchor' : binder.anchor, - '$updateView': _(binder.updateView).bind(binder), - '$config' : config, - '$invalidWidgets': [], - '$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, { - 'set': scope.set, - 'get': scope.get, - 'eval': scope.eval - })).extend({ - 'init':function(){ - config['location']['listen'](_(binder.onUrlChange).bind(binder)); - binder.parseAnchor(); - binder.executeInit(); - binder.updateView(); - return self; - }, - 'element':element[0], - 'updateView': _(binder.updateView).bind(binder), - 'config':config - }); - return self; -} - -angular['startUrlWatcher'] = function(){ - var watcher = new UrlWatcher(window['location']); - watcher.watch(); - return exposeMethods(watcher, {'listen':watcher.listen, 'set':watcher.set, 'get':watcher.get}); -}; angular['compile'] = function(element, config) { - jQuery = window['jQuery']; - msie = jQuery['browser']['msie']; - config = _({ + config = extend({ 'onUpdateView': noop, 'server': "", 'location': {'get':noop, 'set':noop, 'listen':noop} - }).extend(config||{}); - - configureLogging(config); - configureJQueryPlugins(); - - return wireAngular(jQuery(element), config); + }, config||{}); + + var compiler = new Compiler(angularTextMarkup, angularAttrMarkup, angularDirective, angularWidget); + $element = jqLite(element), + rootScope = { + '$window': window + }; + return rootScope['$root'] = compiler.compile($element)($element, rootScope); }; diff --git a/src/Binder.js b/src/Binder.js deleted file mode 100644 index 9fc32513..00000000 --- a/src/Binder.js +++ /dev/null @@ -1,356 +0,0 @@ -function Binder(doc, widgetFactory, datastore, location, config) { - this.doc = doc; - this.location = location; - this.datastore = datastore; - this.anchor = {}; - this.widgetFactory = widgetFactory; - this.config = config || {}; - this.updateListeners = []; -} - -Binder.parseBindings = function(string) { - var results = []; - var lastIndex = 0; - var index; - while((index = string.indexOf('{{', lastIndex)) > -1) { - if (lastIndex < index) - results.push(string.substr(lastIndex, index - lastIndex)); - lastIndex = index; - - index = string.indexOf('}}', index); - index = index < 0 ? string.length : index + 2; - - results.push(string.substr(lastIndex, index - lastIndex)); - lastIndex = index; - } - if (lastIndex != string.length) - results.push(string.substr(lastIndex, string.length - lastIndex)); - return results.length === 0 ? [ string ] : results; -}; - -Binder.hasBinding = function(string) { - var bindings = Binder.parseBindings(string); - return bindings.length > 1 || Binder.binding(bindings[0]) !== null; -}; - -Binder.binding = function(string) { - var binding = string.replace(/\n/gm, ' ').match(/^\{\{(.*)\}\}$/); - return binding ? binding[1] : null; -}; - - -Binder.prototype = { - parseQueryString: function(query) { - var params = {}; - query.replace(/(?:^|&)([^&=]*)=?([^&]*)/g, - function (match, left, right) { - if (left) params[decodeURIComponent(left)] = decodeURIComponent(right); - }); - return params; - }, - - parseAnchor: function() { - var self = this, url = this.location['get']() || ""; - - var anchorIndex = url.indexOf('#'); - if (anchorIndex < 0) return; - var anchor = url.substring(anchorIndex + 1); - - var anchorQuery = this.parseQueryString(anchor); - foreach(self.anchor, function(newValue, key) { - delete self.anchor[key]; - }); - foreach(anchorQuery, function(newValue, key) { - self.anchor[key] = newValue; - }); - }, - - onUrlChange: function() { - this.parseAnchor(); - this.updateView(); - }, - - updateAnchor: function() { - var url = this.location['get']() || ""; - var anchorIndex = url.indexOf('#'); - if (anchorIndex > -1) - url = url.substring(0, anchorIndex); - url += "#"; - var sep = ''; - for (var key in this.anchor) { - var value = this.anchor[key]; - if (typeof value === 'undefined' || value === null) { - delete this.anchor[key]; - } else { - url += sep + encodeURIComponent(key); - if (value !== true) - url += "=" + encodeURIComponent(value); - sep = '&'; - } - } - this.location['set'](url); - return url; - }, - - updateView: function() { - var start = new Date().getTime(); - var scope = jQuery(this.doc).scope(); - scope.clearInvalid(); - scope.updateView(); - var end = new Date().getTime(); - this.updateAnchor(); - foreach(this.updateListeners, function(fn) {fn();}); - }, - - docFindWithSelf: function(exp){ - var doc = jQuery(this.doc); - var selection = doc.find(exp); - if (doc.is(exp)){ - selection = selection.andSelf(); - } - return selection; - }, - - executeInit: function() { - this.docFindWithSelf("[ng-init]").each(function() { - var jThis = jQuery(this); - var scope = jThis.scope(); - try { - scope.eval(jThis.attr('ng-init')); - } catch (e) { - alert("EVAL ERROR:\n" + jThis.attr('ng-init') + '\n' + toJson(e, true)); - } - }); - }, - - entity: function (scope) { - var self = this; - this.docFindWithSelf("[ng-entity]").attr("ng-watch", function() { - try { - var jNode = jQuery(this); - var decl = scope.entity(jNode.attr("ng-entity"), self.datastore); - return decl + (jNode.attr('ng-watch') || ""); - } catch (e) { - log(e); - alert(e); - } - }); - }, - - compile: function() { - var jNode = jQuery(this.doc); - if (this.config['autoSubmit']) { - var submits = this.docFindWithSelf(":submit").not("[ng-action]"); - submits.attr("ng-action", "$save()"); - submits.not(":disabled").not("ng-bind-attr").attr("ng-bind-attr", '{disabled:"{{$invalidWidgets}}"}'); - } - this.precompile(this.doc)(this.doc, jNode.scope(), ""); - this.docFindWithSelf("a[ng-action]").live('click', function (event) { - var jNode = jQuery(this); - var scope = jNode.scope(); - try { - scope.eval(jNode.attr('ng-action')); - jNode.removeAttr('ng-error'); - jNode.removeClass("ng-exception"); - } catch (e) { - jNode.addClass("ng-exception"); - jNode.attr('ng-error', toJson(e, true)); - } - scope.get('$updateView')(); - return false; - }); - }, - - translateBinding: function(node, parentPath, factories) { - var path = parentPath.concat(); - var offset = path.pop(); - var parts = Binder.parseBindings(node.nodeValue); - if (parts.length > 1 || Binder.binding(parts[0])) { - var parent = node.parentNode; - if (isLeafNode(parent)) { - parent.setAttribute('ng-bind-template', node.nodeValue); - factories.push({path:path, fn:function(node, scope, prefix) { - return new BindUpdater(node, node.getAttribute('ng-bind-template')); - }}); - } else { - for (var i = 0; i < parts.length; i++) { - var part = parts[i]; - var binding = Binder.binding(part); - var newNode; - if (binding) { - newNode = document.createElement("span"); - var jNewNode = jQuery(newNode); - jNewNode.attr("ng-bind", binding); - if (i === 0) { - factories.push({path:path.concat(offset + i), fn:this.ng_bind}); - } - } else if (msie && part.charAt(0) == ' ') { - newNode = document.createElement("span"); - newNode.innerHTML = ' ' + part.substring(1); - } else { - newNode = document.createTextNode(part); - } - parent.insertBefore(newNode, node); - } - } - parent.removeChild(node); - } - }, - - precompile: function(root) { - var factories = []; - this.precompileNode(root, [], factories); - return function (template, scope, prefix) { - var len = factories.length; - for (var i = 0; i < len; i++) { - var factory = factories[i]; - var node = template; - var path = factory.path; - for (var j = 0; j < path.length; j++) { - node = node.childNodes[path[j]]; - } - try { - scope.addWidget(factory.fn(node, scope, prefix)); - } catch (e) { - alert(e); - } - } - }; - }, - - precompileNode: function(node, path, factories) { - var nodeType = node.nodeType; - if (nodeType == Node.TEXT_NODE) { - this.translateBinding(node, path, factories); - return; - } else if (nodeType != Node.ELEMENT_NODE && nodeType != Node.DOCUMENT_NODE) { - return; - } - - if (!node.getAttribute) return; - var nonBindable = node.getAttribute('ng-non-bindable'); - if (nonBindable || nonBindable === "") return; - - var attributes = node.attributes; - if (attributes) { - var bindings = node.getAttribute('ng-bind-attr'); - node.removeAttribute('ng-bind-attr'); - bindings = bindings ? fromJson(bindings) : {}; - var attrLen = attributes.length; - for (var i = 0; i < attrLen; i++) { - var attr = attributes[i]; - var attrName = attr.name; - // http://www.glennjones.net/Post/809/getAttributehrefbug.htm - var attrValue = msie && attrName == 'href' ? - decodeURI(node.getAttribute(attrName, 2)) : attr.value; - if (Binder.hasBinding(attrValue)) { - bindings[attrName] = attrValue; - } - } - var json = toJson(bindings); - if (json.length > 2) { - node.setAttribute("ng-bind-attr", json); - } - } - - if (!node.getAttribute) log(node); - var repeaterExpression = node.getAttribute('ng-repeat'); - if (repeaterExpression) { - node.removeAttribute('ng-repeat'); - var precompiled = this.precompile(node); - var view = document.createComment("ng-repeat: " + repeaterExpression); - var parentNode = node.parentNode; - parentNode.insertBefore(view, node); - parentNode.removeChild(node); - function template(childScope, prefix, i) { - var clone = jQuery(node).clone(); - clone.css('display', ''); - clone.attr('ng-repeat-index', "" + i); - clone.data('scope', childScope); - precompiled(clone[0], childScope, prefix + i + ":"); - return clone; - } - factories.push({path:path, fn:function(node, scope, prefix) { - return new RepeaterUpdater(jQuery(node), repeaterExpression, template, prefix); - }}); - return; - } - - if (node.getAttribute('ng-eval')) factories.push({path:path, fn:this.ng_eval}); - if (node.getAttribute('ng-bind')) factories.push({path:path, fn:this.ng_bind}); - if (node.getAttribute('ng-bind-attr')) factories.push({path:path, fn:this.ng_bind_attr}); - if (node.getAttribute('ng-hide')) factories.push({path:path, fn:this.ng_hide}); - if (node.getAttribute('ng-show')) factories.push({path:path, fn:this.ng_show}); - if (node.getAttribute('ng-class')) factories.push({path:path, fn:this.ng_class}); - if (node.getAttribute('ng-class-odd')) factories.push({path:path, fn:this.ng_class_odd}); - if (node.getAttribute('ng-class-even')) factories.push({path:path, fn:this.ng_class_even}); - if (node.getAttribute('ng-style')) factories.push({path:path, fn:this.ng_style}); - if (node.getAttribute('ng-watch')) factories.push({path:path, fn:this.ng_watch}); - var nodeName = node.nodeName; - if ((nodeName == 'INPUT' ) || - nodeName == 'TEXTAREA' || - nodeName == 'SELECT' || - nodeName == 'BUTTON') { - var self = this; - factories.push({path:path, fn:function(node, scope, prefix) { - node.name = prefix + node.name.split(":").pop(); - return self.widgetFactory.createController(jQuery(node), scope); - }}); - } - if (nodeName == 'OPTION') { - var html = jQuery('' + - '' + - '' + - '' + - ''); -}; - -extend(FileController.prototype, { - 'cancel': noop, - 'complete': noop, - 'httpStatus': function(status) { - alert("httpStatus:" + this.scopeName + " status:" + status); - }, - 'ioError': function() { - alert("ioError:" + this.scopeName); - }, - 'open': function() { - alert("open:" + this.scopeName); - }, - 'progress':noop, - 'securityError': function() { - alert("securityError:" + this.scopeName); - }, - 'uploadCompleteData': function(data) { - var value = fromJson(data); - value.url = this.attachmentsPath + '/' + value.id + '/' + value.text; - this.view.find("input").attr('checked', true); - var scope = this.view.scope(); - this.value = value; - this.updateModel(scope); - this.value = null; - }, - 'select': function(name, size, type) { - this.name = name; - this.view.find("a").text(name).attr('href', name); - this.view.find("span").text(angular['filter']['bytes'](size)); - this.upload(); - }, - - updateModel: function(scope) { - var isChecked = this.view.find("input").attr('checked'); - var value = isChecked ? this.value : null; - if (this.lastValue === value) { - return false; - } else { - scope.set(this.scopeName, value); - return true; - } - }, - - updateView: function(scope) { - var modelValue = scope.get(this.scopeName); - if (modelValue && this.value !== modelValue) { - this.value = modelValue; - this.view.find("a"). - attr("href", this.value.url). - text(this.value.text); - this.view.find("span").text(angular['filter']['bytes'](this.value.size)); - } - this.view.find("input").attr('checked', !!modelValue); - }, - - upload: function() { - if (this.name) { - this.uploader['uploadFile'](this.attachmentsPath); - } - } -}); - -/////////////////////// -// NullController -/////////////////////// -function NullController(view) {this.view = view;}; -NullController.prototype = { - updateModel: function() { return true; }, - updateView: noop -}; -NullController.instance = new NullController(); - - -/////////////////////// -// ButtonController -/////////////////////// -var ButtonController = NullController; - -/////////////////////// -// TextController -/////////////////////// -function TextController(view, exp, formatter) { - this.view = view; - this.formatter = formatter; - this.exp = exp; - this.validator = view.getAttribute('ng-validate'); - this.required = typeof view.attributes['ng-required'] != "undefined"; - this.lastErrorText = null; - this.lastValue = undefined; - this.initialValue = this.formatter['parse'](view.value); - var widget = view.getAttribute('ng-widget'); - if (widget === 'datepicker') { - jQuery(view).datepicker(); - } -}; - -TextController.prototype = { - updateModel: function(scope) { - var value = this.formatter['parse'](this.view.value); - if (this.lastValue === value) { - return false; - } else { - scope.setEval(this.exp, value); - this.lastValue = value; - return true; - } - }, - - updateView: function(scope) { - var view = this.view; - var value = scope.get(this.exp); - if (typeof value === "undefined") { - value = this.initialValue; - scope.setEval(this.exp, value); - } - value = value ? value : ''; - if (!_(this.lastValue).isEqual(value)) { - view.value = this.formatter['format'](value); - this.lastValue = value; - } - - var isValidationError = false; - view.removeAttribute('ng-error'); - if (this.required) { - isValidationError = !(value && $.trim("" + value).length > 0); - } - var errorText = isValidationError ? "Required Value" : null; - if (!isValidationError && this.validator && value) { - errorText = scope.validate(this.validator, value, view); - isValidationError = !!errorText; - } - if (this.lastErrorText !== errorText) { - this.lastErrorText = isValidationError; - if (errorText && isVisible(view)) { - view.setAttribute('ng-error', errorText); - scope.markInvalid(this); - } - jQuery(view).toggleClass('ng-validation-error', isValidationError); - } - } -}; - -/////////////////////// -// CheckboxController -/////////////////////// -function CheckboxController(view, exp, formatter) { - this.view = view; - this.exp = exp; - this.lastValue = undefined; - this.formatter = formatter; - this.initialValue = this.formatter['parse'](view.checked ? view.value : ""); -}; - -CheckboxController.prototype = { - updateModel: function(scope) { - var input = this.view; - var value = input.checked ? input.value : ''; - value = this.formatter['parse'](value); - value = this.formatter['format'](value); - if (this.lastValue === value) { - return false; - } else { - scope.setEval(this.exp, this.formatter['parse'](value)); - this.lastValue = value; - return true; - } - }, - - updateView: function(scope) { - var input = this.view; - var value = scope.eval(this.exp); - if (typeof value === "undefined") { - value = this.initialValue; - scope.setEval(this.exp, value); - } - input.checked = this.formatter['parse'](input.value) == value; - } -}; - -/////////////////////// -// SelectController -/////////////////////// -function SelectController(view, exp) { - this.view = view; - this.exp = exp; - this.lastValue = undefined; - this.initialValue = view.value; -}; - -SelectController.prototype = { - updateModel: function(scope) { - var input = this.view; - if (input.selectedIndex < 0) { - scope.setEval(this.exp, null); - } else { - var value = this.view.value; - if (this.lastValue === value) { - return false; +function modelAccessor(scope, element) { + var expr = element.attr('name'), + farmatterName = element.attr('ng-format') || NOOP, + formatter = angularFormatter(farmatterName); + if (!expr) throw "Required field 'name' not found."; + if (!formatter) throw "Formatter named '" + farmatterName + "' not found."; + return { + get: function() { + return formatter['format'](scope.$eval(expr)); + }, + set: function(value) { + scope.$eval(expr + '=' + toJson(formatter['parse'](value))); + } + }; +} + +function compileValidator(expr) { + return new Parser(expr).validator()(); +} + +function valueAccessor(element) { + var validatorName = element.attr('ng-validate') || NOOP, + validator = compileValidator(validatorName), + required = element.attr('ng-required'), + lastError; + required = required || required == ''; + if (!validator) throw "Validator named '" + validatorName + "' not found."; + function validate(value) { + var error = required && !trim(value) ? "Required" : validator.call(this, value); + if (error !== lastError) { + if (error) { + element.addClass(NG_VALIDATION_ERROR); + element.attr(NG_ERROR, error); } else { - scope.setEval(this.exp, value); - this.lastValue = value; - return true; - } - } - }, - - updateView: function(scope) { - var input = this.view; - var value = scope.get(this.exp); - if (typeof value === 'undefined') { - value = this.initialValue; - scope.setEval(this.exp, value); - } - if (value !== this.lastValue) { - input.value = value ? value : ""; - this.lastValue = value; - } - } -}; - -/////////////////////// -// MultiSelectController -/////////////////////// -function MultiSelectController(view, exp) { - this.view = view; - this.exp = exp; - this.lastValue = undefined; - this.initialValue = this.selected(); -}; - -MultiSelectController.prototype = { - selected: function () { - var value = []; - var options = this.view.options; - for ( var i = 0; i < options.length; i++) { - var option = options[i]; - if (option.selected) { - value.push(option.value); + element.removeClass(NG_VALIDATION_ERROR); + element.removeAttr(NG_ERROR); } + lastError = error; } return value; - }, - - updateModel: function(scope) { - var value = this.selected(); - // TODO: This is wrong! no caching going on here as we are always comparing arrays - if (this.lastValue === value) { - return false; - } else { - scope.setEval(this.exp, value); - this.lastValue = value; - return true; - } - }, - - updateView: function(scope) { - var input = this.view; - var selected = scope.get(this.exp); - if (typeof selected === "undefined") { - selected = this.initialValue; - scope.setEval(this.exp, selected); - } - if (selected !== this.lastValue) { - var options = input.options; - for ( var i = 0; i < options.length; i++) { - var option = options[i]; - option.selected = _.include(selected, option.value); - } - this.lastValue = selected; - } - } -}; - -/////////////////////// -// RadioController -/////////////////////// -function RadioController(view, exp) { - this.view = view; - this.exp = exp; - this.lastChecked = undefined; - this.lastValue = undefined; - this.inputValue = view.value; - this.initialValue = view.checked ? view.value : null; -}; - -RadioController.prototype = { - updateModel: function(scope) { - var input = this.view; - if (this.lastChecked) { - return false; - } else { - input.checked = true; - this.lastValue = scope.setEval(this.exp, this.inputValue); - this.lastChecked = true; - return true; - } - }, - - updateView: function(scope) { - var input = this.view; - var value = scope.get(this.exp); - if (this.initialValue && typeof value === "undefined") { - value = this.initialValue; - scope.setEval(this.exp, value); - } - if (this.lastValue != value) { - this.lastChecked = input.checked = this.inputValue == (''+value); - this.lastValue = value; - } - } -}; - -/////////////////////// -//ElementController -/////////////////////// -function BindUpdater(view, exp) { - this.view = view; - this.exp = Binder.parseBindings(exp); - this.hasError = false; -}; - -BindUpdater.toText = function(obj) { - var e = escapeHtml; - switch(typeof obj) { - case "string": - case "boolean": - case "number": - return e(obj); - case "function": - return BindUpdater.toText(obj()); - case "object": - if (isNode(obj)) { - return outerHTML(obj); - } else if (obj instanceof angular.filter.Meta) { - switch(typeof obj.html) { - case "string": - case "number": - return obj.html; - case "function": - return obj.html(); - case "object": - if (isNode(obj.html)) - return outerHTML(obj.html); - default: - break; - } - switch(typeof obj.text) { - case "string": - case "number": - return e(obj.text); - case "function": - return e(obj.text()); - default: - break; - } - } - if (obj === null) - return ""; - return e(toJson(obj, true)); - default: - return ""; - } -}; - -BindUpdater.prototype = { - updateModel: noop, - updateView: function(scope) { - var html = []; - var parts = this.exp; - var length = parts.length; - for(var i=0; i iteratorCounter; --r) { - this.children.pop().element.remove(); - } - // Special case for option in select - if (child && child.element[0].nodeName === "OPTION") { - var select = jQuery(child.element[0].parentNode); - var cntl = select.data('controller'); - if (cntl) { - cntl.lastValue = undefined; - cntl.updateView(scope); - } - } - }); - } -}; - -////////////////////////////////// -// PopUp -////////////////////////////////// - -function PopUp(doc) { - this.doc = doc; -}; - -PopUp.OUT_EVENT = "mouseleave mouseout click dblclick keypress keyup"; - -PopUp.onOver = function(e) { - PopUp.onOut(); - var jNode = jQuery(this); - jNode.bind(PopUp.OUT_EVENT, PopUp.onOut); - var position = jNode.position(); - var de = document.documentElement; - var w = self.innerWidth || (de&&de.clientWidth) || document.body.clientWidth; - var hasArea = w - position.left; - var width = 300; - var title = jNode.hasClass("ng-exception") ? "EXCEPTION:" : "Validation error..."; - var msg = jNode.attr("ng-error"); - - var x; - var arrowPos = hasArea>(width+75) ? "left" : "right"; - var tip = jQuery( - "
        " + - "
        " + - "
        "+title+"
        " + - "
        "+msg+"
        " + - "
        "); - jQuery("body").append(tip); - if(arrowPos === 'left'){ - x = position.left + this.offsetWidth + 11; - }else{ - x = position.left - (width + 15); - tip.find('.ng-arrow-right').css({left:width+1}); - } - - tip.css({left: x+"px", top: (position.top - 3)+"px"}); - return true; -}; - -PopUp.onOut = function() { - jQuery('#ng-callout'). - unbind(PopUp.OUT_EVENT, PopUp.onOut). - remove(); - return true; -}; - -PopUp.prototype = { - bind: function () { - var self = this; - this.doc.find('.ng-validation-error,.ng-exception'). - live("mouseover", PopUp.onOver); - } -}; - -////////////////////////////////// -// Status -////////////////////////////////// - -function NullStatus(body) { -}; - -NullStatus.prototype = { - beginRequest:function(){}, - endRequest:function(){} -}; - -function Status(body) { - this.requestCount = 0; - this.body = body; -}; - -Status.DOM ='
        loading....
        '; - -Status.prototype = { - beginRequest: function () { - if (this.requestCount === 0) { - (this.loader = this.loader || this.body.append(Status.DOM).find("#ng-loading")).show(); } - this.requestCount++; - }, + }; +} + +function noopAccessor() { return { get: noop, set: noop }; } + +var NG_ERROR = 'ng-error', + NG_VALIDATION_ERROR = 'ng-validation-error', + textWidget = inputWidget('keyup change', modelAccessor, valueAccessor, ''), + buttonWidget = inputWidget('click', noopAccessor, noopAccessor, undefined), + INPUT_TYPE = { + 'text': textWidget, + 'textarea': textWidget, + 'hidden': textWidget, + 'password': textWidget, + 'button': buttonWidget, + 'submit': buttonWidget, + 'reset': buttonWidget, + 'image': buttonWidget, + 'checkbox': inputWidget('click', modelAccessor, checkedAccessor, false), + 'radio': inputWidget('click', modelAccessor, radioAccessor, undefined), + 'select-one': inputWidget('click', modelAccessor, valueAccessor, null), + 'select-multiple': inputWidget('click', modelAccessor, optionsAccessor, []) +// 'file': fileWidget??? + }; - endRequest: function () { - this.requestCount--; - if (this.requestCount === 0) { - this.loader.hide("fold"); - } - } -}; +function inputWidget(events, modelAccessor, viewAccessor, initValue) { + return function(element) { + var scope = this, + model = modelAccessor(scope, element), + view = viewAccessor(element), + action = element.attr('ng-action') || '', + value = view.get() || copy(initValue); + if (isDefined(value)) model.set(value); + this.$eval(element.attr('ng-init')||''); + element.bind(events, function(){ + model.set(view.get()); + scope.$tryEval(action, element); + scope.$root.$eval(); + // if we have no initValue than we are just a button, + // therefore we want to prevent default action + return isDefined(initValue); + }); + scope.$watch(model.get, view.set); + }; +} + +function inputWidgetSelector(element){ + return INPUT_TYPE[lowercase(element[0].type)] || noop; +} + +angularWidget('INPUT', inputWidgetSelector); +angularWidget('TEXTAREA', inputWidgetSelector); +angularWidget('BUTTON', inputWidgetSelector); +angularWidget('SELECT', function(element){ + this.descend(true); + return inputWidgetSelector.call(this, element); +}); diff --git a/src/angular-bootstrap.js b/src/angular-bootstrap.js index d2b2ff9c..7798afa5 100644 --- a/src/angular-bootstrap.js +++ b/src/angular-bootstrap.js @@ -1,18 +1,18 @@ /** * The MIT License - * + * * Copyright (c) 2010 Adam Abrons and Misko Hevery http://getangular.com - * + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - * + * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -22,35 +22,58 @@ * THE SOFTWARE. */ (function(previousOnLoad){ - var filename = /(.*)\/angular-(.*).js/; - var scripts = document.getElementsByTagName("script"); + var filename = /(.*)\/angular-(.*).js(#(.*))?/; + var scripts = document.getElementsByTagName("SCRIPT"); var serverPath; + var config = {}; for(var j = 0; j < scripts.length; j++) { var match = (scripts[j].src || "").match(filename); if (match) { serverPath = match[1]; + parseConfig(match[4]); + } + } + + function parseConfig(args) { + var keyValues = args.split('&'), keyValue, i = 0; + for (; i < keyValues.length; i++) { + keyValue = keyValues[i].split('='); + config[keyValue[0]] = keyValue[1] || true; } } function addScript(file){ document.write(''); - }; + } addScript("/Angular.js"); - addScript("/API.js"); - addScript("/Binder.js"); - addScript("/ControlBar.js"); - addScript("/DataStore.js"); - addScript("/Filters.js"); - addScript("/Formatters.js"); addScript("/JSON.js"); - addScript("/Model.js"); + addScript("/Compiler.js"); + addScript("/Scope.js"); + addScript("/jqlite.js"); addScript("/Parser.js"); addScript("/Resource.js"); - addScript("/Scope.js"); - addScript("/Server.js"); - addScript("/Users.js"); - addScript("/Validators.js"); - addScript("/Widgets.js"); + addScript("/URLWatcher.js"); + + // Extension points + addScript("/apis.js"); + addScript("/filters.js"); + addScript("/formatters.js"); + addScript("/validators.js"); + addScript("/directives.js"); + addScript("/markups.js"); + addScript("/widgets.js"); + + if (config.autobind) { + window.onload = function(){ + try { + if (previousOnLoad) previousOnLoad(); + } catch(e) {} + var scope = angular.compile(window.document, config); + if (config.rootScope) window[config.rootScope] = scope; + scope.$init(); + }; + } + })(window.onload); diff --git a/src/apis.js b/src/apis.js new file mode 100644 index 00000000..e375e8fc --- /dev/null +++ b/src/apis.js @@ -0,0 +1,328 @@ +var angularGlobal = { + 'typeOf':function(obj){ + if (obj === null) return "null"; + var type = typeof obj; + if (type == "object") { + if (obj instanceof Array) return "array"; + if (obj instanceof Date) return "date"; + if (obj.nodeType == 1) return "element"; + } + return type; + } +}; + +var angularCollection = {}; +var angularObject = {}; +var angularArray = { + 'includeIf':function(array, value, condition) { + var index = _.indexOf(array, value); + if (condition) { + if (index == -1) + array.push(value); + } else { + array.splice(index, 1); + } + return array; + }, + 'sum':function(array, expression) { + var fn = angular['Function']['compile'](expression); + var sum = 0; + for (var i = 0; i < array.length; i++) { + var value = 1 * fn(array[i]); + if (!isNaN(value)){ + sum += value; + } + } + return sum; + }, + 'remove':function(array, value) { + var index = _.indexOf(array, value); + if (index >=0) + array.splice(index, 1); + return value; + }, + 'find':function(array, condition, defaultValue) { + if (!condition) return undefined; + var fn = angular['Function']['compile'](condition); + _.detect(array, function($){ + if (fn($)){ + defaultValue = $; + return true; + } + }); + return defaultValue; + }, + 'findById':function(array, id) { + return angular.Array.find(array, function($){return $.$id == id;}, null); + }, + 'filter':function(array, expression) { + var predicates = []; + predicates.check = function(value) { + for (var j = 0; j < predicates.length; j++) { + if(!predicates[j](value)) { + return false; + } + } + return true; + }; + var getter = Scope.getter; + var search = function(obj, text){ + if (text.charAt(0) === '!') { + return !search(obj, text.substr(1)); + } + switch (typeof obj) { + case "boolean": + case "number": + case "string": + return ('' + obj).toLowerCase().indexOf(text) > -1; + case "object": + for ( var objKey in obj) { + if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) { + return true; + } + } + return false; + case "array": + for ( var i = 0; i < obj.length; i++) { + if (search(obj[i], text)) { + return true; + } + } + return false; + default: + return false; + } + }; + switch (typeof expression) { + case "boolean": + case "number": + case "string": + expression = {$:expression}; + case "object": + for (var key in expression) { + if (key == '$') { + (function(){ + var text = (''+expression[key]).toLowerCase(); + if (!text) return; + predicates.push(function(value) { + return search(value, text); + }); + })(); + } else { + (function(){ + var path = key; + var text = (''+expression[key]).toLowerCase(); + if (!text) return; + predicates.push(function(value) { + return search(getter(value, path), text); + }); + })(); + } + } + break; + case "function": + predicates.push(expression); + break; + default: + return array; + } + var filtered = []; + for ( var j = 0; j < array.length; j++) { + var value = array[j]; + if (predicates.check(value)) { + filtered.push(value); + } + } + return filtered; + }, + 'add':function(array, value) { + array.push(_.isUndefined(value)? {} : value); + return array; + }, + 'count':function(array, condition) { + if (!condition) return array.length; + var fn = angular['Function']['compile'](condition); + return _.reduce(array, 0, function(count, $){return count + (fn($)?1:0);}); + }, + 'orderBy':function(array, expression, descend) { + function reverse(comp, descending) { + return toBoolean(descending) ? + function(a,b){return comp(b,a);} : comp; + } + function compare(v1, v2){ + var t1 = typeof v1; + var t2 = typeof v2; + if (t1 == t2) { + if (t1 == "string") v1 = v1.toLowerCase(); + if (t1 == "string") v2 = v2.toLowerCase(); + if (v1 === v2) return 0; + return v1 < v2 ? -1 : 1; + } else { + return t1 < t2 ? -1 : 1; + } + } + expression = _.isArray(expression) ? expression: [expression]; + expression = _.map(expression, function($){ + var descending = false; + if (typeof $ == "string" && ($.charAt(0) == '+' || $.charAt(0) == '-')) { + descending = $.charAt(0) == '-'; + $ = $.substring(1); + } + var get = $ ? angular['Function']['compile']($) : _.identity; + return reverse(function(a,b){ + return compare(get(a),get(b)); + }, descending); + }); + var comparator = function(o1, o2){ + for ( var i = 0; i < expression.length; i++) { + var comp = expression[i](o1, o2); + if (comp !== 0) return comp; + } + return 0; + }; + return _.clone(array).sort(reverse(comparator, descend)); + }, + 'orderByToggle':function(predicate, attribute) { + var STRIP = /^([+|-])?(.*)/; + var ascending = false; + var index = -1; + _.detect(predicate, function($, i){ + if ($ == attribute) { + ascending = true; + index = i; + return true; + } + if (($.charAt(0)=='+'||$.charAt(0)=='-') && $.substring(1) == attribute) { + ascending = $.charAt(0) == '+'; + index = i; + return true; + } + }); + if (index >= 0) { + predicate.splice(index, 1); + } + predicate.unshift((ascending ? "-" : "+") + attribute); + return predicate; + }, + 'orderByDirection':function(predicate, attribute, ascend, descend) { + ascend = ascend || 'ng-ascend'; + descend = descend || 'ng-descend'; + var att = predicate[0] || ''; + var direction = true; + if (att.charAt(0) == '-') { + att = att.substring(1); + direction = false; + } else if(att.charAt(0) == '+') { + att = att.substring(1); + } + return att == attribute ? (direction ? ascend : descend) : ""; + }, + 'merge':function(array, index, mergeValue) { + var value = array[index]; + if (!value) { + value = {}; + array[index] = value; + } + merge(mergeValue, value); + return array; + } +}; + +var angularString = { + 'quote':function(string) { + return '"' + string.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(string) { + var str = angular['String']['quote'](string); + var chars = []; + for ( var i = 0; i < str.length; i++) { + var ch = str.charCodeAt(i); + if (ch < 128) { + chars.push(str.charAt(i)); + } else { + var encode = "000" + ch.toString(16); + chars.push("\\u" + encode.substring(encode.length - 4)); + } + } + return chars.join(''); + }, + 'toDate':function(string){ + var match; + if (typeof string == 'string' && + (match = string.match(/^(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)Z$/))){ + var date = new Date(0); + date.setUTCFullYear(match[1], match[2] - 1, match[3]); + date.setUTCHours(match[4], match[5], match[6], 0); + return date; + } + return string; + } +}; + +var angularDate = { + 'toString':function(date){ + function pad(n) { return n < 10 ? "0" + n : n; } + return !date ? date : + date.getUTCFullYear() + '-' + + pad(date.getUTCMonth() + 1) + '-' + + pad(date.getUTCDate()) + 'T' + + pad(date.getUTCHours()) + ':' + + pad(date.getUTCMinutes()) + ':' + + pad(date.getUTCSeconds()) + 'Z' ; + } + }; + +var angularFunction = { + 'compile':function(expression) { + if (_.isFunction(expression)){ + return expression; + } else if (expression){ + var scope = new Scope(); + return function($) { + scope.state = $; + return scope.eval(expression); + }; + } else { + return function($){return $;}; + } + } +}; + +function defineApi(dst, chain, underscoreNames){ + if (_) { + var lastChain = _.last(chain); + foreach(underscoreNames, function(name){ + lastChain[name] = _[name]; + }); + } + angular[dst] = angular[dst] || {}; + foreach(chain, function(parent){ + extend(angular[dst], parent); + }); +} +defineApi('Global', [angularGlobal], + ['extend', 'clone','isEqual', + 'isElement', 'isArray', 'isFunction', 'isUndefined']); +defineApi('Collection', [angularGlobal, angularCollection], + ['each', 'map', 'reduce', 'reduceRight', 'detect', + 'select', 'reject', 'all', 'any', 'include', + 'invoke', 'pluck', 'max', 'min', 'sortBy', + 'sortedIndex', 'toArray', 'size']); +defineApi('Array', [angularGlobal, angularCollection, angularArray], + ['first', 'last', 'compact', 'flatten', 'without', + 'uniq', 'intersect', 'zip', 'indexOf', 'lastIndexOf']); +defineApi('Object', [angularGlobal, angularCollection, angularObject], + ['keys', 'values']); +defineApi('String', [angularGlobal, angularString], []); +defineApi('Date', [angularGlobal, angularDate], []); +//IE bug +angular['Date']['toString'] = angularDate['toString']; +defineApi('Function', [angularGlobal, angularCollection, angularFunction], + ['bind', 'bindAll', 'delay', 'defer', 'wrap', 'compose']); diff --git a/src/delete/Binder.js b/src/delete/Binder.js new file mode 100644 index 00000000..9fc32513 --- /dev/null +++ b/src/delete/Binder.js @@ -0,0 +1,356 @@ +function Binder(doc, widgetFactory, datastore, location, config) { + this.doc = doc; + this.location = location; + this.datastore = datastore; + this.anchor = {}; + this.widgetFactory = widgetFactory; + this.config = config || {}; + this.updateListeners = []; +} + +Binder.parseBindings = function(string) { + var results = []; + var lastIndex = 0; + var index; + while((index = string.indexOf('{{', lastIndex)) > -1) { + if (lastIndex < index) + results.push(string.substr(lastIndex, index - lastIndex)); + lastIndex = index; + + index = string.indexOf('}}', index); + index = index < 0 ? string.length : index + 2; + + results.push(string.substr(lastIndex, index - lastIndex)); + lastIndex = index; + } + if (lastIndex != string.length) + results.push(string.substr(lastIndex, string.length - lastIndex)); + return results.length === 0 ? [ string ] : results; +}; + +Binder.hasBinding = function(string) { + var bindings = Binder.parseBindings(string); + return bindings.length > 1 || Binder.binding(bindings[0]) !== null; +}; + +Binder.binding = function(string) { + var binding = string.replace(/\n/gm, ' ').match(/^\{\{(.*)\}\}$/); + return binding ? binding[1] : null; +}; + + +Binder.prototype = { + parseQueryString: function(query) { + var params = {}; + query.replace(/(?:^|&)([^&=]*)=?([^&]*)/g, + function (match, left, right) { + if (left) params[decodeURIComponent(left)] = decodeURIComponent(right); + }); + return params; + }, + + parseAnchor: function() { + var self = this, url = this.location['get']() || ""; + + var anchorIndex = url.indexOf('#'); + if (anchorIndex < 0) return; + var anchor = url.substring(anchorIndex + 1); + + var anchorQuery = this.parseQueryString(anchor); + foreach(self.anchor, function(newValue, key) { + delete self.anchor[key]; + }); + foreach(anchorQuery, function(newValue, key) { + self.anchor[key] = newValue; + }); + }, + + onUrlChange: function() { + this.parseAnchor(); + this.updateView(); + }, + + updateAnchor: function() { + var url = this.location['get']() || ""; + var anchorIndex = url.indexOf('#'); + if (anchorIndex > -1) + url = url.substring(0, anchorIndex); + url += "#"; + var sep = ''; + for (var key in this.anchor) { + var value = this.anchor[key]; + if (typeof value === 'undefined' || value === null) { + delete this.anchor[key]; + } else { + url += sep + encodeURIComponent(key); + if (value !== true) + url += "=" + encodeURIComponent(value); + sep = '&'; + } + } + this.location['set'](url); + return url; + }, + + updateView: function() { + var start = new Date().getTime(); + var scope = jQuery(this.doc).scope(); + scope.clearInvalid(); + scope.updateView(); + var end = new Date().getTime(); + this.updateAnchor(); + foreach(this.updateListeners, function(fn) {fn();}); + }, + + docFindWithSelf: function(exp){ + var doc = jQuery(this.doc); + var selection = doc.find(exp); + if (doc.is(exp)){ + selection = selection.andSelf(); + } + return selection; + }, + + executeInit: function() { + this.docFindWithSelf("[ng-init]").each(function() { + var jThis = jQuery(this); + var scope = jThis.scope(); + try { + scope.eval(jThis.attr('ng-init')); + } catch (e) { + alert("EVAL ERROR:\n" + jThis.attr('ng-init') + '\n' + toJson(e, true)); + } + }); + }, + + entity: function (scope) { + var self = this; + this.docFindWithSelf("[ng-entity]").attr("ng-watch", function() { + try { + var jNode = jQuery(this); + var decl = scope.entity(jNode.attr("ng-entity"), self.datastore); + return decl + (jNode.attr('ng-watch') || ""); + } catch (e) { + log(e); + alert(e); + } + }); + }, + + compile: function() { + var jNode = jQuery(this.doc); + if (this.config['autoSubmit']) { + var submits = this.docFindWithSelf(":submit").not("[ng-action]"); + submits.attr("ng-action", "$save()"); + submits.not(":disabled").not("ng-bind-attr").attr("ng-bind-attr", '{disabled:"{{$invalidWidgets}}"}'); + } + this.precompile(this.doc)(this.doc, jNode.scope(), ""); + this.docFindWithSelf("a[ng-action]").live('click', function (event) { + var jNode = jQuery(this); + var scope = jNode.scope(); + try { + scope.eval(jNode.attr('ng-action')); + jNode.removeAttr('ng-error'); + jNode.removeClass("ng-exception"); + } catch (e) { + jNode.addClass("ng-exception"); + jNode.attr('ng-error', toJson(e, true)); + } + scope.get('$updateView')(); + return false; + }); + }, + + translateBinding: function(node, parentPath, factories) { + var path = parentPath.concat(); + var offset = path.pop(); + var parts = Binder.parseBindings(node.nodeValue); + if (parts.length > 1 || Binder.binding(parts[0])) { + var parent = node.parentNode; + if (isLeafNode(parent)) { + parent.setAttribute('ng-bind-template', node.nodeValue); + factories.push({path:path, fn:function(node, scope, prefix) { + return new BindUpdater(node, node.getAttribute('ng-bind-template')); + }}); + } else { + for (var i = 0; i < parts.length; i++) { + var part = parts[i]; + var binding = Binder.binding(part); + var newNode; + if (binding) { + newNode = document.createElement("span"); + var jNewNode = jQuery(newNode); + jNewNode.attr("ng-bind", binding); + if (i === 0) { + factories.push({path:path.concat(offset + i), fn:this.ng_bind}); + } + } else if (msie && part.charAt(0) == ' ') { + newNode = document.createElement("span"); + newNode.innerHTML = ' ' + part.substring(1); + } else { + newNode = document.createTextNode(part); + } + parent.insertBefore(newNode, node); + } + } + parent.removeChild(node); + } + }, + + precompile: function(root) { + var factories = []; + this.precompileNode(root, [], factories); + return function (template, scope, prefix) { + var len = factories.length; + for (var i = 0; i < len; i++) { + var factory = factories[i]; + var node = template; + var path = factory.path; + for (var j = 0; j < path.length; j++) { + node = node.childNodes[path[j]]; + } + try { + scope.addWidget(factory.fn(node, scope, prefix)); + } catch (e) { + alert(e); + } + } + }; + }, + + precompileNode: function(node, path, factories) { + var nodeType = node.nodeType; + if (nodeType == Node.TEXT_NODE) { + this.translateBinding(node, path, factories); + return; + } else if (nodeType != Node.ELEMENT_NODE && nodeType != Node.DOCUMENT_NODE) { + return; + } + + if (!node.getAttribute) return; + var nonBindable = node.getAttribute('ng-non-bindable'); + if (nonBindable || nonBindable === "") return; + + var attributes = node.attributes; + if (attributes) { + var bindings = node.getAttribute('ng-bind-attr'); + node.removeAttribute('ng-bind-attr'); + bindings = bindings ? fromJson(bindings) : {}; + var attrLen = attributes.length; + for (var i = 0; i < attrLen; i++) { + var attr = attributes[i]; + var attrName = attr.name; + // http://www.glennjones.net/Post/809/getAttributehrefbug.htm + var attrValue = msie && attrName == 'href' ? + decodeURI(node.getAttribute(attrName, 2)) : attr.value; + if (Binder.hasBinding(attrValue)) { + bindings[attrName] = attrValue; + } + } + var json = toJson(bindings); + if (json.length > 2) { + node.setAttribute("ng-bind-attr", json); + } + } + + if (!node.getAttribute) log(node); + var repeaterExpression = node.getAttribute('ng-repeat'); + if (repeaterExpression) { + node.removeAttribute('ng-repeat'); + var precompiled = this.precompile(node); + var view = document.createComment("ng-repeat: " + repeaterExpression); + var parentNode = node.parentNode; + parentNode.insertBefore(view, node); + parentNode.removeChild(node); + function template(childScope, prefix, i) { + var clone = jQuery(node).clone(); + clone.css('display', ''); + clone.attr('ng-repeat-index', "" + i); + clone.data('scope', childScope); + precompiled(clone[0], childScope, prefix + i + ":"); + return clone; + } + factories.push({path:path, fn:function(node, scope, prefix) { + return new RepeaterUpdater(jQuery(node), repeaterExpression, template, prefix); + }}); + return; + } + + if (node.getAttribute('ng-eval')) factories.push({path:path, fn:this.ng_eval}); + if (node.getAttribute('ng-bind')) factories.push({path:path, fn:this.ng_bind}); + if (node.getAttribute('ng-bind-attr')) factories.push({path:path, fn:this.ng_bind_attr}); + if (node.getAttribute('ng-hide')) factories.push({path:path, fn:this.ng_hide}); + if (node.getAttribute('ng-show')) factories.push({path:path, fn:this.ng_show}); + if (node.getAttribute('ng-class')) factories.push({path:path, fn:this.ng_class}); + if (node.getAttribute('ng-class-odd')) factories.push({path:path, fn:this.ng_class_odd}); + if (node.getAttribute('ng-class-even')) factories.push({path:path, fn:this.ng_class_even}); + if (node.getAttribute('ng-style')) factories.push({path:path, fn:this.ng_style}); + if (node.getAttribute('ng-watch')) factories.push({path:path, fn:this.ng_watch}); + var nodeName = node.nodeName; + if ((nodeName == 'INPUT' ) || + nodeName == 'TEXTAREA' || + nodeName == 'SELECT' || + nodeName == 'BUTTON') { + var self = this; + factories.push({path:path, fn:function(node, scope, prefix) { + node.name = prefix + node.name.split(":").pop(); + return self.widgetFactory.createController(jQuery(node), scope); + }}); + } + if (nodeName == 'OPTION') { + var html = jQuery('' + + '' + + '' + + '' + + ''); +}; + +extend(FileController.prototype, { + 'cancel': noop, + 'complete': noop, + 'httpStatus': function(status) { + alert("httpStatus:" + this.scopeName + " status:" + status); + }, + 'ioError': function() { + alert("ioError:" + this.scopeName); + }, + 'open': function() { + alert("open:" + this.scopeName); + }, + 'progress':noop, + 'securityError': function() { + alert("securityError:" + this.scopeName); + }, + 'uploadCompleteData': function(data) { + var value = fromJson(data); + value.url = this.attachmentsPath + '/' + value.id + '/' + value.text; + this.view.find("input").attr('checked', true); + var scope = this.view.scope(); + this.value = value; + this.updateModel(scope); + this.value = null; + }, + 'select': function(name, size, type) { + this.name = name; + this.view.find("a").text(name).attr('href', name); + this.view.find("span").text(angular['filter']['bytes'](size)); + this.upload(); + }, + + updateModel: function(scope) { + var isChecked = this.view.find("input").attr('checked'); + var value = isChecked ? this.value : null; + if (this.lastValue === value) { + return false; + } else { + scope.set(this.scopeName, value); + return true; + } + }, + + updateView: function(scope) { + var modelValue = scope.get(this.scopeName); + if (modelValue && this.value !== modelValue) { + this.value = modelValue; + this.view.find("a"). + attr("href", this.value.url). + text(this.value.text); + this.view.find("span").text(angular['filter']['bytes'](this.value.size)); + } + this.view.find("input").attr('checked', !!modelValue); + }, + + upload: function() { + if (this.name) { + this.uploader['uploadFile'](this.attachmentsPath); + } + } +}); + +/////////////////////// +// NullController +/////////////////////// +function NullController(view) {this.view = view;}; +NullController.prototype = { + updateModel: function() { return true; }, + updateView: noop +}; +NullController.instance = new NullController(); + + +/////////////////////// +// ButtonController +/////////////////////// +var ButtonController = NullController; + +/////////////////////// +// TextController +/////////////////////// +function TextController(view, exp, formatter) { + this.view = view; + this.formatter = formatter; + this.exp = exp; + this.validator = view.getAttribute('ng-validate'); + this.required = typeof view.attributes['ng-required'] != "undefined"; + this.lastErrorText = null; + this.lastValue = undefined; + this.initialValue = this.formatter['parse'](view.value); + var widget = view.getAttribute('ng-widget'); + if (widget === 'datepicker') { + jQuery(view).datepicker(); + } +}; + +TextController.prototype = { + updateModel: function(scope) { + var value = this.formatter['parse'](this.view.value); + if (this.lastValue === value) { + return false; + } else { + scope.setEval(this.exp, value); + this.lastValue = value; + return true; + } + }, + + updateView: function(scope) { + var view = this.view; + var value = scope.get(this.exp); + if (typeof value === "undefined") { + value = this.initialValue; + scope.setEval(this.exp, value); + } + value = value ? value : ''; + if (!_(this.lastValue).isEqual(value)) { + view.value = this.formatter['format'](value); + this.lastValue = value; + } + + var isValidationError = false; + view.removeAttribute('ng-error'); + if (this.required) { + isValidationError = !(value && $.trim("" + value).length > 0); + } + var errorText = isValidationError ? "Required Value" : null; + if (!isValidationError && this.validator && value) { + errorText = scope.validate(this.validator, value, view); + isValidationError = !!errorText; + } + if (this.lastErrorText !== errorText) { + this.lastErrorText = isValidationError; + if (errorText && isVisible(view)) { + view.setAttribute('ng-error', errorText); + scope.markInvalid(this); + } + jQuery(view).toggleClass('ng-validation-error', isValidationError); + } + } +}; + +/////////////////////// +// CheckboxController +/////////////////////// +function CheckboxController(view, exp, formatter) { + this.view = view; + this.exp = exp; + this.lastValue = undefined; + this.formatter = formatter; + this.initialValue = this.formatter['parse'](view.checked ? view.value : ""); +}; + +CheckboxController.prototype = { + updateModel: function(scope) { + var input = this.view; + var value = input.checked ? input.value : ''; + value = this.formatter['parse'](value); + value = this.formatter['format'](value); + if (this.lastValue === value) { + return false; + } else { + scope.setEval(this.exp, this.formatter['parse'](value)); + this.lastValue = value; + return true; + } + }, + + updateView: function(scope) { + var input = this.view; + var value = scope.eval(this.exp); + if (typeof value === "undefined") { + value = this.initialValue; + scope.setEval(this.exp, value); + } + input.checked = this.formatter['parse'](input.value) == value; + } +}; + +/////////////////////// +// SelectController +/////////////////////// +function SelectController(view, exp) { + this.view = view; + this.exp = exp; + this.lastValue = undefined; + this.initialValue = view.value; +}; + +SelectController.prototype = { + updateModel: function(scope) { + var input = this.view; + if (input.selectedIndex < 0) { + scope.setEval(this.exp, null); + } else { + var value = this.view.value; + if (this.lastValue === value) { + return false; + } else { + scope.setEval(this.exp, value); + this.lastValue = value; + return true; + } + } + }, + + updateView: function(scope) { + var input = this.view; + var value = scope.get(this.exp); + if (typeof value === 'undefined') { + value = this.initialValue; + scope.setEval(this.exp, value); + } + if (value !== this.lastValue) { + input.value = value ? value : ""; + this.lastValue = value; + } + } +}; + +/////////////////////// +// MultiSelectController +/////////////////////// +function MultiSelectController(view, exp) { + this.view = view; + this.exp = exp; + this.lastValue = undefined; + this.initialValue = this.selected(); +}; + +MultiSelectController.prototype = { + selected: function () { + var value = []; + var options = this.view.options; + for ( var i = 0; i < options.length; i++) { + var option = options[i]; + if (option.selected) { + value.push(option.value); + } + } + return value; + }, + + updateModel: function(scope) { + var value = this.selected(); + // TODO: This is wrong! no caching going on here as we are always comparing arrays + if (this.lastValue === value) { + return false; + } else { + scope.setEval(this.exp, value); + this.lastValue = value; + return true; + } + }, + + updateView: function(scope) { + var input = this.view; + var selected = scope.get(this.exp); + if (typeof selected === "undefined") { + selected = this.initialValue; + scope.setEval(this.exp, selected); + } + if (selected !== this.lastValue) { + var options = input.options; + for ( var i = 0; i < options.length; i++) { + var option = options[i]; + option.selected = _.include(selected, option.value); + } + this.lastValue = selected; + } + } +}; + +/////////////////////// +// RadioController +/////////////////////// +function RadioController(view, exp) { + this.view = view; + this.exp = exp; + this.lastChecked = undefined; + this.lastValue = undefined; + this.inputValue = view.value; + this.initialValue = view.checked ? view.value : null; +}; + +RadioController.prototype = { + updateModel: function(scope) { + var input = this.view; + if (this.lastChecked) { + return false; + } else { + input.checked = true; + this.lastValue = scope.setEval(this.exp, this.inputValue); + this.lastChecked = true; + return true; + } + }, + + updateView: function(scope) { + var input = this.view; + var value = scope.get(this.exp); + if (this.initialValue && typeof value === "undefined") { + value = this.initialValue; + scope.setEval(this.exp, value); + } + if (this.lastValue != value) { + this.lastChecked = input.checked = this.inputValue == (''+value); + this.lastValue = value; + } + } +}; + +/////////////////////// +//ElementController +/////////////////////// +function BindUpdater(view, exp) { + this.view = view; + this.exp = Binder.parseBindings(exp); + this.hasError = false; +}; + +BindUpdater.toText = function(obj) { + var e = escapeHtml; + switch(typeof obj) { + case "string": + case "boolean": + case "number": + return e(obj); + case "function": + return BindUpdater.toText(obj()); + case "object": + if (isNode(obj)) { + return outerHTML(obj); + } else if (obj instanceof angular.filter.Meta) { + switch(typeof obj.html) { + case "string": + case "number": + return obj.html; + case "function": + return obj.html(); + case "object": + if (isNode(obj.html)) + return outerHTML(obj.html); + default: + break; + } + switch(typeof obj.text) { + case "string": + case "number": + return e(obj.text); + case "function": + return e(obj.text()); + default: + break; + } + } + if (obj === null) + return ""; + return e(toJson(obj, true)); + default: + return ""; + } +}; + +BindUpdater.prototype = { + updateModel: noop, + updateView: function(scope) { + var html = []; + var parts = this.exp; + var length = parts.length; + for(var i=0; i iteratorCounter; --r) { + this.children.pop().element.remove(); + } + // Special case for option in select + if (child && child.element[0].nodeName === "OPTION") { + var select = jQuery(child.element[0].parentNode); + var cntl = select.data('controller'); + if (cntl) { + cntl.lastValue = undefined; + cntl.updateView(scope); + } + } + }); + } +}; + +////////////////////////////////// +// PopUp +////////////////////////////////// + +function PopUp(doc) { + this.doc = doc; +}; + +PopUp.OUT_EVENT = "mouseleave mouseout click dblclick keypress keyup"; + +PopUp.onOver = function(e) { + PopUp.onOut(); + var jNode = jQuery(this); + jNode.bind(PopUp.OUT_EVENT, PopUp.onOut); + var position = jNode.position(); + var de = document.documentElement; + var w = self.innerWidth || (de&&de.clientWidth) || document.body.clientWidth; + var hasArea = w - position.left; + var width = 300; + var title = jNode.hasClass("ng-exception") ? "EXCEPTION:" : "Validation error..."; + var msg = jNode.attr("ng-error"); + + var x; + var arrowPos = hasArea>(width+75) ? "left" : "right"; + var tip = jQuery( + "
        " + + "
        " + + "
        "+title+"
        " + + "
        "+msg+"
        " + + "
        "); + jQuery("body").append(tip); + if(arrowPos === 'left'){ + x = position.left + this.offsetWidth + 11; + }else{ + x = position.left - (width + 15); + tip.find('.ng-arrow-right').css({left:width+1}); + } + + tip.css({left: x+"px", top: (position.top - 3)+"px"}); + return true; +}; + +PopUp.onOut = function() { + jQuery('#ng-callout'). + unbind(PopUp.OUT_EVENT, PopUp.onOut). + remove(); + return true; +}; + +PopUp.prototype = { + bind: function () { + var self = this; + this.doc.find('.ng-validation-error,.ng-exception'). + live("mouseover", PopUp.onOver); + } +}; + +////////////////////////////////// +// Status +////////////////////////////////// + +function NullStatus(body) { +}; + +NullStatus.prototype = { + beginRequest:function(){}, + endRequest:function(){} +}; + +function Status(body) { + this.requestCount = 0; + this.body = body; +}; + +Status.DOM ='
        loading....
        '; + +Status.prototype = { + beginRequest: function () { + if (this.requestCount === 0) { + (this.loader = this.loader || this.body.append(Status.DOM).find("#ng-loading")).show(); + } + this.requestCount++; + }, + + endRequest: function () { + this.requestCount--; + if (this.requestCount === 0) { + this.loader.hide("fold"); + } + } +}; diff --git a/src/directives.js b/src/directives.js index 10476c77..c54c89e9 100644 --- a/src/directives.js +++ b/src/directives.js @@ -11,9 +11,15 @@ angularDirective("ng-eval", function(expression){ }); angularDirective("ng-bind", function(expression){ + var templateFn = compileBindTemplate("{{" + expression + "}}"); return function(element) { - this.$watch(expression, function(value){ - element.text(value); + var lastValue; + this.$onEval(function() { + var value = templateFn.call(this); + if (value != lastValue) { + element.text(value); + lastValue = value; + } }, element); }; }); @@ -34,7 +40,9 @@ function compileBindTemplate(template){ bindTemplateCache[template] = fn = function(){ var parts = [], self = this; foreach(bindings, function(fn){ - parts.push(fn.call(self)); + var value = fn.call(self); + if (isObject(value)) value = toJson(value, true); + parts.push(value); }); return parts.join(''); }; @@ -125,6 +133,7 @@ angularDirective("ng-action", function(expression, element){ var self = this; element.click(function(){ self.$tryEval(expression, element); + self.$eval(); }); }; }); diff --git a/src/jqLite.js b/src/jqLite.js index 7646bf98..a5014354 100644 --- a/src/jqLite.js +++ b/src/jqLite.js @@ -38,7 +38,8 @@ function JQLite(element) { this[0] = element; } -function jqLite(element) { + +function jqLiteWrap(element) { if (typeof element == 'string') { var div = document.createElement('div'); div.innerHTML = element; @@ -47,6 +48,8 @@ function jqLite(element) { return element instanceof JQLite ? element : new JQLite(element); } +jqLite = jqLite || jqLiteWrap; + JQLite.prototype = { data: function(key, value) { var element = this[0], @@ -85,12 +88,15 @@ JQLite.prototype = { foreach(type.split(' '), function(type){ eventHandler = bind[type]; if (!eventHandler) { - bind[type] = eventHandler = function() { - var value = false; + bind[type] = eventHandler = function(event) { + var bubbleEvent = false; foreach(eventHandler.fns, function(fn){ - value = value || fn.apply(self, arguments); + bubbleEvent = bubbleEvent || fn.apply(self, arguments); }); - return value; + if (!bubbleEvent) { + event.preventDefault(); + event.stopPropagation(); + } }; eventHandler.fns = []; addEventListener(element, type, eventHandler); diff --git a/src/markup.js b/src/markup.js deleted file mode 100644 index 5fb10779..00000000 --- a/src/markup.js +++ /dev/null @@ -1,76 +0,0 @@ -function parseBindings(string) { - var results = []; - var lastIndex = 0; - var index; - while((index = string.indexOf('{{', lastIndex)) > -1) { - if (lastIndex < index) - results.push(string.substr(lastIndex, index - lastIndex)); - lastIndex = index; - - index = string.indexOf('}}', index); - index = index < 0 ? string.length : index + 2; - - results.push(string.substr(lastIndex, index - lastIndex)); - lastIndex = index; - } - if (lastIndex != string.length) - results.push(string.substr(lastIndex, string.length - lastIndex)); - return results.length === 0 ? [ string ] : results; -}; - -function binding(string) { - var binding = string.replace(/\n/gm, ' ').match(/^\{\{(.*)\}\}$/); - return binding ? binding[1] : null; -}; - -function hasBindings(bindings) { - return bindings.length > 1 || Binder.binding(bindings[0]) !== null; -}; - -angularTextMarkup('{{}}', function(text, textNode, parentElement) { - var bindings = parseBindings(text), - self = this; - if (hasBindings(bindings)) { - if (isLeafNode(parentElement[0])) { - parentElement.attr('ng-bind-template', text); - } else { - var cursor = textNode, newElement; - foreach(parseBindings(text), function(text){ - var exp = binding(text); - if (exp) { - newElement = self.element('span'); - newElement.attr('ng-bind', exp); - } else { - newElement = self.text(text); - } - cursor.after(newElement); - cursor = newElement; - }); - } - textNode.remove(); - } -}); - -angularTextMarkup('OPTION', function(text, textNode, parentElement){ - if (parentElement[0].nodeName == "OPTION") { - var select = document.createElement('select'); - select.insertBefore(parentElement[0].cloneNode(true), null); - if (!select.innerHTML.match(/.*<\/\s*option\s*>/gi)) { - parentElement.attr('value', text); - } - } -}); - -var NG_BIND_ATTR = 'ng-bind-attr'; -angularAttrMarkup('{{}}', function(value, name, element){ - if (name.substr(0, 3) != 'ng-') { - var bindings = parseBindings(value), - bindAttr; - if (hasBindings(bindings)) { - element.removeAttr(name); - bindAttr = fromJson(element.attr(NG_BIND_ATTR) || "{}"); - bindAttr[name] = value; - element.attr(NG_BIND_ATTR, toJson(bindAttr)); - } - } -}); diff --git a/src/markups.js b/src/markups.js new file mode 100644 index 00000000..6bc27c85 --- /dev/null +++ b/src/markups.js @@ -0,0 +1,76 @@ +function parseBindings(string) { + var results = []; + var lastIndex = 0; + var index; + while((index = string.indexOf('{{', lastIndex)) > -1) { + if (lastIndex < index) + results.push(string.substr(lastIndex, index - lastIndex)); + lastIndex = index; + + index = string.indexOf('}}', index); + index = index < 0 ? string.length : index + 2; + + results.push(string.substr(lastIndex, index - lastIndex)); + lastIndex = index; + } + if (lastIndex != string.length) + results.push(string.substr(lastIndex, string.length - lastIndex)); + return results.length === 0 ? [ string ] : results; +}; + +function binding(string) { + var binding = string.replace(/\n/gm, ' ').match(/^\{\{(.*)\}\}$/); + return binding ? binding[1] : null; +}; + +function hasBindings(bindings) { + return bindings.length > 1 || binding(bindings[0]) !== null; +}; + +angularTextMarkup('{{}}', function(text, textNode, parentElement) { + var bindings = parseBindings(text), + self = this; + if (hasBindings(bindings)) { + if (isLeafNode(parentElement[0])) { + parentElement.attr('ng-bind-template', text); + } else { + var cursor = textNode, newElement; + foreach(parseBindings(text), function(text){ + var exp = binding(text); + if (exp) { + newElement = self.element('span'); + newElement.attr('ng-bind', exp); + } else { + newElement = self.text(text); + } + cursor.after(newElement); + cursor = newElement; + }); + } + textNode.remove(); + } +}); + +angularTextMarkup('OPTION', function(text, textNode, parentElement){ + if (parentElement[0].nodeName == "OPTION") { + var select = document.createElement('select'); + select.insertBefore(parentElement[0].cloneNode(true), null); + if (!select.innerHTML.match(/.*<\/\s*option\s*>/gi)) { + parentElement.attr('value', text); + } + } +}); + +var NG_BIND_ATTR = 'ng-bind-attr'; +angularAttrMarkup('{{}}', function(value, name, element){ + if (name.substr(0, 3) != 'ng-') { + var bindings = parseBindings(value), + bindAttr; + if (hasBindings(bindings)) { + element.removeAttr(name); + bindAttr = fromJson(element.attr(NG_BIND_ATTR) || "{}"); + bindAttr[name] = value; + element.attr(NG_BIND_ATTR, toJson(bindAttr)); + } + } +}); diff --git a/src/moveToAngularCom/ControlBar.js b/src/moveToAngularCom/ControlBar.js new file mode 100644 index 00000000..685beeb2 --- /dev/null +++ b/src/moveToAngularCom/ControlBar.js @@ -0,0 +1,72 @@ +function ControlBar(document, serverUrl, database) { + this._document = document; + this.serverUrl = serverUrl; + this.database = database; + this._window = window; + this.callbacks = []; +}; + +ControlBar.HTML = + '
        ' + + '
        ' + + '
        ' + + '' + + '
        ' + + '
        '; + + +ControlBar.FORBIDEN = + '
        ' + + 'Sorry, you do not have permission for this!'+ + '
        '; + +ControlBar.prototype = { + bind: function () { + }, + + login: function (loginSubmitFn) { + this.callbacks.push(loginSubmitFn); + if (this.callbacks.length == 1) { + this.doTemplate("/user_session/new.mini?database="+encodeURIComponent(this.database)+"&return_url=" + encodeURIComponent(this.urlWithoutAnchor())); + } + }, + + logout: function (loginSubmitFn) { + this.callbacks.push(loginSubmitFn); + if (this.callbacks.length == 1) { + this.doTemplate("/user_session/do_destroy.mini"); + } + }, + + urlWithoutAnchor: function (path) { + return this._window['location']['href'].split("#")[0]; + }, + + doTemplate: function (path) { + var self = this; + var id = new Date().getTime(); + var url = this.urlWithoutAnchor() + "#$iframe_notify=" + id; + var iframeHeight = 330; + var loginView = jQuery('
        ' + '
        '); - this.console = this.body.find(".console"); - this.testFrame = this.body.find("iframe"); - this.console.find(".run").live("click", function(){ - jQuery(this).parent().find('.log').toggle(); - }); - }, - - - runScenarios:function(){ - var runner = new scenario.Runner(this.console, this.testFrame); - _.stepper(this.scenarios, function(next, scenarioObj, name){ - new scenario.Scenario(name, scenarioObj).run(runner, next); - }, function(){ - } - ); - } -}; - -scenario.Runner = function(console, frame){ - this.console = console; - this.current = null; - this.tests = []; - this.frame = frame; -}; -scenario.Runner.prototype = { - start:function(name){ - var current = this.current = { - name:name, - start:new Date().getTime(), - scenario:jQuery('
        ') + var console = body.find('#runner .console'); + this.testFrame = body.find('#testView iframe'); + this.testWindow = this.testFrame[0].contentWindow; + this.beginSpec = function(name){ + var specElement = jQuery('
      • '); + var stepContainer = jQuery('
          '); + console.append(specElement); + specElement.text(name); + specElement.append(stepContainer); + return function(name){ + var stepElement = jQuery('
        • '); + var logContainer = jQuery('
            '); + stepContainer.append(stepElement); + stepElement.text(name); + stepElement.append(logContainer); + return function(message) { + var logElement = jQuery('
          • '); + logContainer.append(logElement); + logElement.text(message); + }; + }; }; - current.run = current.scenario.append( - '
            ' + - '.' + - '.' + - '.' + - '').find(".run"); - current.log = current.scenario.append('
            ').find(".log"); - current.run.find(".name").text(name); - this.tests.push(current); - this.console.append(current.scenario); - }, - end:function(name){ - var current = this.current; - var run = current.run; - this.current = null; - current.end = new Date().getTime(); - current.time = current.end - current.start; - run.find(".time").text(current.time); - run.find(".state").text(current.error ? "FAIL" : "PASS"); - run.addClass(current.error ? "fail" : "pass"); - if (current.error) - run.find(".run").append('').text(current.error); - current.scenario.find(".log").hide(); + this.execute("widgets: it should verify that basic widgets work"); }, - log:function(level) { - var buf = []; - for ( var i = 1; i < arguments.length; i++) { - var arg = arguments[i]; - buf.push(typeof arg == "string" ?arg:toJson(arg)); - } - var log = jQuery('
            '); - log.text(buf.join(" ")); - this.current.log.append(log); - this.console.scrollTop(this.console[0].scrollHeight); - if (level == "error") - this.current.error = buf.join(" "); - } -}; -scenario.Scenario = function(name, scenario){ - this.name = name; - this.scenario = scenario; -}; -scenario.Scenario.prototype = { - run:function(runner, callback) { - var self = this; - _.stepper(this.scenario, function(next, steps, name){ - if (name.charAt(0) == '$') { - next(); - } else { - runner.start(self.name + "::" + name); - var allSteps = (self.scenario.$before||[]).concat(steps); - _.stepper(allSteps, function(next, step){ - self.executeStep(runner, step, next); - }, function(){ - runner.end(); - next(); - }); - } - }, callback); + addStep: function(name, step) { + this.currentSpec.steps.push({name:name, fn:step}); }, - - verb:function(step){ - var fn = null; - if (!step) fn = function (){ throw "Step is null!"; }; - else if (step.Given) fn = scenario.GIVEN[step.Given]; - else if (step.When) fn = scenario.WHEN[step.When]; - else if (step.Then) fn = scenario.THEN[step.Then]; - return fn || function (){ - throw "ERROR: Need Given/When/Then got: " + toJson(step); + execute: function(name, callback) { + var spec = this.specs[name], + result = { + passed: false, + failed: false, + finished: false, + fail: function(error) { + result.passed = false; + result.failed = true; + result.error = error; + result.log(angular.isString(error) ? error : angular.toJson(error)); + } + }; + specThis = { + result: result, + testWindow: this.testWindow, + testFrame: this.testFrame }; - }, - - - context: function(runner) { - var frame = runner.frame; - var window = frame[0].contentWindow; - var document; - if (window.jQuery) - document = window.jQuery(window.document); - var context = { - frame:frame, - window:window, - log:_.bind(runner.log, runner, "info"), - document:document, - assert:function(element, path){ - if (element.size() != 1) { - throw "Expected to find '1' found '"+ - element.size()+"' for '"+path+"'."; - } - return element; - }, - element:function(path){ - var exp = path.replace("{{","[ng-bind=").replace("}}", "]"); - var element = document.find(exp); - return context.assert(element, path); - } - }; - return context; - }, - - - executeStep:function(runner, step, callback) { - if (!step) { - callback(); - return; - } - runner.log("info", toJson(step)); - var fn = this.verb(step); - var context = this.context(runner); - _.extend(context, step); - try { - (fn.call(context)||function(c){c();})(callback); - } catch (e) { - runner.log("error", "ERROR: " + toJson(e)); - } + var beginStep = this.beginSpec(name); + spec.nextStepIndex = 0; + function done() { + result.finished = true; + (callback||angular.noop).call(specThis); + } + function next(){ + var step = spec.steps[spec.nextStepIndex]; + if (step) { + spec.nextStepIndex ++; + result.log = beginStep(step.name); + try { + step.fn.call(specThis, next); + } catch (e) { + result.fail(e); + done(); + } + } else { + result.passed = !result.failed; + done(); + } + }; + next(); + return specThis; } -}; +}; \ No newline at end of file diff --git a/src/scenario/Steps.js b/src/scenario/Steps.js deleted file mode 100644 index ffe75933..00000000 --- a/src/scenario/Steps.js +++ /dev/null @@ -1,57 +0,0 @@ -angular.scenario.GIVEN = { - browser:function(){ - var self = this; - if (jQuery.browser.safari && this.frame.attr('src') == this.at) { - this.window.location.reload(); - } else { - this.frame.attr('src', this.at); - } - return function(done){ - self.frame.load(function(){ - self.frame.unbind(); - done(); - }); - }; - }, - dataset:function(){ - this.frame.name="$DATASET:" + toJson({dataset:this.dataset}); - } -}; -angular.scenario.WHEN = { - enter:function(){ - var element = this.element(this.at); - element.attr('value', this.text); - element.change(); - }, - click:function(){ - var element = this.element(this.at); - var input = element[0]; - // emulate the browser behavior which causes it - // to be overridden at the end. - var checked = input.checked = !input.checked; - element.trigger('click'); - input.checked = checked; - }, - select:function(){ - var element = this.element(this.at); - var path = "option[value=" + this.option + "]"; - var option = this.assert(element.find(path)); - option[0].selected = !option[0].selected; - element.change(); - } -}; -angular.scenario.THEN = { - text:function(){ - var element = this.element(this.at); - if (typeof this.should_be != undefined ) { - var should_be = this.should_be; - if (_.isArray(this.should_be)) - should_be = JSON.stringify(should_be); - if (element.text() != should_be) - throw "Expected " + should_be + - " but was " + element.text() + "."; - } - }, - drainRequestQueue:function(){ - } -}; diff --git a/src/scenario/_namespace.js b/src/scenario/_namespace.js deleted file mode 100644 index 7da3a5d8..00000000 --- a/src/scenario/_namespace.js +++ /dev/null @@ -1,6 +0,0 @@ -if (!angular) var angular = window['angular'] = {}; -if (!angular['scenario']) var angularScenario = angular['scenario'] = {}; -if (!angular['scenarioDef']) var scenarioDef = angular['scenarioDef'] = {}; -if (!angular['scenario']['GIVEN']) angularScenario['GIVEN'] = {}; -if (!angular['scenario']['WHEN']) angularScenario['WHEN'] = {}; -if (!angular['scenario']['THEN']) angularScenario['THEN'] = {}; diff --git a/src/scenario/bootstrap.js b/src/scenario/bootstrap.js index 169f1860..81272bdd 100644 --- a/src/scenario/bootstrap.js +++ b/src/scenario/bootstrap.js @@ -19,22 +19,8 @@ } window.onload = function(){ - if (!_.stepper) { - _.stepper = function(collection, iterator, done){ - var keys = _.keys(collection); - function next() { - if (keys.length) { - var key = keys.shift(); - iterator(next, collection[key], key); - } else { - (done||_.identity)(); - } - } - next(); - }; - } _.defer(function(){ - new angular.scenario.SuiteRunner(angular.scenarioDef, jQuery(document.body)).run(); + $scenarioRunner.run(jQuery(document.body)); }); (onLoadDelegate||function(){})(); }; @@ -42,8 +28,7 @@ addScript("../../lib/underscore/underscore.js"); addScript("../../lib/jquery/jquery-1.4.2.js"); addScript("../angular-bootstrap.js"); - addScript("_namespace.js"); - addScript("Steps.js"); addScript("Runner.js"); + document.write(''); })(window.onload); diff --git a/test/scenario/RunnerSpec.js b/test/scenario/RunnerSpec.js new file mode 100644 index 00000000..bd7c0599 --- /dev/null +++ b/test/scenario/RunnerSpec.js @@ -0,0 +1,105 @@ +describe('Runner', function(){ + var scenario, runner, log, Describe, It, $scenario; + + function logger(text) { + return function(){log += text;}; + } + + beforeEach(function(){ + log = ''; + scenario = {}; + runner = new angular.scenario.Runner(scenario); + Describe = scenario.describe; + It = scenario.it; + $scenario = scenario.$scenario; + }); + + describe('describe', function(){ + it('should consume the describe functions', function(){ + Describe('describe name', logger('body')); + + expect(log).toEqual('body'); + }); + + describe('it', function(){ + it('should consume it', function(){ + Describe('describe name', function(){ + It('should text', logger('body')); + }); + expect(log).toEqual('body'); + var spec = $scenario.specs['describe name: it should text']; + expect(spec.steps).toEqual([]); + expect(spec.name).toEqual('describe name: it should text'); + }); + }); + }); + + describe('steps building', function(){ + it('should queue steps', function(){ + function step(){}; + Describe('name', function(){ + It('should', function(){ + $scenario.addStep('stepname', step); + }); + }); + expect($scenario.specs['name: it should'].steps).toEqual([{name:'stepname', fn:step}]); + }); + }); + + describe('execution', function(){ + it('should execute the queued steps', function(){ + var next, firstThis, secondThis, doneThis, spec; + $scenario.specs['spec'] = { + steps: [ + {name:'step1', fn: function(done) { + next = done; + log += 'first;'; + firstThis = this; + }}, + {name:'step2', fn:function(done){ + next = done; + log += 'second;'; + secondThis = this; + }} + ] + }; + + spec = $scenario.execute('spec', function(done){ + log += 'done;'; + doneThis = this; + }); + expect(log).toEqual('first;'); + next(); + expect(log).toEqual('first;second;'); + next(); + expect(log).toEqual('first;second;done;'); + expect(spec).not.toEqual(window); + expect(spec).toEqual(firstThis); + expect(spec).toEqual(secondThis); + expect(spec).toEqual(doneThis); + + expect(spec.result.failed).toEqual(false); + expect(spec.result.finished).toEqual(true); + expect(spec.result.error).toBeUndefined(); + expect(spec.result.passed).toEqual(true); + }); + + it('should handle exceptions in a step', function(){ + $scenario.specs['spec'] = { + steps: [ + {name:'error', fn:function(done) { + throw "MyError"; + }} + ] + }; + + var spec = $scenario.execute('spec'); + + expect(spec.result.passed).toEqual(false); + expect(spec.result.failed).toEqual(true); + expect(spec.result.finished).toEqual(true); + expect(spec.result.error).toEqual("MyError"); + }); + }); + +}); \ No newline at end of file diff --git a/test/scenario/StepsTest.js b/test/scenario/StepsTest.js deleted file mode 100644 index 32ef637d..00000000 --- a/test/scenario/StepsTest.js +++ /dev/null @@ -1,7 +0,0 @@ -StepsTest = TestCase("StepsTest"); - -StepsTest.prototype.testGivenDataset=function(){ - var self = {frame:{}, dataset:[]}; - angular.scenario.GIVEN.dataset.call(self); - assertEquals('$DATASET:{"dataset":[]}', self.frame.name); -}; -- cgit v1.2.3 From e3368e12a6207706d8a08b18f9958db3b86ca4e5 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Thu, 20 May 2010 16:55:47 -0700 Subject: semi working state --- css/angular-scenario.css | 15 +++++++++-- src/scenario/Runner.js | 63 ++++++++++++++++++++++++++++----------------- test/scenario/RunnerSpec.js | 3 +++ 3 files changed, 55 insertions(+), 26 deletions(-) diff --git a/css/angular-scenario.css b/css/angular-scenario.css index 70256a5a..3960c357 100644 --- a/css/angular-scenario.css +++ b/css/angular-scenario.css @@ -29,13 +29,24 @@ height: 100%; } +li.running > span { + background-color: yellow; +} -/////////////// +#runner span { + background-color: green; +} -.collapsed .log { +#runner .fail > span { + background-color: red; +} + +.collapsed > ul { display: none; } +////// + .run, .info, .error { display: block; padding: 0 1em; diff --git a/src/scenario/Runner.js b/src/scenario/Runner.js index eeb4330d..970d0c66 100644 --- a/src/scenario/Runner.js +++ b/src/scenario/Runner.js @@ -20,8 +20,8 @@ angular.scenario.Runner = function(scope){ body(); self.currentSpec = null; }; - this.beginSpec = function returnNoop(){ - return returnNoop; + this.logger = function returnNoop(){ + return angular.extend(returnNoop, {close:angular.noop, fail:angular.noop});; }; }; @@ -29,33 +29,45 @@ angular.scenario.Runner.prototype = { run: function(body){ body.append( '
            ' + - '
              ' + + '
              ' + '
              ' + '
              ' + '' + '
              '); var console = body.find('#runner .console'); + console.find('li').live('click', function(){ + jQuery(this).toggleClass('collapsed'); + }); this.testFrame = body.find('#testView iframe'); this.testWindow = this.testFrame[0].contentWindow; - this.beginSpec = function(name){ - var specElement = jQuery('
            • '); - var stepContainer = jQuery('
                '); - console.append(specElement); - specElement.text(name); - specElement.append(stepContainer); - return function(name){ - var stepElement = jQuery('
              • '); - var logContainer = jQuery('
                  '); - stepContainer.append(stepElement); - stepElement.text(name); - stepElement.append(logContainer); - return function(message) { - var logElement = jQuery('
                • '); - logContainer.append(logElement); - logElement.text(message); - }; + function logger(parent) { + var container; + return function(type, text) { + if (!container) { + container = jQuery('
                    '); + parent.append(container); + } + var element = jQuery(''); + element.find('span').text(text); + container.append(element); + return angular.extend(logger(element), { + close: function(){ + element.removeClass('running'); + }, + fail: function(){ + element.removeClass('running'); + var current = element; + while (current[0] != console[0]) { + if (current.is('li')) + current.addClass('fail'); + current.removeClass('collapsed'); + current = current.parent(); + } + } + });; }; - }; + } + this.logger = logger(console); this.execute("widgets: it should verify that basic widgets work"); }, @@ -73,7 +85,7 @@ angular.scenario.Runner.prototype = { result.passed = false; result.failed = true; result.error = error; - result.log(angular.isString(error) ? error : angular.toJson(error)); + result.log('fail', angular.isString(error) ? error : angular.toJson(error)).fail(); } }; specThis = { @@ -81,17 +93,20 @@ angular.scenario.Runner.prototype = { testWindow: this.testWindow, testFrame: this.testFrame }; - var beginStep = this.beginSpec(name); + var stepLogger = this.logger('spec', name); spec.nextStepIndex = 0; function done() { result.finished = true; + stepLogger.close(); (callback||angular.noop).call(specThis); } function next(){ var step = spec.steps[spec.nextStepIndex]; + (result.log || {close:angular.noop}).close(); + result.log = null; if (step) { spec.nextStepIndex ++; - result.log = beginStep(step.name); + result.log = stepLogger('step', step.name); try { step.fn.call(specThis, next); } catch (e) { diff --git a/test/scenario/RunnerSpec.js b/test/scenario/RunnerSpec.js index bd7c0599..2883ab7c 100644 --- a/test/scenario/RunnerSpec.js +++ b/test/scenario/RunnerSpec.js @@ -31,6 +31,9 @@ describe('Runner', function(){ expect(spec.steps).toEqual([]); expect(spec.name).toEqual('describe name: it should text'); }); + + it('should camplain on duplicate it', angular.noop); + }); }); -- cgit v1.2.3 From f6c67e28c94033edf6a16eb6508de54679cb49db Mon Sep 17 00:00:00 2001 From: Andres Ornelas Mesta Date: Mon, 24 May 2010 13:54:32 -0700 Subject: happy --- jsTestDriver-jquery.conf | 3 +++ jsTestDriver.conf | 4 +++- scenario/widgets-scenario2.js | 5 +++-- scenario/widgets.html | 1 - src/scenario/Runner.js | 44 ++++++++++++++++++++++++++++--------------- src/scenario/bootstrap.js | 6 ++++-- test/jquery_alias.js | 1 + test/jquery_remove.js | 1 + test/scenario/RunnerSpec.js | 34 ++++++++++++++++++++++++++++++--- 9 files changed, 75 insertions(+), 24 deletions(-) create mode 100644 test/jquery_alias.js create mode 100644 test/jquery_remove.js diff --git a/jsTestDriver-jquery.conf b/jsTestDriver-jquery.conf index dde88e80..128fcb6c 100644 --- a/jsTestDriver-jquery.conf +++ b/jsTestDriver-jquery.conf @@ -4,6 +4,7 @@ load: - lib/jasmine/jasmine-0.10.3.js - lib/jasmine-jstd-adapter/JasmineAdapter.js - lib/jquery/jquery-1.4.2.js + - test/jquery_alias.js - src/Angular.js - src/*.js - src/scenario/*.js @@ -17,3 +18,5 @@ exclude: - src/angular.suffix - src/angular-bootstrap.js - src/AngularPublic.js + - test/jquery_remove.js + diff --git a/jsTestDriver.conf b/jsTestDriver.conf index 6f959b8f..4a702a3e 100644 --- a/jsTestDriver.conf +++ b/jsTestDriver.conf @@ -3,7 +3,8 @@ server: http://localhost:9876 load: - lib/jasmine/jasmine-0.10.3.js - lib/jasmine-jstd-adapter/JasmineAdapter.js -# - lib/jquery/jquery-1.4.2.js + - lib/jquery/jquery-1.4.2.js + - test/jquery_remove.js - src/Angular.js - src/*.js - src/scenario/*.js @@ -13,6 +14,7 @@ load: - test/*.js exclude: + - test/jquery_alias.js - src/angular.prefix - src/angular.suffix - src/angular-bootstrap.js diff --git a/scenario/widgets-scenario2.js b/scenario/widgets-scenario2.js index e24cabad..b966b270 100644 --- a/scenario/widgets-scenario2.js +++ b/scenario/widgets-scenario2.js @@ -4,7 +4,7 @@ browser = { var self = this; self.testFrame.load(function(){ self.testFrame.unbind(); - self.testDocument = self.testWindow.angular.element(self.testWindow.document); + self.testDocument = jQuery(self.testWindow.document); done(); }); if (this.testFrame.attr('src') == url) { @@ -23,6 +23,7 @@ function input(selector) { var input = this.testDocument.find('input[name=' + selector + ']'); input.val(value); input.trigger('change'); + this.testWindow.angular.element(input[0]).trigger('change'); done(); }); } @@ -49,6 +50,6 @@ describe('widgets', function(){ browser.navigateTo('widgets.html'); expect('{{text.basic}}').toEqual(''); input('text.basic').enter('John'); - expect('{{text.basic}}').toEqual('JohnXX'); + expect('{{text.basic}}').toEqual('John'); }); }); diff --git a/scenario/widgets.html b/scenario/widgets.html index 5c3afa21..4d0f30b0 100644 --- a/scenario/widgets.html +++ b/scenario/widgets.html @@ -2,7 +2,6 @@ - diff --git a/src/scenario/Runner.js b/src/scenario/Runner.js index 970d0c66..9e20d394 100644 --- a/src/scenario/Runner.js +++ b/src/scenario/Runner.js @@ -1,8 +1,9 @@ angular['scenario'] = (angular['scenario'] = {}); -angular.scenario.Runner = function(scope){ +angular.scenario.Runner = function(scope, jQuery){ var self = scope.$scenario = this; this.scope = scope; + this.jQuery = jQuery; var specs = this.specs = {}; var path = []; @@ -27,6 +28,7 @@ angular.scenario.Runner = function(scope){ angular.scenario.Runner.prototype = { run: function(body){ + var jQuery = this.jQuery; body.append( '
                    ' + '
                    ' + @@ -68,7 +70,19 @@ angular.scenario.Runner.prototype = { }; } this.logger = logger(console); - this.execute("widgets: it should verify that basic widgets work"); + var specNames = []; + angular.foreach(this.specs, function(spec, name){ + specNames.push(name); + }, this); + specNames.sort(); + var self = this; + function callback(){ + var next = specNames.shift(); + if(next) { + self.execute(next, callback); + } + }; + callback(); }, addStep: function(name, step) { @@ -102,21 +116,21 @@ angular.scenario.Runner.prototype = { } function next(){ var step = spec.steps[spec.nextStepIndex]; - (result.log || {close:angular.noop}).close(); - result.log = null; - if (step) { - spec.nextStepIndex ++; - result.log = stepLogger('step', step.name); - try { - step.fn.call(specThis, next); - } catch (e) { - result.fail(e); - done(); - } - } else { - result.passed = !result.failed; + (result.log || {close:angular.noop}).close(); + result.log = null; + if (step) { + spec.nextStepIndex ++; + result.log = stepLogger('step', step.name); + try { + step.fn.call(specThis, next); + } catch (e) { + result.fail(e); done(); } + } else { + result.passed = !result.failed; + done(); + } }; next(); return specThis; diff --git a/src/scenario/bootstrap.js b/src/scenario/bootstrap.js index 81272bdd..51d24c38 100644 --- a/src/scenario/bootstrap.js +++ b/src/scenario/bootstrap.js @@ -20,7 +20,7 @@ window.onload = function(){ _.defer(function(){ - $scenarioRunner.run(jQuery(document.body)); + $scenarioRunner.run(jQuery(window.document.body)); }); (onLoadDelegate||function(){})(); }; @@ -29,6 +29,8 @@ addScript("../../lib/jquery/jquery-1.4.2.js"); addScript("../angular-bootstrap.js"); addScript("Runner.js"); - document.write(''); + document.write(''); })(window.onload); diff --git a/test/jquery_alias.js b/test/jquery_alias.js new file mode 100644 index 00000000..4b3fad00 --- /dev/null +++ b/test/jquery_alias.js @@ -0,0 +1 @@ +var _jQuery = jQuery; \ No newline at end of file diff --git a/test/jquery_remove.js b/test/jquery_remove.js new file mode 100644 index 00000000..5283c340 --- /dev/null +++ b/test/jquery_remove.js @@ -0,0 +1 @@ +var _jQuery = jQuery.noConflict(true); \ No newline at end of file diff --git a/test/scenario/RunnerSpec.js b/test/scenario/RunnerSpec.js index 2883ab7c..702e1ab5 100644 --- a/test/scenario/RunnerSpec.js +++ b/test/scenario/RunnerSpec.js @@ -1,14 +1,18 @@ describe('Runner', function(){ - var scenario, runner, log, Describe, It, $scenario; + var scenario, runner, log, Describe, It, $scenario, body; function logger(text) { - return function(){log += text;}; + return function(done){ + log += text; + (done||noop)(); + }; } beforeEach(function(){ log = ''; scenario = {}; - runner = new angular.scenario.Runner(scenario); + body = _jQuery('
                    '); + runner = new angular.scenario.Runner(scenario, _jQuery); Describe = scenario.describe; It = scenario.it; $scenario = scenario.$scenario; @@ -105,4 +109,28 @@ describe('Runner', function(){ }); }); + describe('run', function(){ + var next; + it('should execute all specs', function(){ + Describe('d1', function(){ + It('it1', function(){ $scenario.addStep('s1', logger('s1,')); }); + It('it2', function(){ + $scenario.addStep('s2', logger('s2,')); + $scenario.addStep('s2.2', function(done){ next = done; }); + }); + }); + Describe('d2', function(){ + It('it3', function(){ $scenario.addStep('s3', logger('s3,')); }); + It('it4', function(){ $scenario.addStep('s4', logger('s4,')); }); + }); + + $scenario.run(body); + + expect(log).toEqual('s1,s2,'); + next(); + expect(log).toEqual('s1,s2,s3,s4,'); + + }); + }); + }); \ No newline at end of file -- cgit v1.2.3 From 3fab5d9879272b9f991a67c8135754f00c055834 Mon Sep 17 00:00:00 2001 From: Andres Ornelas Date: Mon, 24 May 2010 15:25:30 -0700 Subject: added error handling on scenario definition --- jsTestDriver-jquery.conf | 1 + jsTestDriver.conf | 1 + scenario/Runner.html | 2 +- scenario/widgets-scenario.js | 20 +++++++++++++++ scenario/widgets-scenario2.js | 55 ------------------------------------------ scenario/widgets-scenarios.js | 49 ------------------------------------- scenario/widgets-scenarios.old | 49 +++++++++++++++++++++++++++++++++++++ scenario/widgets.html | 4 +-- src/scenario/DSL.js | 47 ++++++++++++++++++++++++++++++++++++ src/scenario/Runner.js | 15 +++++++++--- src/scenario/bootstrap.js | 1 + test/scenario/RunnerSpec.js | 18 +++++++++++++- 12 files changed, 151 insertions(+), 111 deletions(-) create mode 100644 scenario/widgets-scenario.js delete mode 100644 scenario/widgets-scenario2.js delete mode 100644 scenario/widgets-scenarios.js create mode 100644 scenario/widgets-scenarios.old create mode 100644 src/scenario/DSL.js diff --git a/jsTestDriver-jquery.conf b/jsTestDriver-jquery.conf index 128fcb6c..953e4521 100644 --- a/jsTestDriver-jquery.conf +++ b/jsTestDriver-jquery.conf @@ -7,6 +7,7 @@ load: - test/jquery_alias.js - src/Angular.js - src/*.js + - src/scenario/Runner.js - src/scenario/*.js - test/testabilityPatch.js - test/angular-mocks.js diff --git a/jsTestDriver.conf b/jsTestDriver.conf index 4a702a3e..c28e9ff1 100644 --- a/jsTestDriver.conf +++ b/jsTestDriver.conf @@ -7,6 +7,7 @@ load: - test/jquery_remove.js - src/Angular.js - src/*.js + - src/scenario/Runner.js - src/scenario/*.js - test/testabilityPatch.js - test/angular-mocks.js diff --git a/scenario/Runner.html b/scenario/Runner.html index 5502283a..ffa08af9 100644 --- a/scenario/Runner.html +++ b/scenario/Runner.html @@ -2,7 +2,7 @@ - + diff --git a/scenario/widgets-scenario.js b/scenario/widgets-scenario.js new file mode 100644 index 00000000..9e23d4df --- /dev/null +++ b/scenario/widgets-scenario.js @@ -0,0 +1,20 @@ +describe('widgets', function(){ + it('should verify that basic widgets work', function(){ + browser.navigateTo('widgets.html'); + + expect('{{text.basic}}').toEqual(''); + input('text.basic').enter('John'); + expect('{{text.basic}}').toEqual('John'); + + expect('{{text.password}}').toEqual(''); + input('text.password').enter('secret'); + expect('{{text.password}}').toEqual('secret'); + + expect('{{text.hidden}}').toEqual('hiddenValue'); + + expect('{{gender}}').toEqual('male'); + input('gender').select('female'); + expect('{{gender}}').toEqual('female'); + + }); +}); diff --git a/scenario/widgets-scenario2.js b/scenario/widgets-scenario2.js deleted file mode 100644 index b966b270..00000000 --- a/scenario/widgets-scenario2.js +++ /dev/null @@ -1,55 +0,0 @@ -browser = { - navigateTo: function(url){ - $scenario.addStep('Navigate to: ' + url, function(done){ - var self = this; - self.testFrame.load(function(){ - self.testFrame.unbind(); - self.testDocument = jQuery(self.testWindow.document); - done(); - }); - if (this.testFrame.attr('src') == url) { - this.testWindow.location.reload(); - } else { - this.testFrame.attr('src', url); - } - }); - } -}; - -function input(selector) { - return { - enter: function(value){ - $scenario.addStep("Set input text of '" + selector + "' to value '" + value + "'", function(done){ - var input = this.testDocument.find('input[name=' + selector + ']'); - input.val(value); - input.trigger('change'); - this.testWindow.angular.element(input[0]).trigger('change'); - done(); - }); - } - }; -} - -function expect(selector) { - return { - toEqual: function(expected) { - $scenario.addStep("Expect that " + selector + " equals '" + expected + "'", function(done){ - var attrName = selector.substring(2, selector.length - 2); - var binding = this.testDocument.find('span[ng-bind=' + attrName + ']'); - if (binding.text() != expected) { - this.result.fail("Expected '" + expected + "' but was '" + binding.text() + "'"); - } - done(); - }); - } - }; -} - -describe('widgets', function(){ - it('should verify that basic widgets work', function(){ - browser.navigateTo('widgets.html'); - expect('{{text.basic}}').toEqual(''); - input('text.basic').enter('John'); - expect('{{text.basic}}').toEqual('John'); - }); -}); diff --git a/scenario/widgets-scenarios.js b/scenario/widgets-scenarios.js deleted file mode 100644 index a1e6c0ed..00000000 --- a/scenario/widgets-scenarios.js +++ /dev/null @@ -1,49 +0,0 @@ -angular.scenarioDef.widgets = { - $before:[ - {Given:"browser", at:"widgets.html"} - ], - checkWidgetBinding:[ - {Then:"text", at:"{{text.basic}}", should_be:""}, - {When:"enter", text:"John", at:":input[name=text.basic]"}, - {Then:"text", at:"{{text.basic}}", should_be:"John"}, - - {Then:"text", at:"{{gender}}", should_be:"male"}, - {When:"click", at:"input:radio[value=female]"}, - {Then:"text", at:"{{gender}}", should_be:"female"}, - - {Then:"text", at:"{{tea}}", should_be:"on"}, - {When:"click", at:"input[name=tea]"}, - {Then:"text", at:"{{tea}}", should_be:""}, - - {Then:"text", at:"{{coffee}}", should_be:""}, - {When:"click", at:"input[name=coffee]"}, - {Then:"text", at:"{{coffee}}", should_be:"on"}, - - {Then:"text", at:"{{count}}", should_be:0}, - {When:"click", at:"form :button"}, - {When:"click", at:"form :submit"}, - {When:"click", at:"form :image"}, - {Then:"text", at:"{{count}}", should_be:3}, - - {Then:"text", at:"{{select}}", should_be:"A"}, - {When:"select", at:"select[name=select]", option:"B"}, - {Then:"text", at:"{{select}}", should_be:"B"}, - - {Then:"text", at:"{{multiple}}", should_be:"[]"}, - {When:"select", at:"select[name=multiple]", option:"A"}, - {Then:"text", at:"{{multiple}}", should_be:["A"]}, - {When:"select", at:"select[name=multiple]", option:"B"}, - {Then:"text", at:"{{multiple}}", should_be:["A", "B"]}, - {When:"select", at:"select[name=multiple]", option:"A"}, - {Then:"text", at:"{{multiple}}", should_be:["B"]}, - - {Then:"text", at:"{{hidden}}", should_be:"hiddenValue"}, - - {Then:"text", at:"{{password}}", should_be:"passwordValue"}, - {When:"enter", text:"reset", at:":input[name=password]"}, - {Then:"text", at:"{{password}}", should_be:"reset"}, - ], - checkNewWidgetEmpty:[ - {Then:"text", at:"{{name}}", should_be:""}, - ] -}; diff --git a/scenario/widgets-scenarios.old b/scenario/widgets-scenarios.old new file mode 100644 index 00000000..a1e6c0ed --- /dev/null +++ b/scenario/widgets-scenarios.old @@ -0,0 +1,49 @@ +angular.scenarioDef.widgets = { + $before:[ + {Given:"browser", at:"widgets.html"} + ], + checkWidgetBinding:[ + {Then:"text", at:"{{text.basic}}", should_be:""}, + {When:"enter", text:"John", at:":input[name=text.basic]"}, + {Then:"text", at:"{{text.basic}}", should_be:"John"}, + + {Then:"text", at:"{{gender}}", should_be:"male"}, + {When:"click", at:"input:radio[value=female]"}, + {Then:"text", at:"{{gender}}", should_be:"female"}, + + {Then:"text", at:"{{tea}}", should_be:"on"}, + {When:"click", at:"input[name=tea]"}, + {Then:"text", at:"{{tea}}", should_be:""}, + + {Then:"text", at:"{{coffee}}", should_be:""}, + {When:"click", at:"input[name=coffee]"}, + {Then:"text", at:"{{coffee}}", should_be:"on"}, + + {Then:"text", at:"{{count}}", should_be:0}, + {When:"click", at:"form :button"}, + {When:"click", at:"form :submit"}, + {When:"click", at:"form :image"}, + {Then:"text", at:"{{count}}", should_be:3}, + + {Then:"text", at:"{{select}}", should_be:"A"}, + {When:"select", at:"select[name=select]", option:"B"}, + {Then:"text", at:"{{select}}", should_be:"B"}, + + {Then:"text", at:"{{multiple}}", should_be:"[]"}, + {When:"select", at:"select[name=multiple]", option:"A"}, + {Then:"text", at:"{{multiple}}", should_be:["A"]}, + {When:"select", at:"select[name=multiple]", option:"B"}, + {Then:"text", at:"{{multiple}}", should_be:["A", "B"]}, + {When:"select", at:"select[name=multiple]", option:"A"}, + {Then:"text", at:"{{multiple}}", should_be:["B"]}, + + {Then:"text", at:"{{hidden}}", should_be:"hiddenValue"}, + + {Then:"text", at:"{{password}}", should_be:"passwordValue"}, + {When:"enter", text:"reset", at:":input[name=password]"}, + {Then:"text", at:"{{password}}", should_be:"reset"}, + ], + checkNewWidgetEmpty:[ + {Then:"text", at:"{{name}}", should_be:""}, + ] +}; diff --git a/scenario/widgets.html b/scenario/widgets.html index 4d0f30b0..86269e86 100644 --- a/scenario/widgets.html +++ b/scenario/widgets.html @@ -26,8 +26,8 @@ hidden - - hidden={{hidden}} + + text.hidden={{text.hidden}} Input selection field diff --git a/src/scenario/DSL.js b/src/scenario/DSL.js new file mode 100644 index 00000000..4bc21d6c --- /dev/null +++ b/src/scenario/DSL.js @@ -0,0 +1,47 @@ +angular.scenario.dsl.browser = { + navigateTo: function(url){ + $scenario.addStep('Navigate to: ' + url, function(done){ + var self = this; + self.testFrame.load(function(){ + self.testFrame.unbind(); + self.testDocument = jQuery(self.testWindow.document); + done(); + }); + if (this.testFrame.attr('src') == url) { + this.testWindow.location.reload(); + } else { + this.testFrame.attr('src', url); + } + }); + } +}; + +angular.scenario.dsl.input = function(selector) { + return { + enter: function(value){ + $scenario.addStep("Set input text of '" + selector + "' to value '" + + value + "'", function(done){ + var input = this.testDocument.find('input[name=' + selector + ']'); + input.val(value); + input.trigger('change'); + this.testWindow.angular.element(input[0]).trigger('change'); + done(); + }); + } + }; +}; + +angular.scenario.dsl.expect = function(selector) { + return { + toEqual: function(expected) { + $scenario.addStep("Expect that " + selector + " equals '" + expected + "'", function(done){ + var attrName = selector.substring(2, selector.length - 2); + var binding = this.testDocument.find('span[ng-bind=' + attrName + ']'); + if (binding.text() != expected) { + this.result.fail("Expected '" + expected + "' but was '" + binding.text() + "'"); + } + done(); + }); + } + }; +}; diff --git a/src/scenario/Runner.js b/src/scenario/Runner.js index 9e20d394..003ce487 100644 --- a/src/scenario/Runner.js +++ b/src/scenario/Runner.js @@ -1,9 +1,11 @@ -angular['scenario'] = (angular['scenario'] = {}); +angular['scenario'] = (angular['scenario'] = {}); +angular.scenario['dsl'] = (angular.scenario['dsl'] = {}); angular.scenario.Runner = function(scope, jQuery){ var self = scope.$scenario = this; this.scope = scope; this.jQuery = jQuery; + angular.extend(scope, angular.scenario.dsl); var specs = this.specs = {}; var path = []; @@ -18,7 +20,13 @@ angular.scenario.Runner = function(scope, jQuery){ name: specName, steps:[] }; - body(); + try { + body(); + } catch(err) { + self.addStep(err.message || 'ERROR', function(){ + throw err; + }); + } self.currentSpec = null; }; this.logger = function returnNoop(){ @@ -55,6 +63,7 @@ angular.scenario.Runner.prototype = { return angular.extend(logger(element), { close: function(){ element.removeClass('running'); + console.scrollTop(console[0].scrollHeight); }, fail: function(){ element.removeClass('running'); @@ -66,7 +75,7 @@ angular.scenario.Runner.prototype = { current = current.parent(); } } - });; + }); }; } this.logger = logger(console); diff --git a/src/scenario/bootstrap.js b/src/scenario/bootstrap.js index 51d24c38..4c9cdc8d 100644 --- a/src/scenario/bootstrap.js +++ b/src/scenario/bootstrap.js @@ -29,6 +29,7 @@ addScript("../../lib/jquery/jquery-1.4.2.js"); addScript("../angular-bootstrap.js"); addScript("Runner.js"); + addScript("DSL.js"); document.write(''); diff --git a/test/scenario/RunnerSpec.js b/test/scenario/RunnerSpec.js index 702e1ab5..35d74f51 100644 --- a/test/scenario/RunnerSpec.js +++ b/test/scenario/RunnerSpec.js @@ -37,7 +37,23 @@ describe('Runner', function(){ }); it('should camplain on duplicate it', angular.noop); - + it('should create a failing step if there is a javascript error', function(){ + var spec; + Describe('D1', function(){ + It('I1', function(){ + spec = $scenario.currentSpec; + throw {message: 'blah'}; + }); + }); + var step = spec.steps[0]; + expect(step.name).toEqual('blah'); + try { + step.fn(); + fail(); + } catch (e) { + expect(e.message).toEqual('blah'); + }; + }); }); }); -- cgit v1.2.3 From 55c0767f16e60e77e9d1b4d46698ddbf343ed8b1 Mon Sep 17 00:00:00 2001 From: Andres Ornelas Date: Mon, 24 May 2010 17:48:17 -0700 Subject: added dsl tests and select method --- src/scenario/DSL.js | 14 ++++++++++++-- test/scenario/DSLSpec.js | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 test/scenario/DSLSpec.js diff --git a/src/scenario/DSL.js b/src/scenario/DSL.js index 4bc21d6c..842f7c7a 100644 --- a/src/scenario/DSL.js +++ b/src/scenario/DSL.js @@ -19,14 +19,24 @@ angular.scenario.dsl.browser = { angular.scenario.dsl.input = function(selector) { return { enter: function(value){ - $scenario.addStep("Set input text of '" + selector + "' to value '" + + $scenario.addStep("Set input text of '" + selector + "' to '" + value + "'", function(done){ var input = this.testDocument.find('input[name=' + selector + ']'); input.val(value); - input.trigger('change'); this.testWindow.angular.element(input[0]).trigger('change'); done(); }); + }, + select: function(value){ + $scenario.addStep("Select radio '" + selector + "' to '" + + value + "'", function(done){ + var input = this.testDocument. + find(':radio[name$=@' + selector + '][value=' + value + ']'); + var event = this.testWindow.document.createEvent('MouseEvent'); + event.initMouseEvent('click', true, true, this.testWindow, 0,0,0,0,0, false, false, false, false, 0, null); + input[0].dispatchEvent(event); + done(); + }); } }; }; diff --git a/test/scenario/DSLSpec.js b/test/scenario/DSLSpec.js new file mode 100644 index 00000000..3c16876d --- /dev/null +++ b/test/scenario/DSLSpec.js @@ -0,0 +1,40 @@ +describe("DSL", function() { + + var lastStep, executeStep, lastDocument; + + beforeEach(function() { + lastStep = null; + $scenario = { + addStep: function(name, stepFunction) { + lastStep = { name:name, fn: stepFunction}; + } + }; + executeStep = function(step, html, callback) { + lastDocument =_jQuery('
                    ' + html + '
                    '); + var specThis = { + testWindow: window, + testDocument: lastDocument + }; + step.fn.call(specThis, callback || noop); + }; + }); + + describe("input", function() { + + var input = angular.scenario.dsl.input; + it('should enter', function() { + input('name').enter('John'); + expect(lastStep.name).toEqual("Set input text of 'name' to 'John'"); + executeStep(lastStep, ''); + expect(lastDocument.find('input').val()).toEqual('John'); + }); + + it('should select', function() { + input('gender').select('female'); + expect(lastStep.name).toEqual("Select radio 'gender' to 'female'"); + executeStep(lastStep, '' + + ''); + expect(lastDocument.find(':radio:checked').val()).toEqual('female'); + }); + }); +}); \ No newline at end of file -- cgit v1.2.3 From 4fec828cf6ce896452945c1dd8aa2db61e2bc746 Mon Sep 17 00:00:00 2001 From: Andres Ornelas Date: Tue, 25 May 2010 09:52:52 -0700 Subject: appended lastDocument to the document.body --- test/scenario/DSLSpec.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/scenario/DSLSpec.js b/test/scenario/DSLSpec.js index 3c16876d..bed1f008 100644 --- a/test/scenario/DSLSpec.js +++ b/test/scenario/DSLSpec.js @@ -11,6 +11,7 @@ describe("DSL", function() { }; executeStep = function(step, html, callback) { lastDocument =_jQuery('
                    ' + html + '
                    '); + _jQuery(document.body).append(lastDocument); var specThis = { testWindow: window, testDocument: lastDocument @@ -32,8 +33,10 @@ describe("DSL", function() { it('should select', function() { input('gender').select('female'); expect(lastStep.name).toEqual("Select radio 'gender' to 'female'"); - executeStep(lastStep, '' + + executeStep(lastStep, + '' + ''); + expect(lastDocument.find(':radio:checked').length).toEqual(1); expect(lastDocument.find(':radio:checked').val()).toEqual('female'); }); }); -- cgit v1.2.3 From 2cce1ffc15ae6483da9cf354f7a5d2d26317427e Mon Sep 17 00:00:00 2001 From: Andres Ornelas Date: Tue, 25 May 2010 13:05:23 -0700 Subject: fixed collapsed issue --- scenario/widgets-scenario.js | 5 +++++ src/scenario/Runner.js | 5 +++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/scenario/widgets-scenario.js b/scenario/widgets-scenario.js index 9e23d4df..f4488190 100644 --- a/scenario/widgets-scenario.js +++ b/scenario/widgets-scenario.js @@ -14,7 +14,12 @@ describe('widgets', function(){ expect('{{gender}}').toEqual('male'); input('gender').select('female'); + input('gender').isChecked('female'); expect('{{gender}}').toEqual('female'); +// expect('{{tea}}').toBeChecked(); +// input('gender').select('female'); +// expect('{{gender}}').toEqual('female'); + }); }); diff --git a/src/scenario/Runner.js b/src/scenario/Runner.js index 003ce487..8669f56b 100644 --- a/src/scenario/Runner.js +++ b/src/scenario/Runner.js @@ -57,12 +57,14 @@ angular.scenario.Runner.prototype = { container = jQuery('
                      '); parent.append(container); } - var element = jQuery(''); + var element = jQuery('
                    • '); element.find('span').text(text); container.append(element); return angular.extend(logger(element), { close: function(){ element.removeClass('running'); + if(!element.hasClass('fail')) + element.addClass('collapsed'); console.scrollTop(console[0].scrollHeight); }, fail: function(){ @@ -71,7 +73,6 @@ angular.scenario.Runner.prototype = { while (current[0] != console[0]) { if (current.is('li')) current.addClass('fail'); - current.removeClass('collapsed'); current = current.parent(); } } -- cgit v1.2.3 From 5992e81b2e302c3b3375567e347227f6a9496585 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Tue, 25 May 2010 14:23:52 -0700 Subject: added rake task to create a single file for scenario runner --- .gitignore | 1 + Rakefile | 26 ++++++++++++++++++++++++-- scenario/Runner-compiled.html | 9 +++++++++ src/scenario/DSL.js | 1 + src/scenario/Runner.js | 18 +++++++++--------- src/scenario/angular.prefix | 30 ++++++++++++++++++++++++++++++ src/scenario/angular.suffix | 11 +++++++++++ src/scenario/bootstrap.js | 9 ++++++++- 8 files changed, 93 insertions(+), 12 deletions(-) create mode 100644 scenario/Runner-compiled.html create mode 100644 src/scenario/angular.prefix create mode 100644 src/scenario/angular.suffix diff --git a/.gitignore b/.gitignore index e600f4cd..2631c477 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ externs.js angular.js angular-minified.js angular-debug.js +angular-scenario.js diff --git a/Rakefile b/Rakefile index 6f6ee8ad..a6edb860 100644 --- a/Rakefile +++ b/Rakefile @@ -3,7 +3,7 @@ include FileUtils task :default => [:compile, :test] desc 'Generate Externs' -task :compileexterns do +task :compile_externs do out = File.new("externs.js", "w") out.write("function _(){};\n") @@ -29,9 +29,31 @@ task :compileexterns do out.close end +desc 'Compile Scenario' +task :compile_scenario do + concat = %x(cat \ + lib/underscore/underscore.js \ + lib/jquery/jquery-1.4.2.js \ + src/scenario/angular.prefix \ + src/Angular.js \ + src/JSON.js \ + src/scenario/Runner.js \ + src/scenario/DSL.js \ + src/scenario/angular.suffix \ + ) + css = %x(cat css/angular-scenario.css) + f = File.new("angular-scenario.js", 'w') + f.write(concat) + f.write('document.write(\'\');') + f.close +end + desc 'Compile JavaScript' task :compile do - Rake::Task['compileexterns'].execute 0 + Rake::Task['compile_externs'].execute 0 + Rake::Task['compile_scenario'].execute 0 concat = %x(cat \ src/angular.prefix \ diff --git a/scenario/Runner-compiled.html b/scenario/Runner-compiled.html new file mode 100644 index 00000000..98dbea69 --- /dev/null +++ b/scenario/Runner-compiled.html @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/scenario/DSL.js b/src/scenario/DSL.js index 842f7c7a..8cbb256d 100644 --- a/src/scenario/DSL.js +++ b/src/scenario/DSL.js @@ -5,6 +5,7 @@ angular.scenario.dsl.browser = { self.testFrame.load(function(){ self.testFrame.unbind(); self.testDocument = jQuery(self.testWindow.document); + self.testWindow = self.testFrame[0].contentWindow; done(); }); if (this.testFrame.attr('src') == url) { diff --git a/src/scenario/Runner.js b/src/scenario/Runner.js index 8669f56b..01e16e79 100644 --- a/src/scenario/Runner.js +++ b/src/scenario/Runner.js @@ -1,11 +1,10 @@ -angular['scenario'] = (angular['scenario'] = {}); -angular.scenario['dsl'] = (angular.scenario['dsl'] = {}); +angular['scenario'] = angular['scenario'] || (angular['scenario'] = {}); +angular.scenario['dsl'] = angular.scenario['dsl'] || (angular.scenario['dsl'] = {}); angular.scenario.Runner = function(scope, jQuery){ var self = scope.$scenario = this; this.scope = scope; this.jQuery = jQuery; - angular.extend(scope, angular.scenario.dsl); var specs = this.specs = {}; var path = []; @@ -30,7 +29,7 @@ angular.scenario.Runner = function(scope, jQuery){ self.currentSpec = null; }; this.logger = function returnNoop(){ - return angular.extend(returnNoop, {close:angular.noop, fail:angular.noop});; + return _(returnNoop).extend({close:_.identity, fail:_.identity});; }; }; @@ -60,7 +59,7 @@ angular.scenario.Runner.prototype = { var element = jQuery('
                    • '); element.find('span').text(text); container.append(element); - return angular.extend(logger(element), { + return _(logger(element)).extend({ close: function(){ element.removeClass('running'); if(!element.hasClass('fail')) @@ -81,7 +80,7 @@ angular.scenario.Runner.prototype = { } this.logger = logger(console); var specNames = []; - angular.foreach(this.specs, function(spec, name){ + _(this.specs).each(function(spec, name){ specNames.push(name); }, this); specNames.sort(); @@ -109,7 +108,7 @@ angular.scenario.Runner.prototype = { result.passed = false; result.failed = true; result.error = error; - result.log('fail', angular.isString(error) ? error : angular.toJson(error)).fail(); + result.log('fail', _(error).isString() ? error : toJson(error)).fail(); } }; specThis = { @@ -122,11 +121,11 @@ angular.scenario.Runner.prototype = { function done() { result.finished = true; stepLogger.close(); - (callback||angular.noop).call(specThis); + (callback||_.identity).call(specThis); } function next(){ var step = spec.steps[spec.nextStepIndex]; - (result.log || {close:angular.noop}).close(); + (result.log || {close:_.identity}).close(); result.log = null; if (step) { spec.nextStepIndex ++; @@ -134,6 +133,7 @@ angular.scenario.Runner.prototype = { try { step.fn.call(specThis, next); } catch (e) { + console.error(e); result.fail(e); done(); } diff --git a/src/scenario/angular.prefix b/src/scenario/angular.prefix new file mode 100644 index 00000000..5b44e17c --- /dev/null +++ b/src/scenario/angular.prefix @@ -0,0 +1,30 @@ +/** + * The MIT License + * + * Copyright (c) 2010 Adam Abrons and Misko Hevery http://getangular.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +(function(window, document, previousOnLoad){ + window.angular = { + scenario: { + dsl: window + } + }; + diff --git a/src/scenario/angular.suffix b/src/scenario/angular.suffix new file mode 100644 index 00000000..fc861cbf --- /dev/null +++ b/src/scenario/angular.suffix @@ -0,0 +1,11 @@ + + var $scenarioRunner = new angular.scenario.Runner(window, jQuery); + + window.onload = function(){ + try { + if (previousOnLoad) previousOnLoad(); + } catch(e) {} + $scenarioRunner.run(jQuery(window.document.body)); + }; + +})(window, document, window.onload); diff --git a/src/scenario/bootstrap.js b/src/scenario/bootstrap.js index 4c9cdc8d..694d0e97 100644 --- a/src/scenario/bootstrap.js +++ b/src/scenario/bootstrap.js @@ -18,6 +18,12 @@ document.write(''); } + window.angular = { + scenario: { + dsl: window + } + }; + window.onload = function(){ _.defer(function(){ $scenarioRunner.run(jQuery(window.document.body)); @@ -27,8 +33,9 @@ addCSS("../../css/angular-scenario.css"); addScript("../../lib/underscore/underscore.js"); addScript("../../lib/jquery/jquery-1.4.2.js"); - addScript("../angular-bootstrap.js"); addScript("Runner.js"); + addScript("../Angular.js"); + addScript("../JSON.js"); addScript("DSL.js"); document.write(' + + + + +
                      +
                        +
                      • + {{item.name}} {{item.parts.join(', ')}} +
                      • +
                      + + diff --git a/src/Scope.js b/src/Scope.js index fe0b6ce3..70869b3b 100644 --- a/src/Scope.js +++ b/src/Scope.js @@ -60,11 +60,12 @@ function expressionCompile(exp){ // TODO(remove this hack) function parserNewScopeAdapter(fn) { return function(){ + var self = this; return fn({ - state: this, + state: self, scope: { - set: this.$set, - get: this.$get + set: function(key, value){ return setter(self, key, value);}, + get: function(key) { return getter(self, key); } } }); }; diff --git a/src/apis.js b/src/apis.js index 5864ad60..166ca27c 100644 --- a/src/apis.js +++ b/src/apis.js @@ -297,7 +297,7 @@ var angularFunction = { return expression; } else if (expression){ return function($) { - return createScope($).$eval(expression); + return expressionCompile(expression).apply($); }; } else { return identity; -- cgit v1.2.3 From 0d87208553af7a9ad6c6c1c73bd3a4105062eafa Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Sun, 30 May 2010 16:11:00 -0700 Subject: remove the relience of parser an special self object, now passing generic self --- src/Parser.js | 16 ++++++++-------- src/Scope.js | 9 +-------- src/directives.js | 3 +-- src/widgets.js | 2 +- 4 files changed, 11 insertions(+), 19 deletions(-) diff --git a/src/Parser.js b/src/Parser.js index dfe56cc9..cfb72c72 100644 --- a/src/Parser.js +++ b/src/Parser.js @@ -17,7 +17,7 @@ Lexer.OPERATORS = { '/':function(self, a,b){return a/b;}, '%':function(self, a,b){return a%b;}, '^':function(self, a,b){return a^b;}, - '=':function(self, a,b){return self.scope.set(a, b);}, + '=':function(self, a,b){return setter(self, a, b);}, '==':function(self, a,b){return a==b;}, '!=':function(self, a,b){return a!=b;}, '<':function(self, a,b){return a diff --git a/src/Parser.js b/src/Parser.js index cfb72c72..df270792 100644 --- a/src/Parser.js +++ b/src/Parser.js @@ -151,9 +151,7 @@ Lexer.prototype = { } var fn = Lexer.OPERATORS[ident]; if (!fn) { - fn = function(self){ - return getter(self, ident); - }; + fn = getterFn(ident); fn.isAssignable = ident; } this.tokens.push({index:start, text:ident, fn:fn}); @@ -563,8 +561,9 @@ Parser.prototype = { fieldAccess: function(object) { var field = this.expect().text; + var getter = getterFn(field); var fn = function (self){ - return getter(object(self), field); + return getter(object(self)); }; fn.isAssignable = field; return fn; diff --git a/src/Scope.js b/src/Scope.js index bed0ff6d..1c223130 100644 --- a/src/Scope.js +++ b/src/Scope.js @@ -43,6 +43,41 @@ function setter(instance, path, value){ return value; } +/////////////////////////////////// + +var getterFnCache = {}; +function getterFn(path){ + var fn = getterFnCache[path]; + if (fn) return fn; + + var code = 'function (self){\n'; + code += ' var last, fn, type;\n'; + foreach(path.split('.'), function(key) { + code += ' if(!self) return self;\n'; + code += ' last = self;\n'; + code += ' self = self.' + key + ';\n'; + code += ' if(typeof self == "function") \n'; + code += ' self = function(){ return last.'+key+'.apply(last, arguments); };\n'; + if (key.charAt(0) == '$') { + // special code for super-imposed functions + var name = key.substr(1); + code += ' if(!self) {\n'; + code += ' type = angular.Global.typeOf(last);\n'; + code += ' fn = (angular[type.charAt(0).toUpperCase() + type.substring(1)]||{})["' + name + '"];\n'; + code += ' if (fn)\n'; + code += ' self = function(){ return fn.apply(last, [last].concat(slice.call(arguments, 0, arguments.length))); };\n'; + code += ' }\n'; + } + }); + code += ' return self;\n}'; + fn = eval('(' + code + ')'); + fn.toString = function(){ return code; }; + + return getterFnCache[path] = fn; +} + +/////////////////////////////////// + var compileCache = {}; function expressionCompile(exp){ if (isFunction(exp)) return exp; diff --git a/test/ScopeSpec.js b/test/ScopeSpec.js index a3b6d9ae..d93400e5 100644 --- a/test/ScopeSpec.js +++ b/test/ScopeSpec.js @@ -157,4 +157,25 @@ describe('scope/model', function(){ } }); }); + + describe('getterFn', function(){ + it('should get chain', function(){ + expect(getterFn('a.b')(undefined)).toEqual(undefined); + expect(getterFn('a.b')({})).toEqual(undefined); + expect(getterFn('a.b')({a:null})).toEqual(undefined); + expect(getterFn('a.b')({a:{}})).toEqual(undefined); + expect(getterFn('a.b')({a:{b:null}})).toEqual(null); + expect(getterFn('a.b')({a:{b:0}})).toEqual(0); + expect(getterFn('a.b')({a:{b:'abc'}})).toEqual('abc'); + }); + + it('should map type method on top of expression', function(){ + expect(getterFn('a.$filter')({a:[]})('')).toEqual([]); + }); + + it('should bind function this', function(){ + expect(getterFn('a')({a:function($){return this.b + $;}, b:1})(2)).toEqual(3); + + }); + }); }); -- cgit v1.2.3 From 81dac70e72430b7ab9a824ab923038c1e00e7003 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Sun, 30 May 2010 20:21:40 -0700 Subject: imrove $orderBy performance --- src/Angular.js | 13 ++++++------- src/apis.js | 4 +++- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Angular.js b/src/Angular.js index 39e9b385..2b26c88d 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -244,18 +244,17 @@ function copy(source, destination){ while(destination.length) { destination.pop(); } + for ( var i = 0; i < source.length; i++) { + destination.push(copy(source[i])); + } } else { foreach(destination, function(value, key){ delete destination[key]; }); + for ( var key in source) { + destination[key] = copy(source[key]); + } } - foreach(source, function(value, key){ - destination[key] = value ? - ( isArray(value) ? - copy(value, []) : - (isObject(value) ? copy(value, {}) : value)) : - value; - }); return destination; } } diff --git a/src/apis.js b/src/apis.js index 0038a6e1..306d0ce8 100644 --- a/src/apis.js +++ b/src/apis.js @@ -190,7 +190,9 @@ var angularArray = { } return 0; }; - return copy(array).sort(reverse(comparator, descend)); + var arrayCopy = []; + for ( var i = 0; i < array.length; i++) { arrayCopy.push(array[i]); } + return arrayCopy.sort(reverse(comparator, descend)); }, 'orderByToggle':function(predicate, attribute) { var STRIP = /^([+|-])?(.*)/; -- cgit v1.2.3 From 6143b04384680d17f38c2d5894a9b9961ea33288 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Mon, 31 May 2010 00:58:29 -0700 Subject: removed few key foreach and replaced thime with for loop for performance. --- scenario/perf.html | 3 ++- src/Scope.js | 21 ++++++++++++--------- src/directives.js | 51 ++++++++++++++++++++++++++++++--------------------- 3 files changed, 44 insertions(+), 31 deletions(-) diff --git a/scenario/perf.html b/scenario/perf.html index e4edc00f..cd676918 100644 --- a/scenario/perf.html +++ b/scenario/perf.html @@ -24,7 +24,8 @@
                      diff --git a/src/Scope.js b/src/Scope.js index 1c223130..637fc25e 100644 --- a/src/Scope.js +++ b/src/Scope.js @@ -53,14 +53,15 @@ function getterFn(path){ var code = 'function (self){\n'; code += ' var last, fn, type;\n'; foreach(path.split('.'), function(key) { + key = (key == 'this') ? '["this"]' : '.' + key; code += ' if(!self) return self;\n'; code += ' last = self;\n'; - code += ' self = self.' + key + ';\n'; + code += ' self = self' + key + ';\n'; code += ' if(typeof self == "function") \n'; - code += ' self = function(){ return last.'+key+'.apply(last, arguments); };\n'; - if (key.charAt(0) == '$') { + code += ' self = function(){ return last'+key+'.apply(last, arguments); };\n'; + if (key.charAt(1) == '$') { // special code for super-imposed functions - var name = key.substr(1); + var name = key.substr(2); code += ' if(!self) {\n'; code += ' type = angular.Global.typeOf(last);\n'; code += ' fn = (angular[type.charAt(0).toUpperCase() + type.substring(1)]||{})["' + name + '"];\n'; @@ -123,11 +124,13 @@ function createScope(parent, services, existing) { if (exp !== undefined) { return expressionCompile(exp).apply(instance, slice.call(arguments, 1, arguments.length)); } else { - foreach(evalLists.sorted, function(list) { - foreach(list, function(eval) { - instance.$tryEval(eval.fn, eval.handler); - }); - }); + for ( var i = 0, iSize = evalLists.sorted.length; i < iSize; i++) { + for ( var queue = evalLists.sorted[i], + jSize = queue.length, + j= 0; j < jSize; j++) { + instance.$tryEval(queue[j].fn, queue[j].handler); + } + } } }, diff --git a/src/directives.js b/src/directives.js index 91a98735..cabf0c23 100644 --- a/src/directives.js +++ b/src/directives.js @@ -24,14 +24,17 @@ angularDirective("ng-eval", function(expression){ angularDirective("ng-bind", function(expression){ return function(element) { - var lastValue, lastError; + var lastValue = noop, lastError = noop; this.$onEval(function() { var error, value = this.$tryEval(expression, function(e){ error = toJson(e); }), - isHtml = value instanceof HTML, - isDomElement = isElement(value); + isHtml, + isDomElement; + if (lastValue === value && lastError == error) return; + isHtml = value instanceof HTML, + isDomElement = isElement(value); if (!isHtml && !isDomElement && isObject(value)) { value = toJson(value); } @@ -72,14 +75,14 @@ function compileBindTemplate(template){ }); bindTemplateCache[template] = fn = function(element){ var parts = [], self = this; - foreach(bindings, function(fn){ - var value = fn.call(self, element); + for ( var i = 0; i < bindings.length; i++) { + var value = bindings[i].call(self, element); if (isElement(value)) value = ''; else if (isObject(value)) value = toJson(value, true); parts.push(value); - }); + }; return parts.join(''); }; } @@ -107,21 +110,26 @@ var REMOVE_ATTRIBUTES = { }; angularDirective("ng-bind-attr", function(expression){ return function(element){ + var lastValue = {}; this.$onEval(function(){ - foreach(this.$eval(expression), function(bindExp, key) { - var value = compileBindTemplate(bindExp).call(this, element), + var values = this.$eval(expression); + for(var key in values) { + var value = compileBindTemplate(values[key]).call(this, element), specialName = REMOVE_ATTRIBUTES[lowercase(key)]; - if (specialName) { - if (element[specialName] = toBoolean(value)) { - element.attr(specialName, value); + if (lastValue[key] !== value) { + lastValue[key] = value; + if (specialName) { + if (element[specialName] = toBoolean(value)) { + element.attr(specialName, value); + } else { + element.removeAttr(key); + } + (element.data('$validate')||noop)(); } else { - element.removeAttr(key); + element.attr(key, value); } - (element.data('$validate')||noop)(); - } else { - element.attr(key, value); } - }, this); + }; }, element); }; }); @@ -153,17 +161,18 @@ angularWidget("@ng-repeat", function(expression, element){ var children = [], currentScope = this; this.$onEval(function(){ - var index = 0, childCount = children.length, childScope, lastElement = reference; - foreach(this.$tryEval(rhs, reference), function(value, key){ + var index = 0, childCount = children.length, childScope, lastElement = reference, + collection = this.$tryEval(rhs, reference); + for ( var key in collection) { if (index < childCount) { // reuse existing child childScope = children[index]; - childScope[valueIdent] = value; + childScope[valueIdent] = collection[key]; if (keyIdent) childScope[keyIdent] = key; } else { // grow children childScope = template(element.clone(), createScope(currentScope)); - childScope[valueIdent] = value; + childScope[valueIdent] = collection[key]; if (keyIdent) childScope[keyIdent] = key; lastElement.after(childScope.$element); childScope.$index = index; @@ -174,7 +183,7 @@ angularWidget("@ng-repeat", function(expression, element){ childScope.$eval(); lastElement = childScope.$element; index ++; - }); + }; // shrink children while(children.length > index) { children.pop().$element.remove(); -- cgit v1.2.3 From a29c5e4c7fd5e708c28e70e974bf873621d5277c Mon Sep 17 00:00:00 2001 From: Shyam Seshadri Date: Wed, 2 Jun 2010 15:04:26 -0700 Subject: Revert "removed few key foreach and replaced thime with for loop for performance." This reverts commit 6143b04384680d17f38c2d5894a9b9961ea33288. --- scenario/perf.html | 3 +-- src/Scope.js | 21 +++++++++------------ src/directives.js | 51 +++++++++++++++++++++------------------------------ 3 files changed, 31 insertions(+), 44 deletions(-) diff --git a/scenario/perf.html b/scenario/perf.html index cd676918..e4edc00f 100644 --- a/scenario/perf.html +++ b/scenario/perf.html @@ -24,8 +24,7 @@
                      diff --git a/src/Scope.js b/src/Scope.js index 637fc25e..1c223130 100644 --- a/src/Scope.js +++ b/src/Scope.js @@ -53,15 +53,14 @@ function getterFn(path){ var code = 'function (self){\n'; code += ' var last, fn, type;\n'; foreach(path.split('.'), function(key) { - key = (key == 'this') ? '["this"]' : '.' + key; code += ' if(!self) return self;\n'; code += ' last = self;\n'; - code += ' self = self' + key + ';\n'; + code += ' self = self.' + key + ';\n'; code += ' if(typeof self == "function") \n'; - code += ' self = function(){ return last'+key+'.apply(last, arguments); };\n'; - if (key.charAt(1) == '$') { + code += ' self = function(){ return last.'+key+'.apply(last, arguments); };\n'; + if (key.charAt(0) == '$') { // special code for super-imposed functions - var name = key.substr(2); + var name = key.substr(1); code += ' if(!self) {\n'; code += ' type = angular.Global.typeOf(last);\n'; code += ' fn = (angular[type.charAt(0).toUpperCase() + type.substring(1)]||{})["' + name + '"];\n'; @@ -124,13 +123,11 @@ function createScope(parent, services, existing) { if (exp !== undefined) { return expressionCompile(exp).apply(instance, slice.call(arguments, 1, arguments.length)); } else { - for ( var i = 0, iSize = evalLists.sorted.length; i < iSize; i++) { - for ( var queue = evalLists.sorted[i], - jSize = queue.length, - j= 0; j < jSize; j++) { - instance.$tryEval(queue[j].fn, queue[j].handler); - } - } + foreach(evalLists.sorted, function(list) { + foreach(list, function(eval) { + instance.$tryEval(eval.fn, eval.handler); + }); + }); } }, diff --git a/src/directives.js b/src/directives.js index cabf0c23..91a98735 100644 --- a/src/directives.js +++ b/src/directives.js @@ -24,17 +24,14 @@ angularDirective("ng-eval", function(expression){ angularDirective("ng-bind", function(expression){ return function(element) { - var lastValue = noop, lastError = noop; + var lastValue, lastError; this.$onEval(function() { var error, value = this.$tryEval(expression, function(e){ error = toJson(e); }), - isHtml, - isDomElement; - if (lastValue === value && lastError == error) return; - isHtml = value instanceof HTML, - isDomElement = isElement(value); + isHtml = value instanceof HTML, + isDomElement = isElement(value); if (!isHtml && !isDomElement && isObject(value)) { value = toJson(value); } @@ -75,14 +72,14 @@ function compileBindTemplate(template){ }); bindTemplateCache[template] = fn = function(element){ var parts = [], self = this; - for ( var i = 0; i < bindings.length; i++) { - var value = bindings[i].call(self, element); + foreach(bindings, function(fn){ + var value = fn.call(self, element); if (isElement(value)) value = ''; else if (isObject(value)) value = toJson(value, true); parts.push(value); - }; + }); return parts.join(''); }; } @@ -110,26 +107,21 @@ var REMOVE_ATTRIBUTES = { }; angularDirective("ng-bind-attr", function(expression){ return function(element){ - var lastValue = {}; this.$onEval(function(){ - var values = this.$eval(expression); - for(var key in values) { - var value = compileBindTemplate(values[key]).call(this, element), + foreach(this.$eval(expression), function(bindExp, key) { + var value = compileBindTemplate(bindExp).call(this, element), specialName = REMOVE_ATTRIBUTES[lowercase(key)]; - if (lastValue[key] !== value) { - lastValue[key] = value; - if (specialName) { - if (element[specialName] = toBoolean(value)) { - element.attr(specialName, value); - } else { - element.removeAttr(key); - } - (element.data('$validate')||noop)(); + if (specialName) { + if (element[specialName] = toBoolean(value)) { + element.attr(specialName, value); } else { - element.attr(key, value); + element.removeAttr(key); } + (element.data('$validate')||noop)(); + } else { + element.attr(key, value); } - }; + }, this); }, element); }; }); @@ -161,18 +153,17 @@ angularWidget("@ng-repeat", function(expression, element){ var children = [], currentScope = this; this.$onEval(function(){ - var index = 0, childCount = children.length, childScope, lastElement = reference, - collection = this.$tryEval(rhs, reference); - for ( var key in collection) { + var index = 0, childCount = children.length, childScope, lastElement = reference; + foreach(this.$tryEval(rhs, reference), function(value, key){ if (index < childCount) { // reuse existing child childScope = children[index]; - childScope[valueIdent] = collection[key]; + childScope[valueIdent] = value; if (keyIdent) childScope[keyIdent] = key; } else { // grow children childScope = template(element.clone(), createScope(currentScope)); - childScope[valueIdent] = collection[key]; + childScope[valueIdent] = value; if (keyIdent) childScope[keyIdent] = key; lastElement.after(childScope.$element); childScope.$index = index; @@ -183,7 +174,7 @@ angularWidget("@ng-repeat", function(expression, element){ childScope.$eval(); lastElement = childScope.$element; index ++; - }; + }); // shrink children while(children.length > index) { children.pop().$element.remove(); -- cgit v1.2.3 From 39312d1fe3a27b248f98f6f26577fcd7e2c64f85 Mon Sep 17 00:00:00 2001 From: Shyam Seshadri Date: Wed, 2 Jun 2010 15:05:34 -0700 Subject: Revert "Revert "removed few key foreach and replaced thime with for loop for performance."" This reverts commit a29c5e4c7fd5e708c28e70e974bf873621d5277c. --- scenario/perf.html | 3 ++- src/Scope.js | 21 ++++++++++++--------- src/directives.js | 51 ++++++++++++++++++++++++++++++--------------------- 3 files changed, 44 insertions(+), 31 deletions(-) diff --git a/scenario/perf.html b/scenario/perf.html index e4edc00f..cd676918 100644 --- a/scenario/perf.html +++ b/scenario/perf.html @@ -24,7 +24,8 @@
                      diff --git a/src/Scope.js b/src/Scope.js index 1c223130..637fc25e 100644 --- a/src/Scope.js +++ b/src/Scope.js @@ -53,14 +53,15 @@ function getterFn(path){ var code = 'function (self){\n'; code += ' var last, fn, type;\n'; foreach(path.split('.'), function(key) { + key = (key == 'this') ? '["this"]' : '.' + key; code += ' if(!self) return self;\n'; code += ' last = self;\n'; - code += ' self = self.' + key + ';\n'; + code += ' self = self' + key + ';\n'; code += ' if(typeof self == "function") \n'; - code += ' self = function(){ return last.'+key+'.apply(last, arguments); };\n'; - if (key.charAt(0) == '$') { + code += ' self = function(){ return last'+key+'.apply(last, arguments); };\n'; + if (key.charAt(1) == '$') { // special code for super-imposed functions - var name = key.substr(1); + var name = key.substr(2); code += ' if(!self) {\n'; code += ' type = angular.Global.typeOf(last);\n'; code += ' fn = (angular[type.charAt(0).toUpperCase() + type.substring(1)]||{})["' + name + '"];\n'; @@ -123,11 +124,13 @@ function createScope(parent, services, existing) { if (exp !== undefined) { return expressionCompile(exp).apply(instance, slice.call(arguments, 1, arguments.length)); } else { - foreach(evalLists.sorted, function(list) { - foreach(list, function(eval) { - instance.$tryEval(eval.fn, eval.handler); - }); - }); + for ( var i = 0, iSize = evalLists.sorted.length; i < iSize; i++) { + for ( var queue = evalLists.sorted[i], + jSize = queue.length, + j= 0; j < jSize; j++) { + instance.$tryEval(queue[j].fn, queue[j].handler); + } + } } }, diff --git a/src/directives.js b/src/directives.js index 91a98735..cabf0c23 100644 --- a/src/directives.js +++ b/src/directives.js @@ -24,14 +24,17 @@ angularDirective("ng-eval", function(expression){ angularDirective("ng-bind", function(expression){ return function(element) { - var lastValue, lastError; + var lastValue = noop, lastError = noop; this.$onEval(function() { var error, value = this.$tryEval(expression, function(e){ error = toJson(e); }), - isHtml = value instanceof HTML, - isDomElement = isElement(value); + isHtml, + isDomElement; + if (lastValue === value && lastError == error) return; + isHtml = value instanceof HTML, + isDomElement = isElement(value); if (!isHtml && !isDomElement && isObject(value)) { value = toJson(value); } @@ -72,14 +75,14 @@ function compileBindTemplate(template){ }); bindTemplateCache[template] = fn = function(element){ var parts = [], self = this; - foreach(bindings, function(fn){ - var value = fn.call(self, element); + for ( var i = 0; i < bindings.length; i++) { + var value = bindings[i].call(self, element); if (isElement(value)) value = ''; else if (isObject(value)) value = toJson(value, true); parts.push(value); - }); + }; return parts.join(''); }; } @@ -107,21 +110,26 @@ var REMOVE_ATTRIBUTES = { }; angularDirective("ng-bind-attr", function(expression){ return function(element){ + var lastValue = {}; this.$onEval(function(){ - foreach(this.$eval(expression), function(bindExp, key) { - var value = compileBindTemplate(bindExp).call(this, element), + var values = this.$eval(expression); + for(var key in values) { + var value = compileBindTemplate(values[key]).call(this, element), specialName = REMOVE_ATTRIBUTES[lowercase(key)]; - if (specialName) { - if (element[specialName] = toBoolean(value)) { - element.attr(specialName, value); + if (lastValue[key] !== value) { + lastValue[key] = value; + if (specialName) { + if (element[specialName] = toBoolean(value)) { + element.attr(specialName, value); + } else { + element.removeAttr(key); + } + (element.data('$validate')||noop)(); } else { - element.removeAttr(key); + element.attr(key, value); } - (element.data('$validate')||noop)(); - } else { - element.attr(key, value); } - }, this); + }; }, element); }; }); @@ -153,17 +161,18 @@ angularWidget("@ng-repeat", function(expression, element){ var children = [], currentScope = this; this.$onEval(function(){ - var index = 0, childCount = children.length, childScope, lastElement = reference; - foreach(this.$tryEval(rhs, reference), function(value, key){ + var index = 0, childCount = children.length, childScope, lastElement = reference, + collection = this.$tryEval(rhs, reference); + for ( var key in collection) { if (index < childCount) { // reuse existing child childScope = children[index]; - childScope[valueIdent] = value; + childScope[valueIdent] = collection[key]; if (keyIdent) childScope[keyIdent] = key; } else { // grow children childScope = template(element.clone(), createScope(currentScope)); - childScope[valueIdent] = value; + childScope[valueIdent] = collection[key]; if (keyIdent) childScope[keyIdent] = key; lastElement.after(childScope.$element); childScope.$index = index; @@ -174,7 +183,7 @@ angularWidget("@ng-repeat", function(expression, element){ childScope.$eval(); lastElement = childScope.$element; index ++; - }); + }; // shrink children while(children.length > index) { children.pop().$element.remove(); -- cgit v1.2.3 From 3245209bdb9e656622756220c5bbeb80d3ae2eac Mon Sep 17 00:00:00 2001 From: Shyam Seshadri Date: Wed, 2 Jun 2010 17:13:10 -0700 Subject: Add ability to add conditions to ng-required --- src/widgets.js | 11 ++++++++--- test/widgetsSpec.js | 23 +++++++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/widgets.js b/src/widgets.js index 09d743e9..42d56e20 100644 --- a/src/widgets.js +++ b/src/widgets.js @@ -35,16 +35,21 @@ function compileValidator(expr) { function valueAccessor(scope, element) { var validatorName = element.attr('ng-validate') || NOOP, validator = compileValidator(validatorName), - required = element.attr('ng-required'), + requiredExpr = element.attr('ng-required'), farmatterName = element.attr('ng-format') || NOOP, formatter = angularFormatter(farmatterName), - format, parse, lastError; + format, parse, lastError, required; invalidWidgets = scope.$invalidWidgets || {markValid:noop, markInvalid:noop}; if (!validator) throw "Validator named '" + validatorName + "' not found."; if (!formatter) throw "Formatter named '" + farmatterName + "' not found."; format = formatter.format; parse = formatter.parse; - required = required || required === ''; + if (requiredExpr) { + scope.$watch(requiredExpr, function(newValue) {required = newValue; validate();}); + } else { + required = requiredExpr === ''; + } + element.data('$validate', validate); return { diff --git a/test/widgetsSpec.js b/test/widgetsSpec.js index 17120682..dc6050b8 100644 --- a/test/widgetsSpec.js +++ b/test/widgetsSpec.js @@ -221,6 +221,29 @@ describe("widget", function(){ expect(element.attr('ng-validation-error')).toEqual('Required'); }); + it('should allow conditions on ng-required', function() { + compile(''); + scope.$set('ineedz', false); + scope.$eval(); + expect(element.hasClass('ng-validation-error')).toBeFalsy(); + expect(element.attr('ng-validation-error')).toBeFalsy(); + + scope.$set('price', 'xxx'); + scope.$eval(); + expect(element.hasClass('ng-validation-error')).toBeFalsy(); + expect(element.attr('ng-validation-error')).toBeFalsy(); + + scope.$set('ineedz', true); + scope.$eval(); + expect(element.hasClass('ng-validation-error')).toBeFalsy(); + expect(element.attr('ng-validation-error')).toBeFalsy(); + + element.val(''); + element.trigger('keyup'); + expect(element.hasClass('ng-validation-error')).toBeTruthy(); + expect(element.attr('ng-validation-error')).toEqual('Required'); + }); + it("should process ng-required2", function() { compile(''); expect(scope.$get('name')).toEqual("Misko"); -- cgit v1.2.3 From 36b58b235eeca4e9580162a697d8a96c41263ebc Mon Sep 17 00:00:00 2001 From: Shyam Seshadri Date: Thu, 3 Jun 2010 11:03:11 -0700 Subject: fix some lint issues --- src/widgets.js | 6 ++++-- test/widgetsSpec.js | 11 ++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/widgets.js b/src/widgets.js index 42d56e20..5df92de0 100644 --- a/src/widgets.js +++ b/src/widgets.js @@ -45,11 +45,13 @@ function valueAccessor(scope, element) { format = formatter.format; parse = formatter.parse; if (requiredExpr) { - scope.$watch(requiredExpr, function(newValue) {required = newValue; validate();}); + scope.$watch(requiredExpr, function(newValue) { + required = newValue; + validate(); + }); } else { required = requiredExpr === ''; } - element.data('$validate', validate); return { diff --git a/test/widgetsSpec.js b/test/widgetsSpec.js index dc6050b8..40f52b8e 100644 --- a/test/widgetsSpec.js +++ b/test/widgetsSpec.js @@ -233,15 +233,16 @@ describe("widget", function(){ expect(element.hasClass('ng-validation-error')).toBeFalsy(); expect(element.attr('ng-validation-error')).toBeFalsy(); + scope.$set('price', ''); scope.$set('ineedz', true); scope.$eval(); - expect(element.hasClass('ng-validation-error')).toBeFalsy(); - expect(element.attr('ng-validation-error')).toBeFalsy(); - - element.val(''); - element.trigger('keyup'); expect(element.hasClass('ng-validation-error')).toBeTruthy(); expect(element.attr('ng-validation-error')).toEqual('Required'); + + element.val('abc'); + element.trigger('keyup'); + expect(element.hasClass('ng-validation-error')).toBeFalsy(); + expect(element.attr('ng-validation-error')).toBeFalsy(); }); it("should process ng-required2", function() { -- cgit v1.2.3 From fe03ea0d1f8814817bee5a35d745db16858eb490 Mon Sep 17 00:00:00 2001 From: Andres Ornelas Date: Wed, 9 Jun 2010 12:35:40 -0700 Subject: add repeater DSL and fix typo --- src/scenario/DSL.js | 27 +++++++++++++++------------ test/scenario/DSLSpec.js | 14 +++++++++++++- test/scenario/RunnerSpec.js | 2 +- 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/scenario/DSL.js b/src/scenario/DSL.js index b318e99c..a017f29d 100644 --- a/src/scenario/DSL.js +++ b/src/scenario/DSL.js @@ -44,17 +44,20 @@ angular.scenario.dsl.input = function(selector) { }; }; -angular.scenario.dsl.expect = function(selector) { - return { - toEqual: function(expected) { - $scenario.addStep("Expect that " + selector + " equals '" + expected + "'", function(done){ - var attrName = selector.substring(2, selector.length - 2); - var binding = this.testDocument.find('span[ng-bind=' + attrName + ']'); - if (binding.text() != expected) { - this.result.fail("Expected '" + expected + "' but was '" + binding.text() + "'"); +angular.scenario.dsl.expect = { + repeater: function(selector) { + return { + count: { + toEqual: function(number) { + $scenario.addStep("Expect to see " + number + " items repeated with selector '" + selector + "'", function(done) { + var items = this.testDocument.find(selector); + if (items.length != number) { + this.result.fail("Expected " + number + " but was " + items.length); + } + done(); + }); } - done(); - }); - } - }; + } + }; + } }; diff --git a/test/scenario/DSLSpec.js b/test/scenario/DSLSpec.js index bed1f008..0cce7b75 100644 --- a/test/scenario/DSLSpec.js +++ b/test/scenario/DSLSpec.js @@ -40,4 +40,16 @@ describe("DSL", function() { expect(lastDocument.find(':radio:checked').val()).toEqual('female'); }); }); -}); \ No newline at end of file + + describe('expect', function() { + var dslExpect = angular.scenario.dsl.expect; + describe('repeater', function() { + it('should check the count of repeated elements', function() { + dslExpect.repeater('.repeater-row').count.toEqual(2); + expect(lastStep.name).toEqual("Expect to see 2 items repeated with selector '.repeater-row'"); + var html = "
                      a
                      b
                      "; + executeStep(lastStep, html); + }); + }); + }); +}); diff --git a/test/scenario/RunnerSpec.js b/test/scenario/RunnerSpec.js index 35d74f51..030bdc06 100644 --- a/test/scenario/RunnerSpec.js +++ b/test/scenario/RunnerSpec.js @@ -36,7 +36,7 @@ describe('Runner', function(){ expect(spec.name).toEqual('describe name: it should text'); }); - it('should camplain on duplicate it', angular.noop); + it('should complain on duplicate it', angular.noop); it('should create a failing step if there is a javascript error', function(){ var spec; Describe('D1', function(){ -- cgit v1.2.3 From f6a405c283ba1f2e0037e0bedb52e5cee618d4ff Mon Sep 17 00:00:00 2001 From: Andres Ornelas Date: Wed, 9 Jun 2010 13:30:54 -0700 Subject: change repeater count expectation wording --- src/scenario/DSL.js | 2 +- test/scenario/DSLSpec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scenario/DSL.js b/src/scenario/DSL.js index a017f29d..feedcdcc 100644 --- a/src/scenario/DSL.js +++ b/src/scenario/DSL.js @@ -49,7 +49,7 @@ angular.scenario.dsl.expect = { return { count: { toEqual: function(number) { - $scenario.addStep("Expect to see " + number + " items repeated with selector '" + selector + "'", function(done) { + $scenario.addStep("Expect that there are " + number + " items in Repeater with selector '" + selector + "'", function(done) { var items = this.testDocument.find(selector); if (items.length != number) { this.result.fail("Expected " + number + " but was " + items.length); diff --git a/test/scenario/DSLSpec.js b/test/scenario/DSLSpec.js index 0cce7b75..5aac9752 100644 --- a/test/scenario/DSLSpec.js +++ b/test/scenario/DSLSpec.js @@ -46,7 +46,7 @@ describe("DSL", function() { describe('repeater', function() { it('should check the count of repeated elements', function() { dslExpect.repeater('.repeater-row').count.toEqual(2); - expect(lastStep.name).toEqual("Expect to see 2 items repeated with selector '.repeater-row'"); + expect(lastStep.name).toEqual("Expect that there are 2 items in Repeater with selector '.repeater-row'"); var html = "
                      a
                      b
                      "; executeStep(lastStep, html); }); -- cgit v1.2.3 From 85fac4d78c131771d7fdd46d6ccd44bae92419cd Mon Sep 17 00:00:00 2001 From: Andres Ornelas Date: Wed, 9 Jun 2010 14:12:54 -0700 Subject: add beforeEach and afterEach to scenario DSL --- src/scenario/Runner.js | 15 +++++++++++++-- test/scenario/RunnerSpec.js | 40 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/scenario/Runner.js b/src/scenario/Runner.js index da6e2c39..8e0cc909 100644 --- a/src/scenario/Runner.js +++ b/src/scenario/Runner.js @@ -8,23 +8,34 @@ angular.scenario.Runner = function(scope, jQuery){ var specs = this.specs = {}; var path = []; - this.scope.describe = function describe(name, body){ + this.scope.describe = function(name, body){ path.push(name); body(); path.pop(); }; - this.scope.it = function it(name, body) { + var beforeEach = noop; + var afterEach = noop; + this.scope.beforeEach = function(body) { + beforeEach = body; + }; + this.scope.afterEach = function(body) { + afterEach = body; + }; + this.scope.it = function(name, body) { var specName = path.join(' ') + ': it ' + name; self.currentSpec = specs[specName] = { name: specName, steps:[] }; try { + beforeEach(); body(); } catch(err) { self.addStep(err.message || 'ERROR', function(){ throw err; }); + } finally { + afterEach(); } self.currentSpec = null; }; diff --git a/test/scenario/RunnerSpec.js b/test/scenario/RunnerSpec.js index 030bdc06..ca6e8eb2 100644 --- a/test/scenario/RunnerSpec.js +++ b/test/scenario/RunnerSpec.js @@ -14,6 +14,8 @@ describe('Runner', function(){ body = _jQuery('
                      '); runner = new angular.scenario.Runner(scenario, _jQuery); Describe = scenario.describe; + BeforeEach = scenario.beforeEach; + AfterEach = scenario.afterEach; It = scenario.it; $scenario = scenario.$scenario; }); @@ -36,7 +38,10 @@ describe('Runner', function(){ expect(spec.name).toEqual('describe name: it should text'); }); - it('should complain on duplicate it', angular.noop); + it('should complain on duplicate it', function() { + // WRITE ME!!!! + }); + it('should create a failing step if there is a javascript error', function(){ var spec; Describe('D1', function(){ @@ -55,6 +60,39 @@ describe('Runner', function(){ }; }); }); + + describe('beforeEach', function() { + it('should execute beforeEach before every it', function() { + Describe('describe name', function(){ + BeforeEach(logger('before;')); + It('should text', logger('body;')); + It('should text2', logger('body2;')); + }); + expect(log).toEqual('before;body;before;body2;'); + }); + }); + describe('afterEach', function() { + it('should execute afterEach after every it', function() { + Describe('describe name', function(){ + AfterEach(logger('after;')); + It('should text', logger('body;')); + It('should text2', logger('body2;')); + }); + expect(log).toEqual('body;after;body2;after;'); + }); + + it('should always execute afterEach after every it', function() { + Describe('describe name', function(){ + AfterEach(logger('after;')); + It('should text', function() { + log = 'body;'; + throw "MyError"; + }); + It('should text2', logger('body2;')); + }); + expect(log).toEqual('body;after;body2;after;'); + }); + }); }); describe('steps building', function(){ -- cgit v1.2.3 From d0a468153d98c892323cb135b509cb46f67e44fd Mon Sep 17 00:00:00 2001 From: Andres Ornelas Date: Wed, 9 Jun 2010 15:38:56 -0700 Subject: fixed undefined testWindow bug --- src/scenario/DSL.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scenario/DSL.js b/src/scenario/DSL.js index feedcdcc..194a28d6 100644 --- a/src/scenario/DSL.js +++ b/src/scenario/DSL.js @@ -2,7 +2,7 @@ angular.scenario.dsl.browser = { navigateTo: function(url){ $scenario.addStep('Navigate to: ' + url, function(done){ var self = this; - self.testFrame.load(function(){ + this.testFrame.load(function(){ self.testFrame.unbind(); self.testWindow = self.testFrame[0].contentWindow; self.testDocument = jQuery(self.testWindow.document); @@ -11,7 +11,7 @@ angular.scenario.dsl.browser = { self.notifyWhenNoOutstandingRequests(done); }); if (this.testFrame.attr('src') == url) { - this.testWindow.location.reload(); + this.testFrame[0].contentWindow.location.reload(); } else { this.testFrame.attr('src', url); } -- cgit v1.2.3 From 769b26b79eb26076c218cb5b57179ee98424bbd7 Mon Sep 17 00:00:00 2001 From: Shyam Seshadri Date: Wed, 16 Jun 2010 10:32:56 -0700 Subject: Fix bug with validator not triggering when attributes are bound and fix some typos. Add test for bug --- src/validators.js | 2 +- src/widgets.js | 16 ++++++++-------- test/widgetsSpec.js | 15 +++++++++++++-- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/validators.js b/src/validators.js index 81d40264..5c7fc952 100644 --- a/src/validators.js +++ b/src/validators.js @@ -1,5 +1,5 @@ foreach({ - 'noop': noop, + 'noop': function() { return null; }, 'regexp': function(value, regexp, msg) { if (!value.match(regexp)) { diff --git a/src/widgets.js b/src/widgets.js index 5df92de0..30a4a088 100644 --- a/src/widgets.js +++ b/src/widgets.js @@ -15,9 +15,9 @@ function modelAccessor(scope, element) { function modelFormattedAccessor(scope, element) { var accessor = modelAccessor(scope, element), - farmatterName = element.attr('ng-format') || NOOP, - formatter = angularFormatter(farmatterName); - if (!formatter) throw "Formatter named '" + farmatterName + "' not found."; + formatterName = element.attr('ng-format') || NOOP, + formatter = angularFormatter(formatterName); + if (!formatter) throw "Formatter named '" + formatterName + "' not found."; return { get: function() { return formatter.format(accessor.get()); @@ -36,12 +36,12 @@ function valueAccessor(scope, element) { var validatorName = element.attr('ng-validate') || NOOP, validator = compileValidator(validatorName), requiredExpr = element.attr('ng-required'), - farmatterName = element.attr('ng-format') || NOOP, - formatter = angularFormatter(farmatterName), + formatterName = element.attr('ng-format') || NOOP, + formatter = angularFormatter(formatterName), format, parse, lastError, required; invalidWidgets = scope.$invalidWidgets || {markValid:noop, markInvalid:noop}; if (!validator) throw "Validator named '" + validatorName + "' not found."; - if (!formatter) throw "Formatter named '" + farmatterName + "' not found."; + if (!formatter) throw "Formatter named '" + formatterName + "' not found."; format = formatter.format; parse = formatter.parse; if (requiredExpr) { @@ -86,8 +86,8 @@ function valueAccessor(scope, element) { var error, validateScope = extend(new (extend(function(){}, {prototype:scope}))(), {$element:element}); error = required && !value ? - "Required" : - (value ? validator(validateScope, value) : null); + 'Required' : + value ? validator(validateScope, value) : null; elementError(element, NG_VALIDATION_ERROR, error); lastError = error; if (error) { diff --git a/test/widgetsSpec.js b/test/widgetsSpec.js index 40f52b8e..a053090e 100644 --- a/test/widgetsSpec.js +++ b/test/widgetsSpec.js @@ -43,7 +43,7 @@ describe("widget", function(){ describe("ng-format", function(){ - it("should farmat text", function(){ + it("should format text", function(){ compile(''); expect(scope.$get('list')).toEqual(['a', 'b', 'c']); @@ -178,7 +178,18 @@ describe("widget", function(){ expect(element.attr('ng-validation-error')).toEqual('Not a number'); }); - it("should not call validator if undefinde/empty", function(){ + it('should not blow up for validation with bound attributes', function() { + compile(''); + expect(element.hasClass('ng-validation-error')).toBeTruthy(); + expect(element.attr('ng-validation-error')).toEqual('Required'); + + scope.$set('price', '123'); + scope.$eval(); + expect(element.hasClass('ng-validation-error')).toBeFalsy(); + expect(element.attr('ng-validation-error')).toBeFalsy(); + }); + + it("should not call validator if undefined/empty", function(){ var lastValue = "NOT_CALLED"; angularValidator.myValidator = function(value){lastValue = value;}; compile(''); -- cgit v1.2.3 From b129a1094e6b42ed82c3ccecc2f40daaa0a6cb6a Mon Sep 17 00:00:00 2001 From: Shyam Seshadri Date: Wed, 16 Jun 2010 10:36:48 -0700 Subject: add back nicer formatting --- src/widgets.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widgets.js b/src/widgets.js index 30a4a088..efafa9c5 100644 --- a/src/widgets.js +++ b/src/widgets.js @@ -87,7 +87,7 @@ function valueAccessor(scope, element) { validateScope = extend(new (extend(function(){}, {prototype:scope}))(), {$element:element}); error = required && !value ? 'Required' : - value ? validator(validateScope, value) : null; + (value ? validator(validateScope, value) : null); elementError(element, NG_VALIDATION_ERROR, error); lastError = error; if (error) { -- cgit v1.2.3