From 7a4b48020688060debe9cb0f9c17615d7585cbe7 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Mon, 5 Apr 2010 11:46:53 -0700 Subject: added ng:switch widget --- src/Angular.js | 9 ++++++++ src/AngularPublic.js | 35 ++++++++++++++++++++++++++++ src/Browser.js | 20 ++++++++++++++-- src/Compiler.js | 1 + src/Scope.js | 17 ++++---------- src/UrlWatcher.js | 46 ------------------------------------- src/Widgets.js | 59 +++++++++++++++++++++++++++--------------------- src/angular-bootstrap.js | 2 +- src/jqLite.js | 7 ++++++ src/services.js | 29 ++++++++---------------- src/~AngularPublic.js | 17 -------------- 11 files changed, 119 insertions(+), 123 deletions(-) create mode 100644 src/AngularPublic.js delete mode 100644 src/UrlWatcher.js delete mode 100644 src/~AngularPublic.js (limited to 'src') diff --git a/src/Angular.js b/src/Angular.js index d00a9bf6..0952a352 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -247,7 +247,16 @@ function escapeHtml(html) { replace(/>/g, '>'); } + +function isRenderableElement(element) { + var name = element && element[0] && element[0].nodeName; + return name && name.charAt(0) != '#' && + !includes(['TR', 'COL', 'COLGROUP', 'TBODY', 'THEAD', 'TFOOT'], name); +} function elementError(element, type, error) { + while (!isRenderableElement(element)) { + element = element.parent() || jqLite(document.body); + } if (error) { element.addClass(type); element.attr(NG_ERROR, error); diff --git a/src/AngularPublic.js b/src/AngularPublic.js new file mode 100644 index 00000000..470eb258 --- /dev/null +++ b/src/AngularPublic.js @@ -0,0 +1,35 @@ +var browserSingleton; +angularService('$browser', function browserFactory(){ + if (!browserSingleton) { + var XHR = XMLHttpRequest; + if (isUndefined(XHR)) { + XHR = function () { + try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e1) {} + try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e2) {} + try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e3) {} + throw new Error("This browser does not support XMLHttpRequest."); + }; + } + browserSingleton = new Browser(window.location, XHR); + browserSingleton.startUrlWatcher(); + } + return browserSingleton; +}); + +extend(angular, { + 'element': jqLite, + 'compile': compile, + 'scope': createScope, + 'copy': copy, + 'extend': extend, + 'foreach': foreach, + 'noop':noop, + 'identity':identity, + 'isUndefined': isUndefined, + 'isDefined': isDefined, + 'isString': isString, + 'isFunction': isFunction, + 'isNumber': isNumber, + 'isArray': isArray +}); + diff --git a/src/Browser.js b/src/Browser.js index 893459ae..6036884f 100644 --- a/src/Browser.js +++ b/src/Browser.js @@ -3,9 +3,10 @@ // Browser ////////////////////////////// -function Browser(location) { +function Browser(location, XHR) { this.location = location; this.delay = 25; + this.XHR = XHR; this.setTimeout = function(fn, delay) { window.setTimeout(fn, delay); }; @@ -14,6 +15,17 @@ function Browser(location) { } Browser.prototype = { + xhr: function(method, url, callback){ + var xhr = new this.XHR(); + xhr.open(method, url, true); + xhr.onreadystatechange = function() { + if (xhr.readyState == 4) { + callback(xhr.status, xhr.responseText); + } + }; + xhr.send(''); + }, + watchUrl: function(fn){ this.listeners.push(fn); }, @@ -23,7 +35,11 @@ Browser.prototype = { (function pull () { if (self.expectedUrl !== self.location.href) { foreach(self.listeners, function(listener){ - listener(self.location.href); + try { + listener(self.location.href); + } catch (e) { + error(e); + } }); self.expectedUrl = self.location.href; } diff --git a/src/Compiler.js b/src/Compiler.js index dac4931f..67c22461 100644 --- a/src/Compiler.js +++ b/src/Compiler.js @@ -71,6 +71,7 @@ Compiler.prototype = { $init: function() { template.init(element, scope); scope.$eval(); + delete scope.$init; return scope; } }); diff --git a/src/Scope.js b/src/Scope.js index 52ab3ed7..562dfbd8 100644 --- a/src/Scope.js +++ b/src/Scope.js @@ -70,17 +70,8 @@ function parserNewScopeAdapter(fn) { }; } -function isRenderableElement(element) { - var name = element && element[0] && element[0].nodeName; - return name && name.charAt(0) != '#' && - !includes(['TR', 'COL', 'COLGROUP', 'TBODY', 'THEAD', 'TFOOT'], name); -} - function rethrow(e) { throw e; } function errorHandlerFor(element, error) { - while (!isRenderableElement(element)) { - element = element.parent() || jqLite(document.body); - } elementError(element, NG_EXCEPTION, isDefined(error) ? toJson(error) : error); } @@ -132,14 +123,16 @@ function createScope(parent, services, existing) { $watch: function(watchExp, listener, exceptionHandler) { var watch = expressionCompile(watchExp), - last = watch.call(instance); - instance.$onEval(PRIORITY_WATCH, function(){ + last; + function watcher(){ var value = watch.call(instance); if (last !== value) { instance.$tryEval(listener, exceptionHandler, value, last); last = value; } - }); + } + instance.$onEval(PRIORITY_WATCH, watcher); + watcher(); }, $onEval: function(priority, expr, exceptionHandler){ diff --git a/src/UrlWatcher.js b/src/UrlWatcher.js deleted file mode 100644 index 1b2a9cf0..00000000 --- a/src/UrlWatcher.js +++ /dev/null @@ -1,46 +0,0 @@ - -// //////////////////////////// -// UrlWatcher -// //////////////////////////// - -function UrlWatcher(location) { - this.location = location; - this.delay = 25; - this.setTimeout = function(fn, delay) { - window.setTimeout(fn, delay); - }; - this.expectedUrl = location.href; - this.listeners = []; -} - -UrlWatcher.prototype = { - watch: function(fn){ - this.listeners.push(fn); - }, - - start: function() { - var self = this; - (function pull () { - if (self.expectedUrl !== self.location.href) { - foreach(self.listeners, function(listener){ - listener(self.location.href); - }); - self.expectedUrl = self.location.href; - } - self.setTimeout(pull, self.delay); - })(); - }, - - 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 this.location.href; - } -}; diff --git a/src/Widgets.js b/src/Widgets.js index 1e703a56..8e668c8f 100644 --- a/src/Widgets.js +++ b/src/Widgets.js @@ -156,40 +156,47 @@ angularWidget('SELECT', function(element){ }); -angularWidget('INLINE', function(element){ - element.replaceWith(this.element("div")); +angularWidget('NG:INCLUDE', function(element){ var compiler = this, - behavior = element.attr("behavior"), - template = element.attr("template"), - initExpr = element.attr("init"); - return function(boundElement){ - var scope = this; - boundElement.load(template, function(){ - var templateScope = compiler.compile(boundElement)(boundElement, scope); - templateScope.$tryEval(initExpr, boundElement); - templateScope.$init(); + src = element.attr("src"); + return element.attr('switch-instance') ? null : function(element){ + var scope = this, childScope; + element.attr('switch-instance', 'compiled'); + scope.$browser.xhr('GET', src, function(code, response){ + element.html(response); + childScope = createScope(scope); + compiler.compile(element)(element, childScope); + childScope.$init(); }); + scope.$onEval(function(){ if (childScope) childScope.$eval(); }); }; }); -angularWidget('INCLUDE', function(element){ - element.replaceWith(this.element("div")); - var matches = []; - element.find("INLINE").each(function(){ - matches.push({match: jQuery(this).attr("match"), element: jQuery(this)}); - }); +angularWidget('NG:SWITCH', function(element){ var compiler = this, - watchExpr = element.attr("watch"); - return function(boundElement){ + watchExpr = element.attr("on"), + cases = []; + eachNode(element, function(caseElement){ + var when = caseElement.attr('ng-switch-when'); + if (when) { + cases.push({ + when: function(value){ return value == when; }, + element: caseElement, + template: compiler.compile(caseElement) + }); + } + }); + element.html(''); + return function(element){ var scope = this; this.$watch(watchExpr, function(value){ - foreach(matches, function(inline){ - if(inline.match == value) { - var template = inline.element.attr("template"); - boundElement.load(template, function(){ - var templateScope = compiler.compile(boundElement)(boundElement, scope); - templateScope.$init(); - }); + element.html(''); + foreach(cases, function(switchCase){ + if (switchCase.when(value)) { + element.append(switchCase.element); + var childScope = createScope(scope); + switchCase.template(switchCase.element, childScope); + childScope.$init(); } }); }); diff --git a/src/angular-bootstrap.js b/src/angular-bootstrap.js index d9633854..ce7849d8 100644 --- a/src/angular-bootstrap.js +++ b/src/angular-bootstrap.js @@ -47,7 +47,7 @@ addScript("/Parser.js"); addScript("/Resource.js"); addScript("/Browser.js"); - addScript("/AngularPublic.js"); + addScript("/~AngularPublic.js"); // Extension points addScript("/apis.js"); diff --git a/src/jqLite.js b/src/jqLite.js index ec77a6fb..f8ed4d7d 100644 --- a/src/jqLite.js +++ b/src/jqLite.js @@ -109,6 +109,10 @@ JQLite.prototype = { this[0].parentNode.replaceChild(jqLite(replaceNode)[0], this[0]); }, + append: function(node) { + this[0].appendChild(jqLite(node)[0]); + }, + remove: function() { this.dealoc(); this[0].parentNode.removeChild(this[0]); @@ -182,6 +186,9 @@ JQLite.prototype = { html: function(value) { if (isDefined(value)) { + for ( var i = 0, children = this[0].childNodes; i < children.length; i++) { + jqLite(children[i]).dealoc(); + } this[0].innerHTML = value; } return this[0].innerHTML; diff --git a/src/services.js b/src/services.js index 16b48031..2532d3d3 100644 --- a/src/services.js +++ b/src/services.js @@ -6,8 +6,8 @@ angularService("$document", function(window){ var URL_MATCH = /^(file|ftp|http|https):\/\/(\w+:{0,1}\w*@)?([\w\.]*)(:([0-9]+))?([^\?#]+)(\?([^#]*))?((#([^\?]*))?(\?([^\?]*))?)$/; var DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp':21}; angularService("$location", function(browser){ - var scope = this; - function location(url){ + var scope = this, location = {parse:parse, toString:toString}; + function parse(url){ if (isDefined(url)) { var match = URL_MATCH.exec(url); if (match) { @@ -23,17 +23,19 @@ angularService("$location", function(browser){ location.hashSearch = parseKeyValue(match[13]); } } - var hashKeyValue = toKeyValue(location.hashSearch); - var hash = (location.hashPath ? location.hashPath : '') + - (hashKeyValue ? '?' + hashKeyValue : ''); + } + function toString() { + var hashKeyValue = toKeyValue(location.hashSearch), + hash = (location.hashPath ? location.hashPath : '') + (hashKeyValue ? '?' + hashKeyValue : ''); return location.href.split('#')[0] + '#' + (hash ? hash : ''); } browser.watchUrl(function(url){ - location(url); + parse(url); + scope.$root.$eval(); }); - location(browser.getUrl()); + parse(browser.getUrl()); this.$onEval(PRIORITY_LAST, function(){ - var href = location(); + var href = toString(); if (href != location.href) { browser.setUrl(href); location.href = href; @@ -42,14 +44,3 @@ angularService("$location", function(browser){ return location; }, {inject: ['$browser']}); -if (!angularService['$browser']) { - var browserSingleton; - angularService('$browser', function browserFactory(){ - if (!browserSingleton) { - browserSingleton = new Browser(window.location); - browserSingleton.startUrlWatcher(); - } - return browserSingleton; - }); -} - diff --git a/src/~AngularPublic.js b/src/~AngularPublic.js deleted file mode 100644 index b9d0f9d7..00000000 --- a/src/~AngularPublic.js +++ /dev/null @@ -1,17 +0,0 @@ -extend(angular, { - 'element': jqLite, - 'compile': compile, - 'scope': createScope, - 'copy': copy, - 'extend': extend, - 'foreach': foreach, - 'noop':noop, - 'identity':identity, - 'isUndefined': isUndefined, - 'isDefined': isDefined, - 'isString': isString, - 'isFunction': isFunction, - 'isNumber': isNumber, - 'isArray': isArray -}); - -- cgit v1.2.3