mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-03-17 07:40:22 +00:00
feat($location): add $locatonChange[begin|completed] event
This allows location change cancelation
This commit is contained in:
parent
8aa18f0ad0
commit
92a2e18076
5 changed files with 252 additions and 68 deletions
7
src/bootstrap/bootstrap-prettify.js
vendored
7
src/bootstrap/bootstrap-prettify.js
vendored
|
|
@ -190,7 +190,12 @@ directive.ngEmbedApp = ['$templateCache', '$browser', '$rootScope', '$location',
|
||||||
$provide.value('$anchorScroll', angular.noop);
|
$provide.value('$anchorScroll', angular.noop);
|
||||||
$provide.value('$browser', $browser);
|
$provide.value('$browser', $browser);
|
||||||
$provide.provider('$location', function() {
|
$provide.provider('$location', function() {
|
||||||
this.$get = function() { return $location; };
|
this.$get = ['$rootScope', function($rootScope) {
|
||||||
|
docsRootScope.$on('$locationChangeSuccess', function(event, oldUrl, newUrl) {
|
||||||
|
$rootScope.$broadcast('$locationChangeSuccess', oldUrl, newUrl);
|
||||||
|
});
|
||||||
|
return $location;
|
||||||
|
}];
|
||||||
this.html5Mode = angular.noop;
|
this.html5Mode = angular.noop;
|
||||||
});
|
});
|
||||||
$provide.decorator('$defer', ['$rootScope', '$delegate', function($rootScope, $delegate) {
|
$provide.decorator('$defer', ['$rootScope', '$delegate', function($rootScope, $delegate) {
|
||||||
|
|
|
||||||
|
|
@ -408,7 +408,10 @@ function locationGetterSetter(property, preprocess) {
|
||||||
* @requires $rootElement
|
* @requires $rootElement
|
||||||
*
|
*
|
||||||
* @description
|
* @description
|
||||||
* The $location service parses the URL in the browser address bar (based on the {@link https://developer.mozilla.org/en/window.location window.location}) and makes the URL available to your application. Changes to the URL in the address bar are reflected into $location service and changes to $location are reflected into the browser address bar.
|
* The $location service parses the URL in the browser address bar (based on the
|
||||||
|
* {@link https://developer.mozilla.org/en/window.location window.location}) and makes the URL
|
||||||
|
* available to your application. Changes to the URL in the address bar are reflected into
|
||||||
|
* $location service and changes to $location are reflected into the browser address bar.
|
||||||
*
|
*
|
||||||
* **The $location service:**
|
* **The $location service:**
|
||||||
*
|
*
|
||||||
|
|
@ -421,7 +424,8 @@ function locationGetterSetter(property, preprocess) {
|
||||||
* - Clicks on a link.
|
* - Clicks on a link.
|
||||||
* - Represents the URL object as a set of methods (protocol, host, port, path, search, hash).
|
* - Represents the URL object as a set of methods (protocol, host, port, path, search, hash).
|
||||||
*
|
*
|
||||||
* For more information see {@link guide/dev_guide.services.$location Developer Guide: Angular Services: Using $location}
|
* For more information see {@link guide/dev_guide.services.$location Developer Guide: Angular
|
||||||
|
* Services: Using $location}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -470,65 +474,73 @@ function $LocationProvider(){
|
||||||
|
|
||||||
this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement',
|
this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement',
|
||||||
function( $rootScope, $browser, $sniffer, $rootElement) {
|
function( $rootScope, $browser, $sniffer, $rootElement) {
|
||||||
var currentUrl,
|
var $location,
|
||||||
basePath = $browser.baseHref() || '/',
|
basePath = $browser.baseHref() || '/',
|
||||||
pathPrefix = pathPrefixFromBase(basePath),
|
pathPrefix = pathPrefixFromBase(basePath),
|
||||||
initUrl = $browser.url();
|
initUrl = $browser.url(),
|
||||||
|
absUrlPrefix;
|
||||||
|
|
||||||
if (html5Mode) {
|
if (html5Mode) {
|
||||||
if ($sniffer.history) {
|
if ($sniffer.history) {
|
||||||
currentUrl = new LocationUrl(convertToHtml5Url(initUrl, basePath, hashPrefix), pathPrefix);
|
$location = new LocationUrl(
|
||||||
|
convertToHtml5Url(initUrl, basePath, hashPrefix),
|
||||||
|
pathPrefix);
|
||||||
} else {
|
} else {
|
||||||
currentUrl = new LocationHashbangUrl(convertToHashbangUrl(initUrl, basePath, hashPrefix),
|
$location = new LocationHashbangUrl(
|
||||||
hashPrefix);
|
convertToHashbangUrl(initUrl, basePath, hashPrefix),
|
||||||
|
hashPrefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
// link rewriting
|
|
||||||
var u = currentUrl,
|
|
||||||
absUrlPrefix = composeProtocolHostPort(u.protocol(), u.host(), u.port()) + pathPrefix;
|
|
||||||
|
|
||||||
$rootElement.bind('click', function(event) {
|
|
||||||
// TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
|
|
||||||
// currently we open nice url link and redirect then
|
|
||||||
|
|
||||||
if (event.ctrlKey || event.metaKey || event.which == 2) return;
|
|
||||||
|
|
||||||
var elm = jqLite(event.target);
|
|
||||||
|
|
||||||
// traverse the DOM up to find first A tag
|
|
||||||
while (elm.length && lowercase(elm[0].nodeName) !== 'a') {
|
|
||||||
elm = elm.parent();
|
|
||||||
}
|
|
||||||
|
|
||||||
var absHref = elm.prop('href');
|
|
||||||
|
|
||||||
if (!absHref ||
|
|
||||||
elm.attr('target') ||
|
|
||||||
absHref.indexOf(absUrlPrefix) !== 0) { // link to different domain or base path
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// update location with href without the prefix
|
|
||||||
currentUrl.url(absHref.substr(absUrlPrefix.length));
|
|
||||||
$rootScope.$apply();
|
|
||||||
event.preventDefault();
|
|
||||||
// hack to work around FF6 bug 684208 when scenario runner clicks on links
|
|
||||||
window.angular['ff-684208-preventDefault'] = true;
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
currentUrl = new LocationHashbangUrl(initUrl, hashPrefix);
|
$location = new LocationHashbangUrl(initUrl, hashPrefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// link rewriting
|
||||||
|
absUrlPrefix = composeProtocolHostPort(
|
||||||
|
$location.protocol(), $location.host(), $location.port()) + pathPrefix;
|
||||||
|
|
||||||
|
$rootElement.bind('click', function(event) {
|
||||||
|
// TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
|
||||||
|
// currently we open nice url link and redirect then
|
||||||
|
|
||||||
|
if (event.ctrlKey || event.metaKey || event.which == 2) return;
|
||||||
|
|
||||||
|
var elm = jqLite(event.target);
|
||||||
|
|
||||||
|
// traverse the DOM up to find first A tag
|
||||||
|
while (elm.length && lowercase(elm[0].nodeName) !== 'a') {
|
||||||
|
elm = elm.parent();
|
||||||
|
}
|
||||||
|
|
||||||
|
var absHref = elm.prop('href');
|
||||||
|
|
||||||
|
if (!absHref ||
|
||||||
|
elm.attr('target') ||
|
||||||
|
absHref.indexOf(absUrlPrefix) !== 0) { // link to different domain or base path
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update location with href without the prefix
|
||||||
|
$location.url(absHref.substr(absUrlPrefix.length));
|
||||||
|
$rootScope.$apply();
|
||||||
|
event.preventDefault();
|
||||||
|
// hack to work around FF6 bug 684208 when scenario runner clicks on links
|
||||||
|
window.angular['ff-684208-preventDefault'] = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// rewrite hashbang url <> html5 url
|
// rewrite hashbang url <> html5 url
|
||||||
if (currentUrl.absUrl() != initUrl) {
|
if ($location.absUrl() != initUrl) {
|
||||||
$browser.url(currentUrl.absUrl(), true);
|
$browser.url($location.absUrl(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// update $location when $browser url changes
|
// update $location when $browser url changes
|
||||||
$browser.onUrlChange(function(newUrl) {
|
$browser.onUrlChange(function(newUrl) {
|
||||||
if (currentUrl.absUrl() != newUrl) {
|
if ($location.absUrl() != newUrl) {
|
||||||
$rootScope.$evalAsync(function() {
|
$rootScope.$evalAsync(function() {
|
||||||
currentUrl.$$parse(newUrl);
|
var oldUrl = $location.absUrl();
|
||||||
|
|
||||||
|
$location.$$parse(newUrl);
|
||||||
|
afterLocationChange(oldUrl);
|
||||||
});
|
});
|
||||||
if (!$rootScope.$$phase) $rootScope.$digest();
|
if (!$rootScope.$$phase) $rootScope.$digest();
|
||||||
}
|
}
|
||||||
|
|
@ -537,17 +549,29 @@ function $LocationProvider(){
|
||||||
// update browser
|
// update browser
|
||||||
var changeCounter = 0;
|
var changeCounter = 0;
|
||||||
$rootScope.$watch(function $locationWatch() {
|
$rootScope.$watch(function $locationWatch() {
|
||||||
if ($browser.url() != currentUrl.absUrl()) {
|
var oldUrl = $browser.url();
|
||||||
|
|
||||||
|
if (!changeCounter || oldUrl != $location.absUrl()) {
|
||||||
changeCounter++;
|
changeCounter++;
|
||||||
$rootScope.$evalAsync(function() {
|
$rootScope.$evalAsync(function() {
|
||||||
$browser.url(currentUrl.absUrl(), currentUrl.$$replace);
|
if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl).
|
||||||
currentUrl.$$replace = false;
|
defaultPrevented) {
|
||||||
|
$location.$$parse(oldUrl);
|
||||||
|
} else {
|
||||||
|
$browser.url($location.absUrl(), $location.$$replace);
|
||||||
|
$location.$$replace = false;
|
||||||
|
afterLocationChange(oldUrl);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return changeCounter;
|
return changeCounter;
|
||||||
});
|
});
|
||||||
|
|
||||||
return currentUrl;
|
return $location;
|
||||||
|
|
||||||
|
function afterLocationChange(oldUrl) {
|
||||||
|
$rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl);
|
||||||
|
}
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -286,7 +286,6 @@ function $RouteProvider(){
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var matcher = switchRouteMatcher,
|
var matcher = switchRouteMatcher,
|
||||||
dirty = 0,
|
|
||||||
forceReload = false,
|
forceReload = false,
|
||||||
$route = {
|
$route = {
|
||||||
routes: routes,
|
routes: routes,
|
||||||
|
|
@ -304,12 +303,12 @@ function $RouteProvider(){
|
||||||
* creates new scope, reinstantiates the controller.
|
* creates new scope, reinstantiates the controller.
|
||||||
*/
|
*/
|
||||||
reload: function() {
|
reload: function() {
|
||||||
dirty++;
|
|
||||||
forceReload = true;
|
forceReload = true;
|
||||||
|
$rootScope.$evalAsync(updateRoute);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
$rootScope.$watch(function() { return dirty + $location.url(); }, updateRoute);
|
$rootScope.$on('$locationChangeSuccess', updateRoute);
|
||||||
|
|
||||||
return $route;
|
return $route;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -791,19 +791,6 @@ describe('$location', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should not rewrite when history disabled', function() {
|
|
||||||
configureService('#new', false);
|
|
||||||
inject(
|
|
||||||
initBrowser(),
|
|
||||||
initLocation(),
|
|
||||||
function($browser) {
|
|
||||||
browserTrigger(link, 'click');
|
|
||||||
expectNoRewrite($browser);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should not rewrite full url links do different domain', function() {
|
it('should not rewrite full url links do different domain', function() {
|
||||||
configureService('http://www.dot.abc/a?b=c', true);
|
configureService('http://www.dot.abc/a?b=c', true);
|
||||||
inject(
|
inject(
|
||||||
|
|
@ -982,4 +969,148 @@ describe('$location', function() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('location cancellation', function() {
|
||||||
|
it('should fire $before/afterLocationChange event', inject(function($location, $browser, $rootScope, $log) {
|
||||||
|
expect($browser.url()).toEqual('http://server/');
|
||||||
|
|
||||||
|
$rootScope.$on('$locationChangeStart', function(event, newUrl, oldUrl) {
|
||||||
|
$log.info('before', newUrl, oldUrl, $browser.url());
|
||||||
|
});
|
||||||
|
$rootScope.$on('$locationChangeSuccess', function(event, newUrl, oldUrl) {
|
||||||
|
$log.info('after', newUrl, oldUrl, $browser.url());
|
||||||
|
});
|
||||||
|
|
||||||
|
expect($location.url()).toEqual('');
|
||||||
|
$location.url('/somePath');
|
||||||
|
expect($location.url()).toEqual('/somePath');
|
||||||
|
expect($browser.url()).toEqual('http://server/');
|
||||||
|
expect($log.info.logs).toEqual([]);
|
||||||
|
|
||||||
|
$rootScope.$apply();
|
||||||
|
|
||||||
|
expect($log.info.logs.shift()).
|
||||||
|
toEqual(['before', 'http://server/#/somePath', 'http://server/', 'http://server/']);
|
||||||
|
expect($log.info.logs.shift()).
|
||||||
|
toEqual(['after', 'http://server/#/somePath', 'http://server/', 'http://server/#/somePath']);
|
||||||
|
expect($location.url()).toEqual('/somePath');
|
||||||
|
expect($browser.url()).toEqual('http://server/#/somePath');
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
it('should allow $locationChangeStart event cancellation', inject(function($location, $browser, $rootScope, $log) {
|
||||||
|
expect($browser.url()).toEqual('http://server/');
|
||||||
|
expect($location.url()).toEqual('');
|
||||||
|
|
||||||
|
$rootScope.$on('$locationChangeStart', function(event, newUrl, oldUrl) {
|
||||||
|
$log.info('before', newUrl, oldUrl, $browser.url());
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
$rootScope.$on('$locationChangeCompleted', function(event, newUrl, oldUrl) {
|
||||||
|
throw Error('location should have been canceled');
|
||||||
|
});
|
||||||
|
|
||||||
|
expect($location.url()).toEqual('');
|
||||||
|
$location.url('/somePath');
|
||||||
|
expect($location.url()).toEqual('/somePath');
|
||||||
|
expect($browser.url()).toEqual('http://server/');
|
||||||
|
expect($log.info.logs).toEqual([]);
|
||||||
|
|
||||||
|
$rootScope.$apply();
|
||||||
|
|
||||||
|
expect($log.info.logs.shift()).
|
||||||
|
toEqual(['before', 'http://server/#/somePath', 'http://server/', 'http://server/']);
|
||||||
|
expect($log.info.logs[1]).toBeUndefined();
|
||||||
|
expect($location.url()).toEqual('');
|
||||||
|
expect($browser.url()).toEqual('http://server/');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it ('should fire $locationChangeCompleted event when change from browser location bar',
|
||||||
|
inject(function($log, $location, $browser, $rootScope) {
|
||||||
|
$rootScope.$apply(); // clear initial $locationChangeStart
|
||||||
|
|
||||||
|
expect($browser.url()).toEqual('http://server/');
|
||||||
|
expect($location.url()).toEqual('');
|
||||||
|
|
||||||
|
$rootScope.$on('$locationChangeStart', function(event, newUrl, oldUrl) {
|
||||||
|
throw Error('there is no before when user enters URL directly to browser');
|
||||||
|
});
|
||||||
|
$rootScope.$on('$locationChangeSuccess', function(event, newUrl, oldUrl) {
|
||||||
|
$log.info('after', newUrl, oldUrl);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
$browser.url('http://server/#/somePath');
|
||||||
|
$browser.poll();
|
||||||
|
|
||||||
|
expect($log.info.logs.shift()).
|
||||||
|
toEqual(['after', 'http://server/#/somePath', 'http://server/']);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
it('should listen on click events on href and prevent browser default in hasbang mode', function() {
|
||||||
|
module(function() {
|
||||||
|
return function($rootElement, $compile, $rootScope) {
|
||||||
|
$rootElement.html('<a href="http://server/#/somePath">link</a>');
|
||||||
|
$compile($rootElement)($rootScope);
|
||||||
|
jqLite(document.body).append($rootElement);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
inject(function($location, $rootScope, $browser, $rootElement) {
|
||||||
|
var log = '',
|
||||||
|
link = $rootElement.find('a');
|
||||||
|
|
||||||
|
|
||||||
|
$rootScope.$on('$locationChangeStart', function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
log += '$locationChangeStart';
|
||||||
|
});
|
||||||
|
$rootScope.$on('$locationChangeCompleted', function() {
|
||||||
|
throw new Error('after cancellation in hashbang mode');
|
||||||
|
});
|
||||||
|
|
||||||
|
browserTrigger(link, 'click');
|
||||||
|
|
||||||
|
expect(log).toEqual('$locationChangeStart');
|
||||||
|
expect($browser.url()).toEqual('http://server/');
|
||||||
|
|
||||||
|
dealoc($rootElement);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should listen on click events on href and prevent browser default in html5 mode', function() {
|
||||||
|
module(function($locationProvider) {
|
||||||
|
$locationProvider.html5Mode(true);
|
||||||
|
return function($rootElement, $compile, $rootScope) {
|
||||||
|
$rootElement.html('<a href="http://server/somePath">link</a>');
|
||||||
|
$compile($rootElement)($rootScope);
|
||||||
|
jqLite(document.body).append($rootElement);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
inject(function($location, $rootScope, $browser, $rootElement) {
|
||||||
|
var log = '',
|
||||||
|
link = $rootElement.find('a');
|
||||||
|
|
||||||
|
$rootScope.$on('$locationChangeStart', function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
log += '$locationChangeStart';
|
||||||
|
});
|
||||||
|
$rootScope.$on('$locationChangeCompleted', function() {
|
||||||
|
throw new Error('after cancalation in html5 mode');
|
||||||
|
});
|
||||||
|
|
||||||
|
browserTrigger(link, 'click');
|
||||||
|
|
||||||
|
expect(log).toEqual('$locationChangeStart');
|
||||||
|
expect($browser.url()).toEqual('http://server/');
|
||||||
|
|
||||||
|
dealoc($rootElement);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,28 @@ describe('$route', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should not change route when location is canceled', function() {
|
||||||
|
module(function($routeProvider) {
|
||||||
|
$routeProvider.when('/somePath', {template: 'some path'});
|
||||||
|
});
|
||||||
|
inject(function($route, $location, $rootScope, $log) {
|
||||||
|
$rootScope.$on('$locationChangeStart', function(event) {
|
||||||
|
$log.info('$locationChangeStart');
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
$rootScope.$on('$beforeRouteChange', function(event) {
|
||||||
|
throw new Error('Should not get here');
|
||||||
|
});
|
||||||
|
|
||||||
|
$location.path('/somePath');
|
||||||
|
$rootScope.$digest();
|
||||||
|
|
||||||
|
expect($log.info.logs.shift()).toEqual(['$locationChangeStart']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should match a route that contains special chars in the path', function() {
|
it('should match a route that contains special chars in the path', function() {
|
||||||
module(function($routeProvider) {
|
module(function($routeProvider) {
|
||||||
$routeProvider.when('/$test.23/foo(bar)/:baz', {templateUrl: 'test.html'});
|
$routeProvider.when('/$test.23/foo(bar)/:baz', {templateUrl: 'test.html'});
|
||||||
|
|
@ -540,8 +562,11 @@ describe('$route', function() {
|
||||||
});
|
});
|
||||||
inject(function($route, $location, $rootScope) {
|
inject(function($route, $location, $rootScope) {
|
||||||
var replace;
|
var replace;
|
||||||
$rootScope.$watch(function() {
|
|
||||||
if (isUndefined(replace)) replace = $location.$$replace;
|
$rootScope.$on('$locationChangeStart', function(event, newUrl, oldUrl) {
|
||||||
|
if (oldUrl == 'http://server/#/foo/id3/eId') {
|
||||||
|
replace = $location.$$replace;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$location.path('/foo/id3/eId');
|
$location.path('/foo/id3/eId');
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue