diff options
Diffstat (limited to 'src/service/location.js')
| -rw-r--r-- | src/service/location.js | 264 |
1 files changed, 264 insertions, 0 deletions
diff --git a/src/service/location.js b/src/service/location.js new file mode 100644 index 00000000..31323284 --- /dev/null +++ b/src/service/location.js @@ -0,0 +1,264 @@ +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']); |
