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.} search * @property {string} hash * @property {string} hashPath * @property {Object.} 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 clear hash | test hash
$location = {{$location}}
*/ 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 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)} 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 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)} 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 *
   *   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 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']);