angular.js/src/service/route.js
Igor Minar 1e59822df7 remove _null and _undefined
they have no significant effect on minified and gziped size. in fact
they make things worse.

file        | before     | after removal
----------------------------------------
concat      | 325415     | 325297
min         | 62070      | 62161
min + gzip  | 25187      | 25176

The bottom line is that we are getting 0.05% decrease in size after
gzip without all of the hassle of using underscores everywhere.
2011-03-26 23:19:04 -07:00

266 lines
9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @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);
}
return match ? dst : null;
}
function updateRoute(){
var childScope, routeParams, pathParams, segmentMatch, key, redir;
$route.current = null;
forEach(routes, function(rParams, rPath) {
if (!pathParams) {
if (pathParams = matcher(location.hashPath, rPath)) {
routeParams = rParams;
}
}
});
// "otherwise" fallback
routeParams = routeParams || routes[null];
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']);