2011-07-17 08:05:43 +00:00
'use strict' ;
2011-02-15 06:12:45 +00:00
var URL _MATCH = /^(file|ftp|http|https):\/\/(\w+:{0,1}\w*@)?([\w\.-]*)(:([0-9]+))?(\/[^\?#]*)?(\?([^#]*))?(#(.*))?$/ ,
2011-06-29 16:30:39 +00:00
PATH _MATCH = /^([^\?#]*)?(\?([^#]*))?(#(.*))?$/ ,
HASH _MATCH = PATH _MATCH ,
DEFAULT _PORTS = { 'http' : 80 , 'https' : 443 , 'ftp' : 21 } ;
2011-02-15 06:12:45 +00:00
/ * *
2011-06-29 16:30:39 +00:00
* Encode path using encodeUriSegment , ignoring forward slashes
2011-02-15 06:12:45 +00:00
*
2011-06-29 16:30:39 +00:00
* @ param { string } path Path to encode
* @ returns { string }
* /
function encodePath ( path ) {
var segments = path . split ( '/' ) ,
i = segments . length ;
while ( i -- ) {
segments [ i ] = encodeUriSegment ( segments [ i ] ) ;
}
return segments . join ( '/' ) ;
}
function matchUrl ( url , obj ) {
var match = URL _MATCH . exec ( url ) ,
match = {
protocol : match [ 1 ] ,
host : match [ 3 ] ,
port : parseInt ( match [ 5 ] ) || DEFAULT _PORTS [ match [ 1 ] ] || null ,
path : match [ 6 ] || '/' ,
search : match [ 8 ] ,
hash : match [ 10 ]
} ;
if ( obj ) {
obj . $$protocol = match . protocol ;
obj . $$host = match . host ;
obj . $$port = match . port ;
}
return match ;
}
function composeProtocolHostPort ( protocol , host , port ) {
return protocol + '://' + host + ( port == DEFAULT _PORTS [ protocol ] ? '' : ':' + port ) ;
}
function pathPrefixFromBase ( basePath ) {
return basePath . substr ( 0 , basePath . lastIndexOf ( '/' ) ) ;
}
function convertToHtml5Url ( url , basePath , hashPrefix ) {
var match = matchUrl ( url ) ;
// already html5 url
if ( decodeURIComponent ( match . path ) != basePath || isUndefined ( match . hash ) ||
match . hash . indexOf ( hashPrefix ) != 0 ) {
return url ;
// convert hashbang url -> html5 url
} else {
return composeProtocolHostPort ( match . protocol , match . host , match . port ) +
pathPrefixFromBase ( basePath ) + match . hash . substr ( hashPrefix . length ) ;
}
}
function convertToHashbangUrl ( url , basePath , hashPrefix ) {
var match = matchUrl ( url ) ;
// already hashbang url
if ( decodeURIComponent ( match . path ) == basePath ) {
return url ;
// convert html5 url -> hashbang url
} else {
var search = match . search && '?' + match . search || '' ,
hash = match . hash && '#' + match . hash || '' ,
pathPrefix = pathPrefixFromBase ( basePath ) ,
path = match . path . substr ( pathPrefix . length ) ;
if ( match . path . indexOf ( pathPrefix ) != 0 ) {
throw 'Invalid url "' + url + '", missing path prefix "' + pathPrefix + '" !' ;
}
return composeProtocolHostPort ( match . protocol , match . host , match . port ) + basePath +
'#' + hashPrefix + path + search + hash ;
}
}
/ * *
* LocationUrl represents an url
* This object is exposed as $location service when html5 is enabled and supported
2011-04-08 16:54:29 +00:00
*
2011-06-29 16:30:39 +00:00
* @ constructor
* @ param { string } url Html5 url
* @ param { string } pathPrefix
* /
function LocationUrl ( url , pathPrefix ) {
pathPrefix = pathPrefix || '' ;
/ * *
* Parse given html5 ( regular ) url string into properties
* @ param { string } url Html5 url
* @ private
* /
this . $$parse = function ( url ) {
var match = matchUrl ( url , this ) ;
if ( match . path . indexOf ( pathPrefix ) != 0 ) {
throw 'Invalid url "' + url + '", missing path prefix "' + pathPrefix + '" !' ;
}
this . $$path = decodeURIComponent ( match . path . substr ( pathPrefix . length ) ) ;
this . $$search = parseKeyValue ( match . search ) ;
this . $$hash = match . hash && decodeURIComponent ( match . hash ) || '' ;
this . $$compose ( ) ;
} ,
/ * *
* Compose url and update ` absUrl ` property
* @ private
* /
this . $$compose = function ( ) {
var search = toKeyValue ( this . $$search ) ,
hash = this . $$hash ? '#' + encodeUriSegment ( this . $$hash ) : '' ;
this . $$url = encodePath ( this . $$path ) + ( search ? '?' + search : '' ) + hash ;
this . $$absUrl = composeProtocolHostPort ( this . $$protocol , this . $$host , this . $$port ) +
pathPrefix + this . $$url ;
} ;
this . $$parse ( url ) ;
}
/ * *
* LocationHashbangUrl represents url
* This object is exposed as $location service when html5 history api is disabled or not supported
2011-02-15 06:12:45 +00:00
*
2011-06-29 16:30:39 +00:00
* @ constructor
* @ param { string } url Legacy url
* @ param { string } hashPrefix Prefix for hash part ( containing path and search )
2011-02-15 06:12:45 +00:00
* /
2011-06-29 16:30:39 +00:00
function LocationHashbangUrl ( url , hashPrefix ) {
var basePath ;
/ * *
* Parse given hashbang url into properties
* @ param { string } url Hashbang url
* @ private
* /
this . $$parse = function ( url ) {
var match = matchUrl ( url , this ) ;
if ( match . hash && match . hash . indexOf ( hashPrefix ) != 0 ) {
throw 'Invalid url "' + url + '", missing hash prefix "' + hashPrefix + '" !' ;
}
basePath = match . path + ( match . search ? '?' + match . search : '' ) ;
match = HASH _MATCH . exec ( ( match . hash || '' ) . substr ( hashPrefix . length ) ) ;
if ( match [ 1 ] ) {
this . $$path = ( match [ 1 ] . charAt ( 0 ) == '/' ? '' : '/' ) + decodeURIComponent ( match [ 1 ] ) ;
} else {
this . $$path = '' ;
}
this . $$search = parseKeyValue ( match [ 3 ] ) ;
this . $$hash = match [ 5 ] && decodeURIComponent ( match [ 5 ] ) || '' ;
this . $$compose ( ) ;
} ;
/ * *
* Compose hashbang url and update ` absUrl ` property
* @ private
* /
this . $$compose = function ( ) {
var search = toKeyValue ( this . $$search ) ,
hash = this . $$hash ? '#' + encodeUriSegment ( this . $$hash ) : '' ;
2011-02-15 06:12:45 +00:00
2011-06-29 16:30:39 +00:00
this . $$url = encodePath ( this . $$path ) + ( search ? '?' + search : '' ) + hash ;
this . $$absUrl = composeProtocolHostPort ( this . $$protocol , this . $$host , this . $$port ) +
basePath + ( this . $$url ? '#' + hashPrefix + this . $$url : '' ) ;
} ;
2011-02-15 06:12:45 +00:00
2011-06-29 16:30:39 +00:00
this . $$parse ( url ) ;
}
2011-02-15 06:12:45 +00:00
2011-06-29 16:30:39 +00:00
LocationUrl . prototype = LocationHashbangUrl . prototype = {
/ * *
* Has any change been replacing ?
* @ private
* /
$$replace : false ,
2011-02-15 06:12:45 +00:00
/ * *
* @ ngdoc method
2011-06-29 16:30:39 +00:00
* @ name angular . service . $location # absUrl
2011-02-15 06:12:45 +00:00
* @ methodOf angular . service . $location
*
* @ description
2011-06-29 16:30:39 +00:00
* This method is getter only .
2011-02-15 06:12:45 +00:00
*
2011-06-29 16:30:39 +00:00
* Return full url representation with all segments encoded according to rules specified in
* { @ link http : //www.ietf.org/rfc/rfc3986.txt RFC 3986}.
2011-02-15 06:12:45 +00:00
*
2011-06-29 16:30:39 +00:00
* @ return { string }
2011-02-15 06:12:45 +00:00
* /
2011-06-29 16:30:39 +00:00
absUrl : locationGetter ( '$$absUrl' ) ,
2011-02-15 06:12:45 +00:00
/ * *
* @ ngdoc method
2011-06-29 16:30:39 +00:00
* @ name angular . service . $location # url
2011-02-15 06:12:45 +00:00
* @ methodOf angular . service . $location
*
* @ description
2011-06-29 16:30:39 +00:00
* This method is getter / setter .
2011-04-08 16:54:29 +00:00
*
2011-06-29 16:30:39 +00:00
* Return url ( e . g . ` /path?a=b#hash ` ) when called without any parameter .
2011-02-15 06:12:45 +00:00
*
2011-06-29 16:30:39 +00:00
* Change path , search and hash , when called with parameter and return ` $ location ` .
2011-02-15 06:12:45 +00:00
*
2011-06-29 16:30:39 +00:00
* @ param { string = } url New url without base prefix ( e . g . ` /path?a=b#hash ` )
* @ return { string }
2011-02-15 06:12:45 +00:00
* /
2011-06-29 16:30:39 +00:00
url : function ( url , replace ) {
if ( isUndefined ( url ) )
return this . $$url ;
2011-02-15 06:12:45 +00:00
2011-06-29 16:30:39 +00:00
var match = PATH _MATCH . exec ( url ) ;
this . path ( decodeURIComponent ( match [ 1 ] || '' ) ) . search ( match [ 3 ] || '' )
. hash ( match [ 5 ] || '' , replace ) ;
2011-02-15 06:12:45 +00:00
2011-06-29 16:30:39 +00:00
return this ;
} ,
2011-02-15 06:12:45 +00:00
2011-06-29 16:30:39 +00:00
/ * *
* @ ngdoc method
* @ name angular . service . $location # protocol
* @ methodOf angular . service . $location
*
* @ description
* This method is getter only .
*
* Return protocol of current url .
*
* @ return { string }
* /
protocol : locationGetter ( '$$protocol' ) ,
2011-02-15 06:12:45 +00:00
2011-06-29 16:30:39 +00:00
/ * *
* @ ngdoc method
* @ name angular . service . $location # host
* @ methodOf angular . service . $location
*
* @ description
* This method is getter only .
*
* Return host of current url .
*
* @ return { string }
* /
host : locationGetter ( '$$host' ) ,
2011-02-15 06:12:45 +00:00
2011-06-29 16:30:39 +00:00
/ * *
* @ ngdoc method
* @ name angular . service . $location # port
* @ methodOf angular . service . $location
*
* @ description
* This method is getter only .
*
* Return port of current url .
*
* @ return { Number }
* /
port : locationGetter ( '$$port' ) ,
2011-02-15 06:12:45 +00:00
/ * *
2011-06-29 16:30:39 +00:00
* @ ngdoc method
* @ name angular . service . $location # path
* @ methodOf angular . service . $location
2011-02-15 06:12:45 +00:00
*
2011-06-29 16:30:39 +00:00
* @ description
* This method is getter / setter .
2011-02-15 06:12:45 +00:00
*
2011-06-29 16:30:39 +00:00
* Return path of current url when called without any parameter .
2011-02-15 06:12:45 +00:00
*
2011-06-29 16:30:39 +00:00
* Change path when called with parameter and return ` $ location ` .
2011-02-15 06:12:45 +00:00
*
2011-06-29 16:30:39 +00:00
* Note : Path should always begin with forward slash ( / ) , t h i s m e t h o d w i l l a d d t h e f o r w a r d s l a s h
* if it is missing .
2011-04-08 16:54:29 +00:00
*
2011-06-29 16:30:39 +00:00
* @ param { string = } path New path
* @ return { string }
* /
path : locationGetterSetter ( '$$path' , function ( path ) {
return path . charAt ( 0 ) == '/' ? path : '/' + path ;
} ) ,
/ * *
* @ ngdoc method
* @ name angular . service . $location # search
* @ methodOf angular . service . $location
*
* @ description
* This method is getter / setter .
*
* Return search part ( as object ) of current url when called without any parameter .
2011-04-08 16:54:29 +00:00
*
2011-06-29 16:30:39 +00:00
* Change search part when called with parameter and return ` $ location ` .
2011-02-15 06:12:45 +00:00
*
2011-06-29 16:30:39 +00:00
* @ param { string | object < string , string >= } search New search part - string or hash object
* @ return { string }
2011-02-15 06:12:45 +00:00
* /
2011-06-29 16:30:39 +00:00
search : function ( search , paramValue ) {
if ( isUndefined ( search ) )
return this . $$search ;
if ( isDefined ( paramValue ) ) {
if ( paramValue === null ) {
delete this . $$search [ search ] ;
2011-02-15 06:12:45 +00:00
} else {
2011-09-02 20:28:52 +00:00
this . $$search [ search ] = encodeUriQuery ( paramValue ) ;
2011-02-15 06:12:45 +00:00
}
2011-06-29 16:30:39 +00:00
} else {
this . $$search = isString ( search ) ? parseKeyValue ( search ) : search ;
2011-02-15 06:12:45 +00:00
}
2011-06-29 16:30:39 +00:00
this . $$compose ( ) ;
return this ;
} ,
2011-02-15 06:12:45 +00:00
/ * *
2011-06-29 16:30:39 +00:00
* @ ngdoc method
* @ name angular . service . $location # hash
* @ methodOf angular . service . $location
*
* @ description
* This method is getter / setter .
*
* Return hash fragment when called without any parameter .
2011-02-15 06:12:45 +00:00
*
2011-06-29 16:30:39 +00:00
* Change hash fragment when called with parameter and return ` $ location ` .
*
* @ param { string = } hash New hash fragment
* @ return { string }
2011-02-15 06:12:45 +00:00
* /
2011-06-29 16:30:39 +00:00
hash : locationGetterSetter ( '$$hash' , identity ) ,
2011-02-15 06:12:45 +00:00
/ * *
2011-06-29 16:30:39 +00:00
* @ ngdoc method
* @ name angular . service . $location # replace
* @ methodOf angular . service . $location
2011-02-15 06:12:45 +00:00
*
2011-06-29 16:30:39 +00:00
* @ description
* If called , all changes to $location during current ` $ digest ` will be replacing current history
* record , instead of adding new one .
2011-02-15 06:12:45 +00:00
* /
2011-06-29 16:30:39 +00:00
replace : function ( ) {
this . $$replace = true ;
return this ;
2011-02-15 06:12:45 +00:00
}
2011-06-29 16:30:39 +00:00
} ;
2011-02-15 06:12:45 +00:00
2011-06-29 16:30:39 +00:00
function locationGetter ( property ) {
return function ( ) {
return this [ property ] ;
} ;
}
function locationGetterSetter ( property , preprocess ) {
return function ( value ) {
if ( isUndefined ( value ) )
return this [ property ] ;
this [ property ] = preprocess ( value ) ;
this . $$compose ( ) ;
return this ;
} ;
}
/ * *
* @ ngdoc service
* @ name angular . service . $location
*
* @ requires $browser
* @ requires $sniffer
* @ requires $config
* @ requires $document
*
* @ description
* The $location service parses the URL in the browser address bar ( based on the { @ link https : //developer.mozilla.org/en/window.location window.location}) and makes the URL available to your application. Changes to the URL in the address bar are reflected into $location service and changes to $location are reflected into the browser address bar.
*
* * * The $location service : * *
*
* - Exposes the current URL in the browser address bar , so you can
* - Watch and observe the URL .
* - Change the URL .
* - Synchronizes the URL with the browser when the user
* - Changes the address bar .
* - Clicks the back or forward button ( or clicks a History link ) .
* - Clicks on a link .
* - Represents the URL object as a set of methods ( protocol , host , port , path , search , hash ) .
*
* For more information see { @ link guide / dev _guide . services . $location Developer Guide : Angular Services : Using $location }
* /
angularServiceInject ( '$location' , function ( $browser , $sniffer , $config , $document ) {
var scope = this , currentUrl ,
basePath = $browser . baseHref ( ) || '/' ,
pathPrefix = pathPrefixFromBase ( basePath ) ,
hashPrefix = $config . hashPrefix || '' ,
initUrl = $browser . url ( ) ;
if ( $config . html5Mode ) {
if ( $sniffer . history ) {
currentUrl = new LocationUrl ( convertToHtml5Url ( initUrl , basePath , hashPrefix ) , pathPrefix ) ;
} else {
currentUrl = new LocationHashbangUrl ( convertToHashbangUrl ( initUrl , basePath , hashPrefix ) ,
hashPrefix ) ;
2011-02-15 06:12:45 +00:00
}
2011-06-29 16:30:39 +00:00
// link rewriting
var u = currentUrl ,
absUrlPrefix = composeProtocolHostPort ( u . protocol ( ) , u . host ( ) , u . port ( ) ) + pathPrefix ;
$document . bind ( 'click' , function ( event ) {
// TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
// currently we open nice url link and redirect then
if ( uppercase ( event . target . nodeName ) != 'A' || event . ctrlKey || event . which == 2 ) return ;
var elm = jqLite ( event . target ) ,
href = elm . attr ( 'href' ) ;
if ( ! href || isDefined ( elm . attr ( 'ng:ext-link' ) ) || elm . attr ( 'target' ) ) return ;
// remove same domain from full url links (IE7 always returns full hrefs)
href = href . replace ( absUrlPrefix , '' ) ;
// link to different domain (or base path)
if ( href . substr ( 0 , 4 ) == 'http' ) return ;
// remove pathPrefix from absolute links
href = href . indexOf ( pathPrefix ) === 0 ? href . substr ( pathPrefix . length ) : href ;
currentUrl . url ( href ) ;
scope . $apply ( ) ;
event . preventDefault ( ) ;
2011-09-21 12:10:34 +00:00
// hack to work around FF6 bug 684208 when scenario runner clicks on links
window . angular [ 'ff-684208-preventDefault' ] = true ;
2011-06-29 16:30:39 +00:00
} ) ;
} else {
currentUrl = new LocationHashbangUrl ( initUrl , hashPrefix ) ;
2011-02-15 06:12:45 +00:00
}
2011-06-29 16:30:39 +00:00
// rewrite hashbang url <> html5 url
if ( currentUrl . absUrl ( ) != initUrl ) {
$browser . url ( currentUrl . absUrl ( ) , true ) ;
}
// update $location when $browser url changes
$browser . onUrlChange ( function ( newUrl ) {
if ( currentUrl . absUrl ( ) != newUrl ) {
currentUrl . $$parse ( newUrl ) ;
scope . $apply ( ) ;
}
} ) ;
// update browser
var changeCounter = 0 ;
scope . $watch ( function ( ) {
if ( $browser . url ( ) != currentUrl . absUrl ( ) ) {
changeCounter ++ ;
scope . $evalAsync ( function ( ) {
$browser . url ( currentUrl . absUrl ( ) , currentUrl . $$replace ) ;
currentUrl . $$replace = false ;
} ) ;
2011-02-15 06:12:45 +00:00
}
2011-06-29 16:30:39 +00:00
return changeCounter ;
} ) ;
return currentUrl ;
} , [ '$browser' , '$sniffer' , '$locationConfig' , '$document' ] ) ;
angular . service ( '$locationConfig' , function ( ) {
return {
html5Mode : false ,
hashPrefix : ''
} ;
} ) ;