angular.js/src/service/location.js
Igor Minar 1777110958 split up services into individual files
- split up services into files under src/service
- split up specs into files under test/service
- rewrite all specs so that they don't depend on one global forEach
- get rid of obsolete code and tests in ng:switch
- rename mock $log spec from "$log" to "$log mock"
2011-02-15 11:01:53 -05:00

264 lines
6.9 KiB
JavaScript

var URL_MATCH = /^(file|ftp|http|https):\/\/(\w+:{0,1}\w*@)?([\w\.-]*)(:([0-9]+))?(\/[^\?#]*)?(\?([^#]*))?(#(.*))?$/,
HASH_MATCH = /^([^\?]*)?(\?([^\?]*))?$/,
DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp':21};
/**
* @workInProgress
* @ngdoc service
* @name angular.service.$location
* @requires $browser
*
* @property {string} href
* @property {string} protocol
* @property {string} host
* @property {number} port
* @property {string} path
* @property {Object.<string|boolean>} search
* @property {string} hash
* @property {string} hashPath
* @property {Object.<string|boolean>} hashSearch
*
* @description
* Parses the browser location url and makes it available to your application.
* Any changes to the url are reflected into $location service and changes to
* $location are reflected to url.
* Notice that using browser's forward/back buttons changes the $location.
*
* @example
<doc:example>
<doc:source>
<a href="#">clear hash</a> |
<a href="#myPath?name=misko">test hash</a><br/>
<input type='text' name="$location.hash"/>
<pre>$location = {{$location}}</pre>
</doc:source>
<doc:scenario>
</doc:scenario>
</doc:example>
*/
angularServiceInject("$location", function($browser) {
var scope = this,
location = {update:update, updateHash: updateHash},
lastLocation = {};
$browser.onHashChange(function() { //register
update($browser.getUrl());
copy(location, lastLocation);
scope.$eval();
})(); //initialize
this.$onEval(PRIORITY_FIRST, sync);
this.$onEval(PRIORITY_LAST, updateBrowser);
return location;
// PUBLIC METHODS
/**
* @workInProgress
* @ngdoc method
* @name angular.service.$location#update
* @methodOf angular.service.$location
*
* @description
* Update location object
* Does not immediately update the browser
* Browser is updated at the end of $eval()
*
* @example
<doc:example>
<doc:source>
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}});
</doc:source>
<doc:scenario>
</doc:scenario>
</doc:example>
*
* @param {(string|Object)} href Full href as a string or object with properties
*/
function update(href) {
if (isString(href)) {
extend(location, parseHref(href));
} else {
if (isDefined(href.hash)) {
extend(href, isString(href.hash) ? parseHash(href.hash) : href.hash);
}
extend(location, href);
if (isDefined(href.hashPath || href.hashSearch)) {
location.hash = composeHash(location);
}
location.href = composeHref(location);
}
}
/**
* @workInProgress
* @ngdoc method
* @name angular.service.$location#updateHash
* @methodOf angular.service.$location
*
* @description
* Update location hash part
* @see update()
*
* @example
<doc:example>
<doc:source>
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}})
</doc:source>
<doc:scenario>
</doc:scenario>
</doc:example>
*
* @param {(string|Object)} path A hashPath or hashSearch object
* @param {Object=} search A hashSearch object
*/
function updateHash(path, search) {
var hash = {};
if (isString(path)) {
hash.hashPath = path;
hash.hashSearch = search || {};
} else
hash.hashSearch = path;
hash.hash = composeHash(hash);
update({hash: hash});
}
// INNER METHODS
/**
* Synchronizes all location object properties.
*
* User is allowed to change properties, so after property change,
* location object is not in consistent state.
*
* Properties are synced with the following precedence order:
*
* - `$location.href`
* - `$location.hash`
* - everything else
*
* @example
* <pre>
* scope.$location.href = 'http://www.angularjs.org/path#a/b'
* </pre>
* immediately after this call, other properties are still the old ones...
*
* This method checks the changes and update location to the consistent state
*/
function sync() {
if (!equals(location, lastLocation)) {
if (location.href != lastLocation.href) {
update(location.href);
return;
}
if (location.hash != lastLocation.hash) {
var hash = parseHash(location.hash);
updateHash(hash.hashPath, hash.hashSearch);
} else {
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() {
sync();
if ($browser.getUrl() != location.href) {
$browser.setUrl(location.href);
copy(location, lastLocation);
}
}
/**
* Compose href string from a location object
*
* @param {Object} loc The 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} loc Object with hashPath and hashSearch properties
* @return {string} Hash string
*/
function composeHash(loc) {
var hashSearch = toKeyValue(loc.hashSearch);
//TODO: temporary fix for issue #158
return escape(loc.hashPath).replace(/%21/gi, '!').replace(/%3A/gi, ':').replace(/%24/gi, '$') +
(hashSearch ? '?' + hashSearch : '');
}
/**
* Parse href string into location object
*
* @param {string} href
* @return {Object} The location object
*/
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
*/
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']);