mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-03-17 07:40:22 +00:00
Update $location API Close #62
update(objOrString)
updateHash(objOrString [, objOrString])
toString()
cancel()
Examples:
$location.update('http://www.angularjs.org/path#path?a=b');
$location.update({port: 443, protocol: 'https'});
$location.updateHash('hashPath');
$location.updateHash({a: 'b'});
$location.updateHash('hashPath', {a: 'b'});
This commit was produced by squash of more commits, here are the old messages:
- Change tests to use update() instead of parse().
- First implementation of update() method
- Test for update() with object parameter
- Add new tests for location, refactor location code
- Add tests for updateHash()
- Implement updateHash()
- Take one or two arguments, could be string - update hashPath, or hash object - update hashSearch...
- Fixed other service tests, to use new $location.update()
Added $location.cancel() method (with test)
Added $location.parse() for back compatability
Remove parse() method
This commit is contained in:
parent
9e9bdbdc40
commit
341b2b3a9b
2 changed files with 303 additions and 94 deletions
261
src/services.js
261
src/services.js
|
|
@ -13,91 +13,214 @@ angularServiceInject("$document", function(window){
|
|||
return jqLite(window.document);
|
||||
}, ['$window'], EAGER_PUBLISHED);
|
||||
|
||||
angularServiceInject("$location", function(browser){
|
||||
angularServiceInject("$location", function(browser) {
|
||||
var scope = this,
|
||||
location = {parse:parseUrl, toString:toString, update:update},
|
||||
lastLocation = {};
|
||||
var lastBrowserUrl = browser.getUrl();
|
||||
location = {toString:toString, update:update, updateHash: updateHash, cancel: cancel},
|
||||
lastLocationHref = browser.getUrl(),
|
||||
lastLocationHash;
|
||||
|
||||
browser.addPollFn(function(){
|
||||
if (lastBrowserUrl !== browser.getUrl()) {
|
||||
update(lastBrowserUrl = browser.getUrl());
|
||||
if (lastLocationHref !== browser.getUrl()) {
|
||||
update(lastLocationHref = browser.getUrl());
|
||||
scope.$eval();
|
||||
}
|
||||
});
|
||||
this.$onEval(PRIORITY_FIRST, update);
|
||||
this.$onEval(PRIORITY_LAST, update);
|
||||
update(lastBrowserUrl);
|
||||
|
||||
this.$onEval(PRIORITY_FIRST, updateBrowser);
|
||||
this.$onEval(PRIORITY_LAST, updateBrowser);
|
||||
|
||||
update(lastLocationHref);
|
||||
lastLocationHash = location.hash;
|
||||
|
||||
return location;
|
||||
|
||||
function update(href){
|
||||
if (href) {
|
||||
parseUrl(href);
|
||||
} else {
|
||||
href = check('href') || checkProtocol();
|
||||
var hash = check('hash');
|
||||
if (isUndefined(hash)) hash = checkHashPathSearch();
|
||||
if (isDefined(hash)) {
|
||||
href = (href || location.href).split('#')[0];
|
||||
href+= '#' + hash;
|
||||
|
||||
// PUBLIC METHODS
|
||||
|
||||
/**
|
||||
* Update location object
|
||||
* Does not immediately update the browser
|
||||
* Browser is updated at the end of $eval()
|
||||
*
|
||||
* @example
|
||||
* scope.$location.update('http://www.angularjs.org/path#hash?search=x');
|
||||
* scope.$location.update({host: 'www.google.com', protocol: 'https'});
|
||||
* scope.$location.update({hashPath: '/path', hashSearch: {a: 'b', x: true}});
|
||||
*
|
||||
* @param {String | Object} Full href as a string or hash object with properties
|
||||
*/
|
||||
function update(href) {
|
||||
if (isString(href)) {
|
||||
extend(location, parseHref(href));
|
||||
}
|
||||
else {
|
||||
if (isDefined(href.hash)) {
|
||||
extend(href, parseHash(href.hash));
|
||||
}
|
||||
if (isDefined(href)) {
|
||||
parseUrl(href);
|
||||
browser.setUrl(href);
|
||||
|
||||
extend(location, href);
|
||||
|
||||
if (isDefined(href.hashPath || href.hashSearch)) {
|
||||
location.hash = composeHash(location);
|
||||
}
|
||||
|
||||
location.href = composeHref(location);
|
||||
}
|
||||
}
|
||||
|
||||
function check(param) {
|
||||
return lastLocation[param] == location[param] ? _undefined : location[param];
|
||||
}
|
||||
|
||||
function checkProtocol(){
|
||||
if (lastLocation.protocol === location.protocol &&
|
||||
lastLocation.host === location.host &&
|
||||
lastLocation.port === location.port &&
|
||||
lastLocation.path === location.path &&
|
||||
equals(lastLocation.search, location.search))
|
||||
return _undefined;
|
||||
var url = toKeyValue(location.search);
|
||||
var port = (location.port == DEFAULT_PORTS[location.protocol] ? _null : location.port);
|
||||
return location.protocol + '://' + location.host +
|
||||
(port ? ':' + port : '') + location.path +
|
||||
(url ? '?' + url : '');
|
||||
}
|
||||
|
||||
function checkHashPathSearch(){
|
||||
if (lastLocation.hashPath === location.hashPath &&
|
||||
equals(lastLocation.hashSearch, location.hashSearch) )
|
||||
return _undefined;
|
||||
var url = toKeyValue(location.hashSearch);
|
||||
return escape(location.hashPath) + (url ? '?' + url : '');
|
||||
}
|
||||
|
||||
function parseUrl(url){
|
||||
if (isDefined(url)) {
|
||||
var match = URL_MATCH.exec(url);
|
||||
if (match) {
|
||||
location.href = url.replace('#$', '');
|
||||
location.protocol = match[1];
|
||||
location.host = match[3] || '';
|
||||
location.port = match[5] || DEFAULT_PORTS[location.protocol] || _null;
|
||||
location.path = match[6];
|
||||
location.search = parseKeyValue(match[8]);
|
||||
location.hash = match[10] || '';
|
||||
match = HASH_MATCH.exec(location.hash);
|
||||
location.hashPath = unescape(match[1] || '');
|
||||
location.hashSearch = parseKeyValue(match[3]);
|
||||
|
||||
copy(location, lastLocation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update location hash
|
||||
* @see update()
|
||||
*
|
||||
* @example
|
||||
* scope.$location.updateHash('/hp')
|
||||
* ==> update({hashPath: '/hp'})
|
||||
*
|
||||
* scope.$location.updateHash({a: true, b: 'val'})
|
||||
* ==> update({hashSearch: {a: true, b: 'val'}})
|
||||
*
|
||||
* scope.$location.updateHash('/hp', {a: true})
|
||||
* ==> update({hashPath: '/hp', hashSearch: {a: true}})
|
||||
*
|
||||
* @param {String | Object} hashPath as String or hashSearch as Object
|
||||
* @param {String | Object} hashPath as String or hashSearch as Object [optional]
|
||||
*/
|
||||
function updateHash() {
|
||||
var hash = {};
|
||||
for (var i = 0; i < Math.min(arguments.length, 2); i++) {
|
||||
hash[isString(arguments[i]) ? 'hashPath' : 'hashSearch'] = arguments[i];
|
||||
}
|
||||
update(hash);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns string representation - href
|
||||
*
|
||||
* @return {String} Location's href property
|
||||
*/
|
||||
function toString() {
|
||||
update();
|
||||
updateLocation();
|
||||
return location.href;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel change of the location
|
||||
*
|
||||
* Calling update(), updateHash() or setting a property does not immediately
|
||||
* change the browser's url. Url is changed at the end of $eval()
|
||||
*
|
||||
* By calling this method, you can cancel the change (before end of $eval())
|
||||
*
|
||||
*/
|
||||
function cancel() {
|
||||
update(lastLocationHref);
|
||||
}
|
||||
|
||||
// INNER METHODS
|
||||
|
||||
/**
|
||||
* Update location object
|
||||
*
|
||||
* User is allowed to change properties, so after property change,
|
||||
* location object is not in consistent state.
|
||||
*
|
||||
* @example
|
||||
* scope.$location.href = 'http://www.angularjs.org/path#a/b'
|
||||
* immediately after this call, other properties are still the old ones...
|
||||
*
|
||||
* This method checks the changes and update location to the consistent state
|
||||
*/
|
||||
function updateLocation() {
|
||||
if (location.href == lastLocationHref) {
|
||||
if (location.hash == lastLocationHash) {
|
||||
location.hash = composeHash(location);
|
||||
}
|
||||
location.href = composeHref(location);
|
||||
}
|
||||
update(location.href);
|
||||
}
|
||||
|
||||
/**
|
||||
* If location has changed, update the browser
|
||||
* This method is called at the end of $eval() phase
|
||||
*/
|
||||
function updateBrowser() {
|
||||
updateLocation();
|
||||
|
||||
if (location.href != lastLocationHref) {
|
||||
browser.setUrl(lastLocationHref = location.href);
|
||||
lastLocationHash = location.hash;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose href string from a location object
|
||||
*
|
||||
* @param {Object} Location object with all properties
|
||||
* @return {String} Composed href
|
||||
*/
|
||||
function composeHref(loc) {
|
||||
var url = toKeyValue(loc.search);
|
||||
var port = (loc.port == DEFAULT_PORTS[loc.protocol] ? _null : loc.port);
|
||||
|
||||
return loc.protocol + '://' + loc.host +
|
||||
(port ? ':' + port : '') + loc.path +
|
||||
(url ? '?' + url : '') + (loc.hash ? '#' + loc.hash : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose hash string from location object
|
||||
*
|
||||
* @param {Object} Object with hashPath and hashSearch properties
|
||||
* @return {String} Hash string
|
||||
*/
|
||||
function composeHash(loc) {
|
||||
var hashSearch = toKeyValue(loc.hashSearch);
|
||||
return escape(loc.hashPath) + (hashSearch ? '?' + hashSearch : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse href string into location object
|
||||
*
|
||||
* @param {String} Href
|
||||
* @return {Object} Location
|
||||
*/
|
||||
function parseHref(href) {
|
||||
var loc = {};
|
||||
var match = URL_MATCH.exec(href);
|
||||
|
||||
if (match) {
|
||||
loc.href = href.replace('#$', '');
|
||||
loc.protocol = match[1];
|
||||
loc.host = match[3] || '';
|
||||
loc.port = match[5] || DEFAULT_PORTS[loc.protocol] || _null;
|
||||
loc.path = match[6];
|
||||
loc.search = parseKeyValue(match[8]);
|
||||
loc.hash = match[10] || '';
|
||||
|
||||
extend(loc, parseHash(loc.hash));
|
||||
}
|
||||
|
||||
return loc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse hash string into object
|
||||
*
|
||||
* @param {String} Hash
|
||||
* @param {Object} Object with hashPath and hashSearch properties
|
||||
*/
|
||||
function parseHash(hash) {
|
||||
var h = {};
|
||||
var match = HASH_MATCH.exec(hash);
|
||||
|
||||
if (match) {
|
||||
h.hash = hash;
|
||||
h.hashPath = unescape(match[1] || '');
|
||||
h.hashSearch = parseKeyValue(match[3]);
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
}, ['$browser'], EAGER_PUBLISHED);
|
||||
|
||||
angularServiceInject("$log", function($window){
|
||||
|
|
|
|||
|
|
@ -82,10 +82,14 @@ describe("service", function(){
|
|||
});
|
||||
|
||||
describe("$location", function(){
|
||||
it("should inject $location", function(){
|
||||
scope.$location.parse('http://host:123/p/a/t/h.html?query=value#path?key=value&flag&key2=');
|
||||
expect(scope.$location.href).
|
||||
toEqual("http://host:123/p/a/t/h.html?query=value#path?key=value&flag&key2=");
|
||||
it("should inject $location", function() {
|
||||
expect(scope.$location).toBeDefined();
|
||||
});
|
||||
|
||||
it("update should update location object immediately", function() {
|
||||
var href = 'http://host:123/p/a/t/h.html?query=value#path?key=value&flag&key2=';
|
||||
scope.$location.update(href);
|
||||
expect(scope.$location.href).toEqual(href);
|
||||
expect(scope.$location.protocol).toEqual("http");
|
||||
expect(scope.$location.host).toEqual("host");
|
||||
expect(scope.$location.port).toEqual("123");
|
||||
|
|
@ -94,16 +98,38 @@ describe("service", function(){
|
|||
expect(scope.$location.hash).toEqual('path?key=value&flag&key2=');
|
||||
expect(scope.$location.hashPath).toEqual('path');
|
||||
expect(scope.$location.hashSearch).toEqual({key: 'value', flag: true, key2: ''});
|
||||
|
||||
scope.$location.hashPath = 'page=http://path';
|
||||
scope.$location.hashSearch = {k:'a=b'};
|
||||
|
||||
expect(scope.$location.toString()).
|
||||
toEqual('http://host:123/p/a/t/h.html?query=value#page%3Dhttp%3A//path?k=a%3Db');
|
||||
});
|
||||
|
||||
it('toString() should return actual representation', function() {
|
||||
var href = 'http://host:123/p/a/t/h.html?query=value#path?key=value&flag&key2=';
|
||||
scope.$location.update(href);
|
||||
expect(scope.$location.toString()).toEqual(href);
|
||||
scope.$eval();
|
||||
|
||||
scope.$location.host = 'new';
|
||||
scope.$location.path = '';
|
||||
expect(scope.$location.toString()).toEqual('http://new:123?query=value#path?key=value&flag&key2=');
|
||||
});
|
||||
|
||||
it('toString() should not update browser', function() {
|
||||
var url = $browser.getUrl();
|
||||
scope.$location.update('http://www.angularjs.org');
|
||||
expect(scope.$location.toString()).toEqual('http://www.angularjs.org');
|
||||
expect($browser.getUrl()).toEqual(url);
|
||||
});
|
||||
|
||||
it('should update browser at the end of $eval', function() {
|
||||
var url = $browser.getUrl();
|
||||
scope.$location.update('http://www.angularjs.org/');
|
||||
scope.$location.update({path: '/a/b'});
|
||||
expect(scope.$location.toString()).toEqual('http://www.angularjs.org/a/b');
|
||||
expect($browser.getUrl()).toEqual(url);
|
||||
scope.$eval();
|
||||
expect($browser.getUrl()).toEqual('http://www.angularjs.org/a/b');
|
||||
});
|
||||
|
||||
it('should parse file://', function(){
|
||||
scope.$location.parse('file:///Users/Shared/misko/work/angular.js/scenario/widgets.html');
|
||||
scope.$location.update('file:///Users/Shared/misko/work/angular.js/scenario/widgets.html');
|
||||
expect(scope.$location.href).toEqual("file:///Users/Shared/misko/work/angular.js/scenario/widgets.html");
|
||||
expect(scope.$location.protocol).toEqual("file");
|
||||
expect(scope.$location.host).toEqual("");
|
||||
|
|
@ -113,26 +139,47 @@ describe("service", function(){
|
|||
expect(scope.$location.hash).toEqual('');
|
||||
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');
|
||||
});
|
||||
|
||||
it('should update url on hash change', function(){
|
||||
scope.$location.parse('http://server/#path?a=b');
|
||||
scope.$location.hash = '';
|
||||
expect(scope.$location.toString()).toEqual('http://server/#');
|
||||
it('should update hashPath and hashSearch on hash update', function(){
|
||||
scope.$location.update('http://server/#path?a=b');
|
||||
scope.$eval();
|
||||
scope.$location.update({hash: ''});
|
||||
|
||||
expect(scope.$location.hashPath).toEqual('');
|
||||
expect(scope.$location.hashSearch).toEqual({});
|
||||
});
|
||||
|
||||
it('should update hash on hashPath or hashSearch update', function() {
|
||||
scope.$location.update('http://server/#path?a=b');
|
||||
scope.$eval();
|
||||
scope.$location.update({hashPath: '', hashSearch: {}});
|
||||
|
||||
expect(scope.$location.hash).toEqual('');
|
||||
});
|
||||
|
||||
it('should update url on hashPath change', function(){
|
||||
scope.$location.parse('http://server/#path?a=b');
|
||||
it('should update hashPath and hashSearch on hash property change', function(){
|
||||
scope.$location.update('http://server/#path?a=b');
|
||||
scope.$eval();
|
||||
scope.$location.hash = '';
|
||||
|
||||
expect(scope.$location.toString()).toEqual('http://server/');
|
||||
expect(scope.$location.hashPath).toEqual('');
|
||||
expect(scope.$location.hashSearch).toEqual({});
|
||||
});
|
||||
|
||||
it('should update hash on hashPath or hashSearch property change', function() {
|
||||
scope.$location.update('http://server/#path?a=b');
|
||||
scope.$eval();
|
||||
scope.$location.hashPath = '';
|
||||
expect(scope.$location.toString()).toEqual('http://server/#?a=b');
|
||||
expect(scope.$location.hash).toEqual('?a=b');
|
||||
scope.$location.hashSearch = {};
|
||||
|
||||
expect(scope.$location.toString()).toEqual('http://server/');
|
||||
expect(scope.$location.hash).toEqual('');
|
||||
});
|
||||
|
||||
it("should parse url which contains - in host", function(){
|
||||
scope.$location.parse('http://a-b1.c-d.09/path');
|
||||
scope.$location.update('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');
|
||||
|
|
@ -152,6 +199,45 @@ describe("service", function(){
|
|||
scope.$eval();
|
||||
expect(log).toEqual('/abc;');
|
||||
});
|
||||
|
||||
it('udpate() should accept hash object and update only given properties', function() {
|
||||
scope.$location.update("http://host:123/p/a/t/h.html?query=value#path?key=value&flag&key2=");
|
||||
scope.$location.update({host: 'new', port: 24});
|
||||
|
||||
expect(scope.$location.host).toEqual('new');
|
||||
expect(scope.$location.port).toEqual(24);
|
||||
expect(scope.$location.protocol).toEqual('http');
|
||||
expect(scope.$location.href).toEqual("http://new:24/p/a/t/h.html?query=value#path?key=value&flag&key2=");
|
||||
});
|
||||
|
||||
it('updateHash() should accept one string argument to update path', function() {
|
||||
scope.$location.updateHash('path');
|
||||
expect(scope.$location.hash).toEqual('path');
|
||||
expect(scope.$location.hashPath).toEqual('path');
|
||||
});
|
||||
|
||||
it('updateHash() should accept one hash argument to update search', function() {
|
||||
scope.$location.updateHash({a: 'b'});
|
||||
expect(scope.$location.hash).toEqual('?a=b');
|
||||
expect(scope.$location.hashSearch).toEqual({a: 'b'});
|
||||
});
|
||||
|
||||
it('updateHash() should accept path and search both', function() {
|
||||
scope.$location.updateHash('path', {a: 'b'});
|
||||
expect(scope.$location.hash).toEqual('path?a=b');
|
||||
expect(scope.$location.hashSearch).toEqual({a: 'b'});
|
||||
expect(scope.$location.hashPath).toEqual('path');
|
||||
});
|
||||
|
||||
it('should not update browser if you call cancel()', function() {
|
||||
spyOn($browser, 'setUrl');
|
||||
|
||||
scope.$location.update('http://www.angularjs.org/a/b#a/b');
|
||||
scope.$location.cancel();
|
||||
scope.$eval();
|
||||
|
||||
expect($browser.setUrl).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("$invalidWidgets", function(){
|
||||
|
|
@ -195,7 +281,7 @@ describe("service", function(){
|
|||
$route.onChange(function(){
|
||||
log += 'onChange();';
|
||||
});
|
||||
scope.$location.parse('http://server#/Book/Moby/Chapter/Intro?p=123');
|
||||
scope.$location.update('http://server#/Book/Moby/Chapter/Intro?p=123');
|
||||
scope.$eval();
|
||||
expect(log).toEqual('onChange();');
|
||||
expect($route.current.params).toEqual({book:'Moby', chapter:'Intro', p:'123'});
|
||||
|
|
@ -203,14 +289,14 @@ describe("service", function(){
|
|||
var lastId = $route.current.scope.$id;
|
||||
|
||||
log = '';
|
||||
scope.$location.parse('http://server#/Blank?ignore');
|
||||
scope.$location.update('http://server#/Blank?ignore');
|
||||
scope.$eval();
|
||||
expect(log).toEqual('onChange();');
|
||||
expect($route.current.params).toEqual({ignore:true});
|
||||
expect($route.current.scope.$id).not.toEqual(lastId);
|
||||
|
||||
log = '';
|
||||
scope.$location.parse('http://server#/NONE');
|
||||
scope.$location.update('http://server#/NONE');
|
||||
scope.$eval();
|
||||
expect(log).toEqual('onChange();');
|
||||
expect($route.current).toEqual(null);
|
||||
|
|
|
|||
Loading…
Reference in a new issue