2011-02-15 06:12:45 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @workInProgress
|
|
|
|
|
|
* @ngdoc service
|
|
|
|
|
|
* @name angular.service.$route
|
|
|
|
|
|
* @requires $location
|
|
|
|
|
|
*
|
|
|
|
|
|
* @property {Object} current Reference to the current route definition.
|
|
|
|
|
|
* @property {Array.<Object>} routes Array of all configured routes.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @description
|
|
|
|
|
|
* Watches `$location.hashPath` and tries to map the hash to an existing route
|
|
|
|
|
|
* definition. It is used for deep-linking URLs to controllers and views (HTML partials).
|
|
|
|
|
|
*
|
|
|
|
|
|
* The `$route` service is typically used in conjunction with {@link angular.widget.ng:view ng:view}
|
|
|
|
|
|
* widget.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @example
|
|
|
|
|
|
This example shows how changing the URL hash causes the <tt>$route</tt>
|
|
|
|
|
|
to match a route against the URL, and the <tt>[[ng:include]]</tt> pulls in the partial.
|
|
|
|
|
|
Try changing the URL in the input box to see changes.
|
|
|
|
|
|
|
|
|
|
|
|
<doc:example>
|
|
|
|
|
|
<doc:source>
|
|
|
|
|
|
<script>
|
|
|
|
|
|
angular.service('myApp', function($route) {
|
|
|
|
|
|
$route.when('/Book/:bookId', {template:'rsrc/book.html', controller:BookCntl});
|
|
|
|
|
|
$route.when('/Book/:bookId/ch/:chapterId', {template:'rsrc/chapter.html', controller:ChapterCntl});
|
|
|
|
|
|
$route.onChange(function() {
|
|
|
|
|
|
$route.current.scope.params = $route.current.params;
|
|
|
|
|
|
});
|
|
|
|
|
|
}, {$inject: ['$route']});
|
|
|
|
|
|
|
|
|
|
|
|
function BookCntl() {
|
|
|
|
|
|
this.name = "BookCntl";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function ChapterCntl() {
|
|
|
|
|
|
this.name = "ChapterCntl";
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
Chose:
|
|
|
|
|
|
<a href="#/Book/Moby">Moby</a> |
|
|
|
|
|
|
<a href="#/Book/Moby/ch/1">Moby: Ch1</a> |
|
|
|
|
|
|
<a href="#/Book/Gatsby">Gatsby</a> |
|
|
|
|
|
|
<a href="#/Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a><br/>
|
|
|
|
|
|
<input type="text" name="$location.hashPath" size="80" />
|
|
|
|
|
|
<pre>$location={{$location}}</pre>
|
|
|
|
|
|
<pre>$route.current.template={{$route.current.template}}</pre>
|
|
|
|
|
|
<pre>$route.current.params={{$route.current.params}}</pre>
|
|
|
|
|
|
<pre>$route.current.scope.name={{$route.current.scope.name}}</pre>
|
|
|
|
|
|
<hr/>
|
|
|
|
|
|
<ng:include src="$route.current.template" scope="$route.current.scope"/>
|
|
|
|
|
|
</doc:source>
|
|
|
|
|
|
<doc:scenario>
|
|
|
|
|
|
</doc:scenario>
|
|
|
|
|
|
</doc:example>
|
|
|
|
|
|
*/
|
|
|
|
|
|
angularServiceInject('$route', function(location, $updateView) {
|
|
|
|
|
|
var routes = {},
|
|
|
|
|
|
onChange = [],
|
|
|
|
|
|
matcher = switchRouteMatcher,
|
|
|
|
|
|
parentScope = this,
|
|
|
|
|
|
dirty = 0,
|
|
|
|
|
|
$route = {
|
|
|
|
|
|
routes: routes,
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @workInProgress
|
|
|
|
|
|
* @ngdoc method
|
|
|
|
|
|
* @name angular.service.$route#onChange
|
|
|
|
|
|
* @methodOf angular.service.$route
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {function()} fn Function that will be called when `$route.current` changes.
|
|
|
|
|
|
* @returns {function()} The registered function.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @description
|
|
|
|
|
|
* Register a handler function that will be called when route changes
|
|
|
|
|
|
*/
|
|
|
|
|
|
onChange: function(fn) {
|
|
|
|
|
|
onChange.push(fn);
|
|
|
|
|
|
return fn;
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @workInProgress
|
|
|
|
|
|
* @ngdoc method
|
|
|
|
|
|
* @name angular.service.$route#parent
|
|
|
|
|
|
* @methodOf angular.service.$route
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {Scope} [scope=rootScope] Scope to be used as parent for newly created
|
|
|
|
|
|
* `$route.current.scope` scopes.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @description
|
|
|
|
|
|
* Sets a scope to be used as the parent scope for scopes created on route change. If not
|
|
|
|
|
|
* set, defaults to the root scope.
|
|
|
|
|
|
*/
|
|
|
|
|
|
parent: function(scope) {
|
|
|
|
|
|
if (scope) parentScope = scope;
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @workInProgress
|
|
|
|
|
|
* @ngdoc method
|
|
|
|
|
|
* @name angular.service.$route#when
|
|
|
|
|
|
* @methodOf angular.service.$route
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {string} path Route path (matched against `$location.hash`)
|
|
|
|
|
|
* @param {Object} params Mapping information to be assigned to `$route.current` on route
|
|
|
|
|
|
* match.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Object properties:
|
|
|
|
|
|
*
|
|
|
|
|
|
* - `controller` – `{function()=}` – Controller fn that should be associated with newly
|
|
|
|
|
|
* created scope.
|
|
|
|
|
|
* - `template` – `{string=}` – path to an html template that should be used by
|
|
|
|
|
|
* {@link angular.widget.ng:view ng:view} or
|
|
|
|
|
|
* {@link angular.widget.ng:include ng:include} widgets.
|
|
|
|
|
|
* - `redirectTo` – {(string|function())=} – value to update
|
|
|
|
|
|
* {@link angular.service.$location $location} hash with and trigger route redirection.
|
|
|
|
|
|
*
|
|
|
|
|
|
* If `redirectTo` is a function, it will be called with the following parameters:
|
|
|
|
|
|
*
|
|
|
|
|
|
* - `{Object.<string>}` - route parameters extracted from the current
|
|
|
|
|
|
* `$location.hashPath` by applying the current route template.
|
|
|
|
|
|
* - `{string}` - current `$location.hash`
|
|
|
|
|
|
* - `{string}` - current `$location.hashPath`
|
|
|
|
|
|
* - `{string}` - current `$location.hashSearch`
|
|
|
|
|
|
*
|
|
|
|
|
|
* The custom `redirectTo` function is expected to return a string which will be used
|
|
|
|
|
|
* to update `$location.hash`.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @returns {Object} route object
|
|
|
|
|
|
*
|
|
|
|
|
|
* @description
|
|
|
|
|
|
* Adds a new route definition to the `$route` service.
|
|
|
|
|
|
*/
|
|
|
|
|
|
when:function (path, params) {
|
|
|
|
|
|
if (isUndefined(path)) return routes; //TODO(im): remove - not needed!
|
|
|
|
|
|
var route = routes[path];
|
|
|
|
|
|
if (!route) route = routes[path] = {};
|
|
|
|
|
|
if (params) extend(route, params);
|
|
|
|
|
|
dirty++;
|
|
|
|
|
|
return route;
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @workInProgress
|
|
|
|
|
|
* @ngdoc method
|
|
|
|
|
|
* @name angular.service.$route#otherwise
|
|
|
|
|
|
* @methodOf angular.service.$route
|
|
|
|
|
|
*
|
|
|
|
|
|
* @description
|
|
|
|
|
|
* Sets route definition that will be used on route change when no other route definition
|
|
|
|
|
|
* is matched.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {Object} params Mapping information to be assigned to `$route.current`.
|
|
|
|
|
|
*/
|
|
|
|
|
|
otherwise: function(params) {
|
|
|
|
|
|
$route.when(null, params);
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @workInProgress
|
|
|
|
|
|
* @ngdoc method
|
|
|
|
|
|
* @name angular.service.$route#reload
|
|
|
|
|
|
* @methodOf angular.service.$route
|
|
|
|
|
|
*
|
|
|
|
|
|
* @description
|
|
|
|
|
|
* Causes `$route` service to reload (and recreate the `$route.current` scope) upon the next
|
|
|
|
|
|
* eval even if {@link angular.service.$location $location} hasn't changed.
|
|
|
|
|
|
*/
|
|
|
|
|
|
reload: function() {
|
|
|
|
|
|
dirty++;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function switchRouteMatcher(on, when, dstName) {
|
|
|
|
|
|
var regex = '^' + when.replace(/[\.\\\(\)\^\$]/g, "\$1") + '$',
|
|
|
|
|
|
params = [],
|
|
|
|
|
|
dst = {};
|
|
|
|
|
|
forEach(when.split(/\W/), function(param){
|
|
|
|
|
|
if (param) {
|
|
|
|
|
|
var paramRegExp = new RegExp(":" + param + "([\\W])");
|
|
|
|
|
|
if (regex.match(paramRegExp)) {
|
|
|
|
|
|
regex = regex.replace(paramRegExp, "([^\/]*)$1");
|
|
|
|
|
|
params.push(param);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
var match = on.match(new RegExp(regex));
|
|
|
|
|
|
if (match) {
|
|
|
|
|
|
forEach(params, function(name, index){
|
|
|
|
|
|
dst[name] = match[index + 1];
|
|
|
|
|
|
});
|
|
|
|
|
|
if (dstName) this.$set(dstName, dst);
|
|
|
|
|
|
}
|
2011-03-26 23:06:38 +00:00
|
|
|
|
return match ? dst : null;
|
2011-02-15 06:12:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function updateRoute(){
|
|
|
|
|
|
var childScope, routeParams, pathParams, segmentMatch, key, redir;
|
|
|
|
|
|
|
2011-03-26 23:06:38 +00:00
|
|
|
|
$route.current = null;
|
2011-02-15 06:12:45 +00:00
|
|
|
|
forEach(routes, function(rParams, rPath) {
|
|
|
|
|
|
if (!pathParams) {
|
|
|
|
|
|
if (pathParams = matcher(location.hashPath, rPath)) {
|
|
|
|
|
|
routeParams = rParams;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// "otherwise" fallback
|
2011-03-26 23:06:38 +00:00
|
|
|
|
routeParams = routeParams || routes[null];
|
2011-02-15 06:12:45 +00:00
|
|
|
|
|
|
|
|
|
|
if(routeParams) {
|
|
|
|
|
|
if (routeParams.redirectTo) {
|
|
|
|
|
|
if (isString(routeParams.redirectTo)) {
|
|
|
|
|
|
// interpolate the redirectTo string
|
|
|
|
|
|
redir = {hashPath: '',
|
|
|
|
|
|
hashSearch: extend({}, location.hashSearch, pathParams)};
|
|
|
|
|
|
|
|
|
|
|
|
forEach(routeParams.redirectTo.split(':'), function(segment, i) {
|
|
|
|
|
|
if (i==0) {
|
|
|
|
|
|
redir.hashPath += segment;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
segmentMatch = segment.match(/(\w+)(.*)/);
|
|
|
|
|
|
key = segmentMatch[1];
|
|
|
|
|
|
redir.hashPath += pathParams[key] || location.hashSearch[key];
|
|
|
|
|
|
redir.hashPath += segmentMatch[2] || '';
|
|
|
|
|
|
delete redir.hashSearch[key];
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// call custom redirectTo function
|
|
|
|
|
|
redir = {hash: routeParams.redirectTo(pathParams, location.hash, location.hashPath,
|
|
|
|
|
|
location.hashSearch)};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
location.update(redir);
|
|
|
|
|
|
$updateView(); //TODO this is to work around the $location<=>$browser issues
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
childScope = createScope(parentScope);
|
|
|
|
|
|
$route.current = extend({}, routeParams, {
|
|
|
|
|
|
scope: childScope,
|
|
|
|
|
|
params: extend({}, location.hashSearch, pathParams)
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//fire onChange callbacks
|
|
|
|
|
|
forEach(onChange, parentScope.$tryEval);
|
|
|
|
|
|
|
|
|
|
|
|
if (childScope) {
|
|
|
|
|
|
childScope.$become($route.current.controller);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.$watch(function(){return dirty + location.hash;}, updateRoute);
|
|
|
|
|
|
|
|
|
|
|
|
return $route;
|
|
|
|
|
|
}, ['$location', '$updateView']);
|