diff options
| author | Misko Hevery | 2010-07-29 12:50:14 -0700 |
|---|---|---|
| committer | Misko Hevery | 2010-07-29 12:54:13 -0700 |
| commit | 1b768b84439e725010acc943ebfda462e49d3704 (patch) | |
| tree | 06476962f7116e8c10ddb35d17c5bd3038528aaa | |
| parent | 6bd8006edcbfe1dc1be8cb865fbcfe25157fe117 (diff) | |
| download | angular.js-1b768b84439e725010acc943ebfda462e49d3704.tar.bz2 | |
refactored $location service so that it correctly updates under all conditions
| -rw-r--r-- | example/temp.html | 86 | ||||
| -rw-r--r-- | scenario/location.html | 15 | ||||
| -rw-r--r-- | src/Angular.js | 6 | ||||
| -rw-r--r-- | src/angular-bootstrap.js | 6 | ||||
| -rw-r--r-- | src/formatters.js | 1 | ||||
| -rw-r--r-- | src/jqLite.js | 2 | ||||
| -rw-r--r-- | src/services.js | 95 | ||||
| -rw-r--r-- | test/servicesSpec.js | 19 |
8 files changed, 165 insertions, 65 deletions
diff --git a/example/temp.html b/example/temp.html index f21d3f5c..b238c185 100644 --- a/example/temp.html +++ b/example/temp.html @@ -1,15 +1,85 @@ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> -<html> +<html xmlns:ng="http://angularjs.org"> <head> - <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.js"></script> <script type="text/javascript" src="../src/angular-bootstrap.js#autobind"></script> </head> <body ng:init="$window.$root = this"> - <div ng:click="$window.alert('outter')"> - outter - <div ng:click="$window.alert('inner')">inner</div> - <a href="#ERROR" ng:click="$window.alert('link')">link</a> - </div> + +<script> +function TicTacToeCntl(){ + this.cellStyle= { + 'height': '20px', + 'width': '20px', + 'border': '1px solid black', + 'text-align': 'center', + 'vertical-align': 'middle', + 'cursor': 'pointer' + }; + this.reset(); + this.$watch('$location.hashPath', this.setMemento); + this.$onEval(function(){ + this.$location.hashPath = this.getMemento(); + }); +} +TicTacToeCntl.prototype = { + dropPiece: function(row, col) { + if (!this.winner && !this.board[row][col]) { + this.board[row][col] = this.nextMove; + this.nextMove = this.nextMove == 'X' ? 'O' : 'X'; + this.grade(); + } + }, + reset: function(){ + this.board = [ + ['', '', ''], + ['', '', ''], + ['', '', ''] + ]; + this.nextMove = 'X'; + this.winner = ''; + }, + grade: function(){ + var b = this.board; + this.winner = + row(0) || row(1) || row(2) || + col(0) || col(1) || col(2) || + diagonal(-1) || diagonal(1); + function row(r) { return same(b[r][0], b[r][1], b[r][2]);} + function col(c) { return same(b[0][c], b[1][c], b[2][c]);} + function diagonal(i) { return same(b[0][1-i], b[1][1], b[2][1+i]);} + function same(a, b, c) { return (a==b && b==c) ? a : '';}; + }, + getMemento: function(){ + var rows = []; + angular.foreach(this.board, function(row){ + rows.push(row.join(',')); + }); + return rows.join(';') + '/' + this.nextMove; + }, + setMemento: function(value) { + if (value) { + value = value.split('/'); + this.nextMove = value[1]; + angular.foreach(value[0].split(';'), function(row, i){ + this.board[i] = row.split(','); + }, this); + } else { + this.reset(); + } + } +}; +</script> +<h3>Tic-Tac-Toe</h3> +Next Player: {{nextMove}} +<div ng:show="winner">Player {{winner}} has won!</div> +<table ng:controller="TicTacToeCntl"> + <tr ng:repeat="row in board" style="height:15px;"> + <td ng:repeat="cell in row" ng:style="cellStyle" + ng:click="dropPiece($parent.$index, $index)">{{cell}}</td> + </tr> +</table> +<button ng:click="reset()">reset board</button> + </body> -</html> +</html>
\ No newline at end of file diff --git a/scenario/location.html b/scenario/location.html new file mode 100644 index 00000000..a162636b --- /dev/null +++ b/scenario/location.html @@ -0,0 +1,15 @@ + <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> + <head> + <link rel="stylesheet" type="text/css" href="style.css"/> + <script type="text/javascript" src="../src/angular-bootstrap.js#autobind"></script> + </head> + <body ng:init="$window.$scope = this"> + <pre>$location={{$location}}</pre> + <hr/> + href: <input type="text" name="$location.href" size="120"/> <br/> + hash: <input type="text" name="$location.hash" size="120"/> <br/> + hashPath: <input type="text" name="$location.hashPath" size="120"/> <br/> + hashSearch: <input type="text" name="$location.hashSearch" size="120" ng:format="json"/> <br/> + </body> + </html> diff --git a/src/Angular.js b/src/Angular.js index 32e3ccf7..80acddf0 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -300,10 +300,10 @@ function bind(_this, _function) { } : function() { return _function.apply(_this, curryArgs.concat(slice.call(arguments, 0, arguments.length))); - } + }; } else { - // in IE, native methonds ore not functions and so they can not be bound (but they don't need to be) - return function(a, b, c, d, e){ return _function(a, b, c, d, e); }; + // in IE, native methods ore not functions and so they can not be bound (but they don't need to be) + return _function; } } diff --git a/src/angular-bootstrap.js b/src/angular-bootstrap.js index 90e1104e..e055371a 100644 --- a/src/angular-bootstrap.js +++ b/src/angular-bootstrap.js @@ -22,16 +22,14 @@ * THE SOFTWARE. */ (function(previousOnLoad){ - var filename = /(.*)\/angular-(.*).js(#(.*))?/, + var filename = /(.*)\/angular-(.*).js(#.*)?/, scripts = document.getElementsByTagName("SCRIPT"), serverPath, - config, match; for(var j = 0; j < scripts.length; j++) { match = (scripts[j].src || "").match(filename); if (match) { serverPath = match[1]; - config = match[4]; } } @@ -63,7 +61,7 @@ try { if (previousOnLoad) previousOnLoad(); } catch(e) {} - angularInit(parseKeyValue(config)); + angularInit(parseKeyValue(angularJsConfig(document))); }; })(window.onload); diff --git a/src/formatters.js b/src/formatters.js index 40462cf3..ca1ce83e 100644 --- a/src/formatters.js +++ b/src/formatters.js @@ -5,6 +5,7 @@ var NUMBER = /^\s*[-+]?\d*(\.\d*)?\s*$/; extend(angularFormatter, { 'noop':formatter(identity, identity), + 'json':formatter(toJson, fromJson), 'boolean':formatter(toString, toBoolean), 'number':formatter(toString, function(obj){ diff --git a/src/jqLite.js b/src/jqLite.js index 04682754..22b3c070 100644 --- a/src/jqLite.js +++ b/src/jqLite.js @@ -107,7 +107,7 @@ JQLite.prototype = { if (!event.preventDefault) { event.preventDefault = function(){ event.returnValue = false; - } + }; } foreach(eventHandler.fns, function(fn){ fn.call(self, event); diff --git a/src/services.js b/src/services.js index 106f8954..3dd7df09 100644 --- a/src/services.js +++ b/src/services.js @@ -7,61 +7,78 @@ var URL_MATCH = /^(file|ftp|http|https):\/\/(\w+:{0,1}\w*@)?([\w\.-]*)(:([0-9]+) var HASH_MATCH = /^([^\?]*)?(\?([^\?]*))?$/; var DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp':21}; angularService("$location", function(browser){ - var scope = this, location = {parse:parseUrl, toString:toString}; - var lastHash, lastUrl; + var scope = this, + location = {parse:parseUrl, toString:toString, update:update}, + lastLocation = {}; + + browser.watchUrl(function(url){ + update(url); + scope.$root.$eval(); + }); + this.$onEval(PRIORITY_FIRST, update); + this.$onEval(PRIORITY_LAST, update); + update(browser.getUrl()); + return location; + + function update(href){ + if (href) { + parseUrl(href); + } else { + href = check('href') || check('protocol', '://', 'host', ':', 'port', '', 'path', '?', 'search'); + var hash = check('hash'); + if (isUndefined(hash)) hash = check('hashPath', '?', 'hashSearch'); + if (isDefined(hash)) { + href = (href || location.href).split('#')[0]; + href+= '#' + hash; + } + if (isDefined(href)) { + parseUrl(href); + browser.setUrl(href); + } + } + } + + function check() { + var i = -1, + length=arguments.length, + name, seperator, parts = [], + value, same = true; + for(; i<length; i = i+2) { + parts.push(seperator = (arguments[i] || '')); + name = arguments[i + 1]; + value=location[name]; + parts.push(typeof value == 'object' ? toKeyValue(value) : value); + same = same && equals(lastLocation[name], value); + } + return same ? undefined : parts.join(''); + } + function parseUrl(url){ if (isDefined(url)) { var match = URL_MATCH.exec(url); if (match) { - location.href = url; + location.href = url.replace('#$', ''); location.protocol = match[1]; location.host = match[3] || ''; - location.port = match[5] || DEFAULT_PORTS[location.href] || null; + location.port = match[5] || DEFAULT_PORTS[location.protocol] || null; location.path = match[6]; location.search = parseKeyValue(match[8]); location.hash = match[9] || ''; if (location.hash) location.hash = location.hash.substr(1); - parseHash(location.hash); + match = HASH_MATCH.exec(location.hash); + location.hashPath = match[1] || ''; + location.hashSearch = parseKeyValue(match[3]); + + copy(location, lastLocation); } } } - function parseHash(hash) { - var match = HASH_MATCH.exec(hash); - location.hashPath = match[1] || ''; - location.hashSearch = parseKeyValue(match[3]); - lastHash = hash; - } + function toString() { - if (lastHash === location.hash) { - var hashKeyValue = toKeyValue(location.hashSearch), - hash = (location.hashPath ? location.hashPath : '') + (hashKeyValue ? '?' + hashKeyValue : ''), - url = location.href.split('#')[0] + '#' + (hash ? hash : ''); - if (url !== location.href) parseUrl(url); - return url; - } else { - parseUrl(location.href.split('#')[0] + '#' + location.hash); - return toString(); - } + update(); + return location.href; } - browser.watchUrl(function(url){ - parseUrl(url); - scope.$root.$eval(); - }); - parseUrl(browser.getUrl()); - this.$onEval(PRIORITY_FIRST, function(){ - if (location.hash != lastHash) { - parseHash(location.hash); - } - }); - this.$onEval(PRIORITY_LAST, function(){ - var url = toString(); - if (lastUrl != url) { - browser.setUrl(url); - lastUrl = url; - } - }); - return location; }, {inject: ['$browser']}); angularService("$log", function($window){ diff --git a/test/servicesSpec.js b/test/servicesSpec.js index 91538703..cb5c9b30 100644 --- a/test/servicesSpec.js +++ b/test/servicesSpec.js @@ -106,7 +106,7 @@ describe("service", function(){ expect(scope.$location.hashPath).toEqual(''); expect(scope.$location.hashSearch).toEqual({}); - expect(scope.$location.toString()).toEqual('file:///Users/Shared/misko/work/angular.js/scenario/widgets.html#'); + expect(scope.$location.toString()).toEqual('file:///Users/Shared/misko/work/angular.js/scenario/widgets.html'); }); it('should update url on hash change', function(){ @@ -123,6 +123,14 @@ describe("service", function(){ expect(scope.$location.hash).toEqual('?a=b'); }); + it("should parse url which contains - in host", function(){ + scope.$location.parse('http://a-b1.c-d.09/path'); + expect(scope.$location.href).toEqual('http://a-b1.c-d.09/path'); + expect(scope.$location.protocol).toEqual('http'); + expect(scope.$location.host).toEqual('a-b1.c-d.09'); + expect(scope.$location.path).toEqual('/path'); + }); + it('should update hash before any processing', function(){ var scope = compile('<div>'); var log = ''; @@ -136,15 +144,6 @@ describe("service", function(){ scope.$eval(); expect(log).toEqual('/abc;'); }); - - it("should parse url which contains - in host", function(){ - scope.$location.parse('http://a-b1.c-d.09/path'); - expect(scope.$location.href).toEqual('http://a-b1.c-d.09/path'); - expect(scope.$location.protocol).toEqual('http'); - expect(scope.$location.host).toEqual('a-b1.c-d.09'); - expect(scope.$location.path).toEqual('/path'); - }); - }); describe("$invalidWidgets", function(){ |
