2011-05-20 05:49:30 +02:00
/ * *
* History . js Core
* @ author Benjamin Arthur Lupton < contact @ balupton . com >
* @ copyright 2010 - 2011 Benjamin Arthur Lupton < contact @ balupton . com >
* @ license New BSD License < http : //creativecommons.org/licenses/BSD/>
* /
( function ( window , undefined ) {
"use strict" ;
2011-12-13 10:34:25 +01:00
// ========================================================================
2011-05-20 05:49:30 +02:00
// Initialise
// Localise Globals
var
console = window . console || undefined , // Prevent a JSLint complain
document = window . document , // Make sure we are using the correct document
navigator = window . navigator , // Make sure we are using the correct navigator
2011-12-13 10:34:25 +01:00
sessionStorage = window . sessionStorage || false , // sessionStorage
2011-05-20 05:49:30 +02:00
setTimeout = window . setTimeout ,
clearTimeout = window . clearTimeout ,
setInterval = window . setInterval ,
clearInterval = window . clearInterval ,
JSON = window . JSON ,
2011-12-13 10:34:25 +01:00
alert = window . alert ,
2011-05-20 05:49:30 +02:00
History = window . History = window . History || { } , // Public History Object
history = window . history ; // Old History Object
2013-03-20 00:33:21 +01:00
try {
sessionStorage . setItem ( 'TEST' , '1' ) ;
sessionStorage . removeItem ( 'TEST' ) ;
} catch ( e ) {
sessionStorage = false ;
}
2011-05-20 05:49:30 +02:00
// MooTools Compatibility
JSON . stringify = JSON . stringify || JSON . encode ;
JSON . parse = JSON . parse || JSON . decode ;
// Check Existence
if ( typeof History . init !== 'undefined' ) {
throw new Error ( 'History.js Core has already been loaded...' ) ;
}
// Initialise History
2013-03-20 00:33:21 +01:00
History . init = function ( options ) {
2011-05-20 05:49:30 +02:00
// Check Load Status of Adapter
if ( typeof History . Adapter === 'undefined' ) {
return false ;
}
// Check Load Status of Core
if ( typeof History . initCore !== 'undefined' ) {
History . initCore ( ) ;
}
// Check Load Status of HTML4 Support
if ( typeof History . initHtml4 !== 'undefined' ) {
History . initHtml4 ( ) ;
}
// Return true
return true ;
} ;
2011-12-13 10:34:25 +01:00
// ========================================================================
2011-05-20 05:49:30 +02:00
// Initialise Core
// Initialise Core
2013-03-20 00:33:21 +01:00
History . initCore = function ( options ) {
2011-05-20 05:49:30 +02:00
// Initialise
if ( typeof History . initCore . initialized !== 'undefined' ) {
// Already Loaded
return false ;
}
else {
History . initCore . initialized = true ;
}
2011-12-13 10:34:25 +01:00
// ====================================================================
2011-05-20 05:49:30 +02:00
// Options
/ * *
* History . options
* Configurable options
* /
History . options = History . options || { } ;
/ * *
* History . options . hashChangeInterval
* How long should the interval be before hashchange checks
* /
History . options . hashChangeInterval = History . options . hashChangeInterval || 100 ;
/ * *
* History . options . safariPollInterval
* How long should the interval be before safari poll checks
* /
History . options . safariPollInterval = History . options . safariPollInterval || 500 ;
/ * *
* History . options . doubleCheckInterval
* How long should the interval be before we perform a double check
* /
History . options . doubleCheckInterval = History . options . doubleCheckInterval || 500 ;
2013-03-20 00:33:21 +01:00
/ * *
* History . options . disableSuid
* Force History not to append suid
* /
History . options . disableSuid = History . options . disableSuid || false ;
2011-05-20 05:49:30 +02:00
/ * *
* History . options . storeInterval
* How long should we wait between store calls
* /
History . options . storeInterval = History . options . storeInterval || 1000 ;
/ * *
* History . options . busyDelay
* How long should we wait between busy events
* /
History . options . busyDelay = History . options . busyDelay || 250 ;
/ * *
* History . options . debug
* If true will enable debug messages to be logged
* /
History . options . debug = History . options . debug || false ;
/ * *
* History . options . initialTitle
* What is the title of the initial state
* /
History . options . initialTitle = History . options . initialTitle || document . title ;
2013-03-20 00:33:21 +01:00
/ * *
* History . options . html4Mode
* If true , will force HTMl4 mode ( hashtags )
* /
History . options . html4Mode = History . options . html4Mode || false ;
/ * *
* History . options . delayInit
* Want to override default options and call init manually .
* /
History . options . delayInit = History . options . delayInit || false ;
2011-05-20 05:49:30 +02:00
2011-12-13 10:34:25 +01:00
// ====================================================================
2011-05-20 05:49:30 +02:00
// Interval record
/ * *
* History . intervalList
* List of intervals set , to be cleared when document is unloaded .
* /
History . intervalList = [ ] ;
/ * *
* History . clearAllIntervals
* Clears all setInterval instances .
* /
History . clearAllIntervals = function ( ) {
var i , il = History . intervalList ;
if ( typeof il !== "undefined" && il !== null ) {
for ( i = 0 ; i < il . length ; i ++ ) {
clearInterval ( il [ i ] ) ;
}
History . intervalList = null ;
}
} ;
2011-12-13 10:34:25 +01:00
// ====================================================================
2011-05-20 05:49:30 +02:00
// Debug
/ * *
* History . debug ( message , ... )
* Logs the passed arguments if debug enabled
* /
History . debug = function ( ) {
if ( ( History . options . debug || false ) ) {
History . log . apply ( History , arguments ) ;
}
} ;
/ * *
* History . log ( message , ... )
* Logs the passed arguments
* /
History . log = function ( ) {
// Prepare
var
consoleExists = ! ( typeof console === 'undefined' || typeof console . log === 'undefined' || typeof console . log . apply === 'undefined' ) ,
textarea = document . getElementById ( 'log' ) ,
message ,
2011-12-13 10:34:25 +01:00
i , n ,
args , arg
2011-05-20 05:49:30 +02:00
;
// Write to Console
if ( consoleExists ) {
2011-12-13 10:34:25 +01:00
args = Array . prototype . slice . call ( arguments ) ;
2011-05-20 05:49:30 +02:00
message = args . shift ( ) ;
if ( typeof console . debug !== 'undefined' ) {
console . debug . apply ( console , [ message , args ] ) ;
}
else {
console . log . apply ( console , [ message , args ] ) ;
}
}
else {
message = ( "\n" + arguments [ 0 ] + "\n" ) ;
}
// Write to log
for ( i = 1 , n = arguments . length ; i < n ; ++ i ) {
2011-12-13 10:34:25 +01:00
arg = arguments [ i ] ;
2011-05-20 05:49:30 +02:00
if ( typeof arg === 'object' && typeof JSON !== 'undefined' ) {
try {
arg = JSON . stringify ( arg ) ;
}
catch ( Exception ) {
// Recursive Object
}
}
message += "\n" + arg + "\n" ;
}
// Textarea
if ( textarea ) {
textarea . value += message + "\n-----\n" ;
textarea . scrollTop = textarea . scrollHeight - textarea . clientHeight ;
}
// No Textarea, No Console
else if ( ! consoleExists ) {
alert ( message ) ;
}
// Return true
return true ;
} ;
2011-12-13 10:34:25 +01:00
// ====================================================================
2011-05-20 05:49:30 +02:00
// Emulated Status
/ * *
* History . getInternetExplorerMajorVersion ( )
* Get ' s the major version of Internet Explorer
* @ return { integer }
* @ license Public Domain
* @ author Benjamin Arthur Lupton < contact @ balupton . com >
* @ author James Padolsey < https : //gist.github.com/527683>
* /
History . getInternetExplorerMajorVersion = function ( ) {
var result = History . getInternetExplorerMajorVersion . cached =
( typeof History . getInternetExplorerMajorVersion . cached !== 'undefined' )
? History . getInternetExplorerMajorVersion . cached
: ( function ( ) {
var v = 3 ,
div = document . createElement ( 'div' ) ,
all = div . getElementsByTagName ( 'i' ) ;
while ( ( div . innerHTML = '<!--[if gt IE ' + ( ++ v ) + ']><i></i><![endif]-->' ) && all [ 0 ] ) { }
return ( v > 4 ) ? v : false ;
} ) ( )
;
return result ;
} ;
/ * *
* History . isInternetExplorer ( )
* Are we using Internet Explorer ?
* @ return { boolean }
* @ license Public Domain
* @ author Benjamin Arthur Lupton < contact @ balupton . com >
* /
History . isInternetExplorer = function ( ) {
var result =
History . isInternetExplorer . cached =
( typeof History . isInternetExplorer . cached !== 'undefined' )
? History . isInternetExplorer . cached
: Boolean ( History . getInternetExplorerMajorVersion ( ) )
;
return result ;
} ;
/ * *
* History . emulated
* Which features require emulating ?
* /
2013-03-20 00:33:21 +01:00
if ( History . options . html4Mode ) {
History . emulated = {
pushState : true ,
hashChange : true
} ;
}
else {
History . emulated = {
pushState : ! Boolean (
window . history && window . history . pushState && window . history . replaceState
&& ! (
( / Mobile\/([1-7][a-z]|(8([abcde]|f(1[0-8]))))/i ) . test ( navigator . userAgent ) /* disable for versions of iOS before version 4.3 (8F190) */
|| ( /AppleWebKit\/5([0-2]|3[0-2])/i ) . test ( navigator . userAgent ) /* disable for the mercury iOS browser, or at least older versions of the webkit engine */
)
) ,
hashChange : Boolean (
! ( ( 'onhashchange' in window ) || ( 'onhashchange' in document ) )
||
( History . isInternetExplorer ( ) && History . getInternetExplorerMajorVersion ( ) < 8 )
2011-05-20 05:49:30 +02:00
)
2013-03-20 00:33:21 +01:00
} ;
}
2011-05-20 05:49:30 +02:00
/ * *
* History . enabled
* Is History enabled ?
* /
History . enabled = ! History . emulated . pushState ;
/ * *
* History . bugs
* Which bugs are present
* /
History . bugs = {
/ * *
* Safari 5 and Safari iOS 4 fail to return to the correct state once a hash is replaced by a ` replaceState ` call
* https : //bugs.webkit.org/show_bug.cgi?id=56249
* /
setHash : Boolean ( ! History . emulated . pushState && navigator . vendor === 'Apple Computer, Inc.' && /AppleWebKit\/5([0-2]|3[0-3])/ . test ( navigator . userAgent ) ) ,
/ * *
* Safari 5 and Safari iOS 4 sometimes fail to apply the state change under busy conditions
* https : //bugs.webkit.org/show_bug.cgi?id=42940
* /
safariPoll : Boolean ( ! History . emulated . pushState && navigator . vendor === 'Apple Computer, Inc.' && /AppleWebKit\/5([0-2]|3[0-3])/ . test ( navigator . userAgent ) ) ,
/ * *
* MSIE 6 and 7 sometimes do not apply a hash even it was told to ( requiring a second call to the apply function )
* /
ieDoubleCheck : Boolean ( History . isInternetExplorer ( ) && History . getInternetExplorerMajorVersion ( ) < 8 ) ,
/ * *
* MSIE 6 requires the entire hash to be encoded for the hashes to trigger the onHashChange event
* /
hashEscape : Boolean ( History . isInternetExplorer ( ) && History . getInternetExplorerMajorVersion ( ) < 7 )
} ;
/ * *
* History . isEmptyObject ( obj )
* Checks to see if the Object is Empty
* @ param { Object } obj
* @ return { boolean }
* /
History . isEmptyObject = function ( obj ) {
for ( var name in obj ) {
2013-03-20 00:33:21 +01:00
if ( obj . hasOwnProperty ( name ) ) {
return false ;
}
2011-05-20 05:49:30 +02:00
}
return true ;
} ;
/ * *
* History . cloneObject ( obj )
2011-12-13 10:34:25 +01:00
* Clones a object and eliminate all references to the original contexts
2011-05-20 05:49:30 +02:00
* @ param { Object } obj
* @ return { Object }
* /
History . cloneObject = function ( obj ) {
var hash , newObj ;
if ( obj ) {
hash = JSON . stringify ( obj ) ;
newObj = JSON . parse ( hash ) ;
}
else {
newObj = { } ;
}
return newObj ;
} ;
2011-12-13 10:34:25 +01:00
// ====================================================================
2011-05-20 05:49:30 +02:00
// URL Helpers
/ * *
* History . getRootUrl ( )
* Turns "http://mysite.com/dir/page.html?asd" into "http://mysite.com"
* @ return { String } rootUrl
* /
History . getRootUrl = function ( ) {
// Create
var rootUrl = document . location . protocol + '//' + ( document . location . hostname || document . location . host ) ;
if ( document . location . port || false ) {
rootUrl += ':' + document . location . port ;
}
rootUrl += '/' ;
// Return
return rootUrl ;
} ;
/ * *
* History . getBaseHref ( )
* Fetches the ` href ` attribute of the ` <base href="..."> ` element if it exists
* @ return { String } baseHref
* /
History . getBaseHref = function ( ) {
// Create
var
baseElements = document . getElementsByTagName ( 'base' ) ,
baseElement = null ,
baseHref = '' ;
// Test for Base Element
if ( baseElements . length === 1 ) {
// Prepare for Base Element
baseElement = baseElements [ 0 ] ;
baseHref = baseElement . href . replace ( /[^\/]+$/ , '' ) ;
}
// Adjust trailing slash
baseHref = baseHref . replace ( /\/+$/ , '' ) ;
if ( baseHref ) baseHref += '/' ;
// Return
return baseHref ;
} ;
/ * *
* History . getBaseUrl ( )
* Fetches the baseHref or basePageUrl or rootUrl ( whichever one exists first )
* @ return { String } baseUrl
* /
History . getBaseUrl = function ( ) {
// Create
var baseUrl = History . getBaseHref ( ) || History . getBasePageUrl ( ) || History . getRootUrl ( ) ;
// Return
return baseUrl ;
} ;
/ * *
* History . getPageUrl ( )
* Fetches the URL of the current page
* @ return { String } pageUrl
* /
History . getPageUrl = function ( ) {
// Fetch
var
State = History . getState ( false , false ) ,
2013-01-15 13:27:11 +01:00
stateUrl = ( State || { } ) . url || History . getLocationHref ( ) ,
2011-12-13 10:34:25 +01:00
pageUrl ;
2011-05-20 05:49:30 +02:00
// Create
2011-12-13 10:34:25 +01:00
pageUrl = stateUrl . replace ( /\/+$/ , '' ) . replace ( /[^\/]+$/ , function ( part , index , string ) {
2011-05-20 05:49:30 +02:00
return ( /\./ ) . test ( part ) ? part : part + '/' ;
} ) ;
// Return
return pageUrl ;
} ;
/ * *
* History . getBasePageUrl ( )
* Fetches the Url of the directory of the current page
* @ return { String } basePageUrl
* /
History . getBasePageUrl = function ( ) {
// Create
2013-01-15 13:27:11 +01:00
var basePageUrl = ( History . getLocationHref ( ) ) . replace ( /[#\?].*/ , '' ) . replace ( /[^\/]+$/ , function ( part , index , string ) {
2011-05-20 05:49:30 +02:00
return ( /[^\/]$/ ) . test ( part ) ? '' : part ;
} ) . replace ( /\/+$/ , '' ) + '/' ;
// Return
return basePageUrl ;
} ;
/ * *
* History . getFullUrl ( url )
* Ensures that we have an absolute URL and not a relative URL
* @ param { string } url
* @ param { Boolean } allowBaseHref
* @ return { string } fullUrl
* /
History . getFullUrl = function ( url , allowBaseHref ) {
// Prepare
var fullUrl = url , firstChar = url . substring ( 0 , 1 ) ;
allowBaseHref = ( typeof allowBaseHref === 'undefined' ) ? true : allowBaseHref ;
// Check
if ( /[a-z]+\:\/\// . test ( url ) ) {
// Full URL
}
else if ( firstChar === '/' ) {
// Root URL
fullUrl = History . getRootUrl ( ) + url . replace ( /^\/+/ , '' ) ;
}
else if ( firstChar === '#' ) {
// Anchor URL
fullUrl = History . getPageUrl ( ) . replace ( /#.*/ , '' ) + url ;
}
else if ( firstChar === '?' ) {
// Query URL
fullUrl = History . getPageUrl ( ) . replace ( /[\?#].*/ , '' ) + url ;
}
else {
// Relative URL
if ( allowBaseHref ) {
fullUrl = History . getBaseUrl ( ) + url . replace ( /^(\.\/)+/ , '' ) ;
} else {
fullUrl = History . getBasePageUrl ( ) + url . replace ( /^(\.\/)+/ , '' ) ;
}
// We have an if condition above as we do not want hashes
// which are relative to the baseHref in our URLs
// as if the baseHref changes, then all our bookmarks
// would now point to different locations
// whereas the basePageUrl will always stay the same
}
// Return
return fullUrl . replace ( /\#$/ , '' ) ;
} ;
/ * *
* History . getShortUrl ( url )
* Ensures that we have a relative URL and not a absolute URL
* @ param { string } url
* @ return { string } url
* /
History . getShortUrl = function ( url ) {
// Prepare
var shortUrl = url , baseUrl = History . getBaseUrl ( ) , rootUrl = History . getRootUrl ( ) ;
// Trim baseUrl
if ( History . emulated . pushState ) {
// We are in a if statement as when pushState is not emulated
// The actual url these short urls are relative to can change
// So within the same session, we the url may end up somewhere different
shortUrl = shortUrl . replace ( baseUrl , '' ) ;
}
// Trim rootUrl
shortUrl = shortUrl . replace ( rootUrl , '/' ) ;
// Ensure we can still detect it as a state
if ( History . isTraditionalAnchor ( shortUrl ) ) {
shortUrl = './' + shortUrl ;
}
// Clean It
shortUrl = shortUrl . replace ( /^(\.\/)+/g , './' ) . replace ( /\#$/ , '' ) ;
// Return
return shortUrl ;
} ;
2013-01-15 13:27:11 +01:00
/ * *
* History . getLocationHref ( document )
* Returns a normalized version of document . location . href
* accounting for browser inconsistencies , etc .
2013-03-20 00:33:21 +01:00
*
2013-01-15 13:27:11 +01:00
* This URL will be URI - encoded and will include the hash
*
* @ param { object } document
* @ return { string } url
* /
History . getLocationHref = function ( doc ) {
doc = doc || document ;
// most of the time, this will be true
if ( doc . URL === doc . location . href )
return doc . location . href ;
// some versions of webkit URI-decode document.location.href
// but they leave document.URL in an encoded state
if ( doc . location . href === decodeURIComponent ( doc . URL ) )
return doc . URL ;
// FF 3.6 only updates document.URL when a page is reloaded
// document.location.href is updated correctly
if ( doc . location . hash && decodeURIComponent ( doc . location . href . replace ( /^[^#]+/ , "" ) ) === doc . location . hash )
return doc . location . href ;
2013-03-20 00:33:21 +01:00
if ( doc . URL . indexOf ( '#' ) == - 1 && doc . location . href . indexOf ( '#' ) != - 1 )
return doc . location . href ;
2013-01-15 13:27:11 +01:00
return doc . URL || doc . location . href ;
} ;
2011-12-13 10:34:25 +01:00
// ====================================================================
2011-05-20 05:49:30 +02:00
// State Storage
/ * *
* History . store
* The store for all session specific data
* /
2011-12-13 10:34:25 +01:00
History . store = { } ;
2011-05-20 05:49:30 +02:00
/ * *
* History . idToState
* 1 - 1 : State ID to State Object
* /
History . idToState = History . idToState || { } ;
/ * *
* History . stateToId
* 1 - 1 : State String to State ID
* /
History . stateToId = History . stateToId || { } ;
/ * *
* History . urlToId
* 1 - 1 : State URL to State ID
* /
History . urlToId = History . urlToId || { } ;
/ * *
* History . storedStates
* Store the states in an array
* /
History . storedStates = History . storedStates || [ ] ;
/ * *
* History . savedStates
* Saved the states in an array
* /
History . savedStates = History . savedStates || [ ] ;
2011-12-13 10:34:25 +01:00
/ * *
* History . noramlizeStore ( )
* Noramlize the store by adding necessary values
* /
History . normalizeStore = function ( ) {
History . store . idToState = History . store . idToState || { } ;
History . store . urlToId = History . store . urlToId || { } ;
History . store . stateToId = History . store . stateToId || { } ;
} ;
2011-05-20 05:49:30 +02:00
/ * *
* History . getState ( )
* Get an object containing the data , title and url of the current state
* @ param { Boolean } friendly
* @ param { Boolean } create
* @ return { Object } State
* /
History . getState = function ( friendly , create ) {
// Prepare
if ( typeof friendly === 'undefined' ) { friendly = true ; }
if ( typeof create === 'undefined' ) { create = true ; }
// Fetch
var State = History . getLastSavedState ( ) ;
// Create
if ( ! State && create ) {
State = History . createStateObject ( ) ;
}
// Adjust
if ( friendly ) {
State = History . cloneObject ( State ) ;
State . url = State . cleanUrl || State . url ;
}
// Return
return State ;
} ;
/ * *
* History . getIdByState ( State )
* Gets a ID for a State
* @ param { State } newState
* @ return { String } id
* /
History . getIdByState = function ( newState ) {
// Fetch ID
2011-12-13 10:34:25 +01:00
var id = History . extractId ( newState . url ) ,
str ;
2013-03-20 00:33:21 +01:00
2011-05-20 05:49:30 +02:00
if ( ! id ) {
// Find ID via State String
2011-12-13 10:34:25 +01:00
str = History . getStateString ( newState ) ;
2011-05-20 05:49:30 +02:00
if ( typeof History . stateToId [ str ] !== 'undefined' ) {
id = History . stateToId [ str ] ;
}
else if ( typeof History . store . stateToId [ str ] !== 'undefined' ) {
id = History . store . stateToId [ str ] ;
}
else {
// Generate a new ID
while ( true ) {
2011-12-13 10:34:25 +01:00
id = ( new Date ( ) ) . getTime ( ) + String ( Math . random ( ) ) . replace ( /\D/g , '' ) ;
2011-05-20 05:49:30 +02:00
if ( typeof History . idToState [ id ] === 'undefined' && typeof History . store . idToState [ id ] === 'undefined' ) {
break ;
}
}
// Apply the new State to the ID
History . stateToId [ str ] = id ;
History . idToState [ id ] = newState ;
}
}
// Return ID
return id ;
} ;
/ * *
* History . normalizeState ( State )
* Expands a State Object
* @ param { object } State
* @ return { object }
* /
History . normalizeState = function ( oldState ) {
2011-12-13 10:34:25 +01:00
// Variables
var newState , dataNotEmpty ;
2011-05-20 05:49:30 +02:00
// Prepare
if ( ! oldState || ( typeof oldState !== 'object' ) ) {
oldState = { } ;
}
// Check
if ( typeof oldState . normalized !== 'undefined' ) {
return oldState ;
}
// Adjust
if ( ! oldState . data || ( typeof oldState . data !== 'object' ) ) {
oldState . data = { } ;
}
2011-12-13 10:34:25 +01:00
// ----------------------------------------------------------------
2011-05-20 05:49:30 +02:00
// Create
2011-12-13 10:34:25 +01:00
newState = { } ;
2011-05-20 05:49:30 +02:00
newState . normalized = true ;
newState . title = oldState . title || '' ;
2013-01-15 13:27:11 +01:00
newState . url = History . getFullUrl ( oldState . url ? decodeURIComponent ( oldState . url ) : ( History . getLocationHref ( ) ) ) ;
2011-05-20 05:49:30 +02:00
newState . hash = History . getShortUrl ( newState . url ) ;
newState . data = History . cloneObject ( oldState . data ) ;
// Fetch ID
newState . id = History . getIdByState ( newState ) ;
2011-12-13 10:34:25 +01:00
// ----------------------------------------------------------------
2011-05-20 05:49:30 +02:00
// Clean the URL
newState . cleanUrl = newState . url . replace ( /\??\&_suid.*/ , '' ) ;
newState . url = newState . cleanUrl ;
// Check to see if we have more than just a url
2011-12-13 10:34:25 +01:00
dataNotEmpty = ! History . isEmptyObject ( newState . data ) ;
2011-05-20 05:49:30 +02:00
// Apply
2013-03-20 00:33:21 +01:00
if ( ( newState . title || dataNotEmpty ) && History . options . disableSuid !== true ) {
2011-05-20 05:49:30 +02:00
// Add ID to Hash
newState . hash = History . getShortUrl ( newState . url ) . replace ( /\??\&_suid.*/ , '' ) ;
if ( ! /\?/ . test ( newState . hash ) ) {
newState . hash += '?' ;
}
newState . hash += '&_suid=' + newState . id ;
}
// Create the Hashed URL
newState . hashedUrl = History . getFullUrl ( newState . hash ) ;
2011-12-13 10:34:25 +01:00
// ----------------------------------------------------------------
2011-05-20 05:49:30 +02:00
// Update the URL if we have a duplicate
if ( ( History . emulated . pushState || History . bugs . safariPoll ) && History . hasUrlDuplicate ( newState ) ) {
newState . url = newState . hashedUrl ;
}
2011-12-13 10:34:25 +01:00
// ----------------------------------------------------------------
2011-05-20 05:49:30 +02:00
// Return
return newState ;
} ;
/ * *
* History . createStateObject ( data , title , url )
* Creates a object based on the data , title and url state params
* @ param { object } data
* @ param { string } title
* @ param { string } url
* @ return { object }
* /
History . createStateObject = function ( data , title , url ) {
// Hashify
var State = {
'data' : data ,
'title' : title ,
2013-03-20 00:33:21 +01:00
'url' : url
2011-05-20 05:49:30 +02:00
} ;
// Expand the State
State = History . normalizeState ( State ) ;
// Return object
return State ;
} ;
/ * *
* History . getStateById ( id )
* Get a state by it ' s UID
* @ param { String } id
* /
History . getStateById = function ( id ) {
// Prepare
id = String ( id ) ;
// Retrieve
var State = History . idToState [ id ] || History . store . idToState [ id ] || undefined ;
// Return State
return State ;
} ;
/ * *
* Get a State ' s String
* @ param { State } passedState
* /
History . getStateString = function ( passedState ) {
// Prepare
2011-12-13 10:34:25 +01:00
var State , cleanedState , str ;
// Fetch
State = History . normalizeState ( passedState ) ;
2011-05-20 05:49:30 +02:00
// Clean
2011-12-13 10:34:25 +01:00
cleanedState = {
2011-05-20 05:49:30 +02:00
data : State . data ,
title : passedState . title ,
url : passedState . url
} ;
// Fetch
2011-12-13 10:34:25 +01:00
str = JSON . stringify ( cleanedState ) ;
2011-05-20 05:49:30 +02:00
// Return
return str ;
} ;
/ * *
* Get a State ' s ID
* @ param { State } passedState
* @ return { String } id
* /
History . getStateId = function ( passedState ) {
// Prepare
2011-12-13 10:34:25 +01:00
var State , id ;
2013-03-20 00:33:21 +01:00
2011-12-13 10:34:25 +01:00
// Fetch
State = History . normalizeState ( passedState ) ;
2011-05-20 05:49:30 +02:00
// Fetch
2011-12-13 10:34:25 +01:00
id = State . id ;
2011-05-20 05:49:30 +02:00
// Return
return id ;
} ;
/ * *
* History . getHashByState ( State )
* Creates a Hash for the State Object
* @ param { State } passedState
* @ return { String } hash
* /
History . getHashByState = function ( passedState ) {
// Prepare
2011-12-13 10:34:25 +01:00
var State , hash ;
2013-03-20 00:33:21 +01:00
2011-05-20 05:49:30 +02:00
// Fetch
2011-12-13 10:34:25 +01:00
State = History . normalizeState ( passedState ) ;
// Hash
2011-05-20 05:49:30 +02:00
hash = State . hash ;
// Return
return hash ;
} ;
/ * *
* History . extractId ( url _or _hash )
* Get a State ID by it ' s URL or Hash
* @ param { string } url _or _hash
* @ return { string } id
* /
History . extractId = function ( url _or _hash ) {
// Prepare
2013-03-20 00:33:21 +01:00
var id , parts , url , tmp ;
2011-05-20 05:49:30 +02:00
// Extract
2013-03-20 00:33:21 +01:00
// If the URL has a #, use the id from before the #
if ( url _or _hash . indexOf ( '#' ) != - 1 )
{
tmp = url _or _hash . split ( "#" ) [ 0 ] ;
}
else
{
tmp = url _or _hash ;
}
parts = /(.*)\&_suid=([0-9]+)$/ . exec ( tmp ) ;
2011-05-20 05:49:30 +02:00
url = parts ? ( parts [ 1 ] || url _or _hash ) : url _or _hash ;
id = parts ? String ( parts [ 2 ] || '' ) : '' ;
// Return
return id || false ;
} ;
/ * *
* History . isTraditionalAnchor
* Checks to see if the url is a traditional anchor or not
* @ param { String } url _or _hash
* @ return { Boolean }
* /
History . isTraditionalAnchor = function ( url _or _hash ) {
// Check
var isTraditional = ! ( /[\/\?\.]/ . test ( url _or _hash ) ) ;
// Return
return isTraditional ;
} ;
/ * *
* History . extractState
* Get a State by it ' s URL or Hash
* @ param { String } url _or _hash
* @ return { State | null }
* /
History . extractState = function ( url _or _hash , create ) {
// Prepare
2011-12-13 10:34:25 +01:00
var State = null , id , url ;
2011-05-20 05:49:30 +02:00
create = create || false ;
// Fetch SUID
2011-12-13 10:34:25 +01:00
id = History . extractId ( url _or _hash ) ;
2011-05-20 05:49:30 +02:00
if ( id ) {
State = History . getStateById ( id ) ;
}
// Fetch SUID returned no State
if ( ! State ) {
// Fetch URL
2011-12-13 10:34:25 +01:00
url = History . getFullUrl ( url _or _hash ) ;
2011-05-20 05:49:30 +02:00
// Check URL
id = History . getIdByUrl ( url ) || false ;
if ( id ) {
State = History . getStateById ( id ) ;
}
// Create State
if ( ! State && create && ! History . isTraditionalAnchor ( url _or _hash ) ) {
State = History . createStateObject ( null , null , url ) ;
}
}
// Return
return State ;
} ;
/ * *
* History . getIdByUrl ( )
* Get a State ID by a State URL
* /
History . getIdByUrl = function ( url ) {
// Fetch
var id = History . urlToId [ url ] || History . store . urlToId [ url ] || undefined ;
// Return
return id ;
} ;
/ * *
* History . getLastSavedState ( )
* Get an object containing the data , title and url of the current state
* @ return { Object } State
* /
History . getLastSavedState = function ( ) {
return History . savedStates [ History . savedStates . length - 1 ] || undefined ;
} ;
/ * *
* History . getLastStoredState ( )
* Get an object containing the data , title and url of the current state
* @ return { Object } State
* /
History . getLastStoredState = function ( ) {
return History . storedStates [ History . storedStates . length - 1 ] || undefined ;
} ;
/ * *
* History . hasUrlDuplicate
* Checks if a Url will have a url conflict
* @ param { Object } newState
* @ return { Boolean } hasDuplicate
* /
History . hasUrlDuplicate = function ( newState ) {
// Prepare
2011-12-13 10:34:25 +01:00
var hasDuplicate = false ,
oldState ;
2011-05-20 05:49:30 +02:00
// Fetch
2011-12-13 10:34:25 +01:00
oldState = History . extractState ( newState . url ) ;
2011-05-20 05:49:30 +02:00
// Check
hasDuplicate = oldState && oldState . id !== newState . id ;
// Return
return hasDuplicate ;
} ;
/ * *
* History . storeState
* Store a State
* @ param { Object } newState
* @ return { Object } newState
* /
History . storeState = function ( newState ) {
// Store the State
History . urlToId [ newState . url ] = newState . id ;
// Push the State
History . storedStates . push ( History . cloneObject ( newState ) ) ;
// Return newState
return newState ;
} ;
/ * *
* History . isLastSavedState ( newState )
* Tests to see if the state is the last state
* @ param { Object } newState
* @ return { boolean } isLast
* /
History . isLastSavedState = function ( newState ) {
// Prepare
2011-12-13 10:34:25 +01:00
var isLast = false ,
newId , oldState , oldId ;
2011-05-20 05:49:30 +02:00
// Check
if ( History . savedStates . length ) {
2011-12-13 10:34:25 +01:00
newId = newState . id ;
oldState = History . getLastSavedState ( ) ;
oldId = oldState . id ;
2011-05-20 05:49:30 +02:00
// Check
isLast = ( newId === oldId ) ;
}
// Return
return isLast ;
} ;
/ * *
* History . saveState
* Push a State
* @ param { Object } newState
* @ return { boolean } changed
* /
History . saveState = function ( newState ) {
// Check Hash
if ( History . isLastSavedState ( newState ) ) {
return false ;
}
// Push the State
History . savedStates . push ( History . cloneObject ( newState ) ) ;
// Return true
return true ;
} ;
/ * *
* History . getStateByIndex ( )
* Gets a state by the index
* @ param { integer } index
* @ return { Object }
* /
History . getStateByIndex = function ( index ) {
// Prepare
var State = null ;
// Handle
if ( typeof index === 'undefined' ) {
// Get the last inserted
State = History . savedStates [ History . savedStates . length - 1 ] ;
}
else if ( index < 0 ) {
// Get from the end
State = History . savedStates [ History . savedStates . length + index ] ;
}
else {
// Get from the beginning
State = History . savedStates [ index ] ;
}
// Return State
return State ;
} ;
2013-03-20 00:33:21 +01:00
/ * *
* History . getCurrentIndex ( )
* Gets the current index
* @ return ( integer )
* /
History . getCurrentIndex = function ( ) {
// Prepare
var index = null ;
// No states saved
if ( History . savedStates . length < 1 ) {
index = 0 ;
}
else {
index = History . savedStates . length - 1 ;
}
return index ;
} ;
2011-12-13 10:34:25 +01:00
// ====================================================================
2011-05-20 05:49:30 +02:00
// Hash Helpers
/ * *
* History . getHash ( )
2013-01-15 13:27:11 +01:00
* @ param { Location = } location
2011-05-20 05:49:30 +02:00
* Gets the current document hash
2013-01-15 13:27:11 +01:00
* Note : unlike location . hash , this is guaranteed to return the escaped hash in all browsers
2011-05-20 05:49:30 +02:00
* @ return { string }
* /
2013-03-20 00:33:21 +01:00
History . getHash = function ( doc ) {
var url = History . getLocationHref ( doc ) ,
hash ;
hash = History . getHashByUrl ( url ) ;
return hash ;
2011-05-20 05:49:30 +02:00
} ;
/ * *
* History . unescapeHash ( )
* normalize and Unescape a Hash
* @ param { String } hash
* @ return { string }
* /
History . unescapeHash = function ( hash ) {
// Prepare
var result = History . normalizeHash ( hash ) ;
// Unescape hash
2013-01-15 13:27:11 +01:00
result = decodeURIComponent ( result ) ;
2011-05-20 05:49:30 +02:00
// Return result
return result ;
} ;
/ * *
* History . normalizeHash ( )
* normalize a hash across browsers
* @ return { string }
* /
History . normalizeHash = function ( hash ) {
2011-12-13 10:34:25 +01:00
// Prepare
2011-05-20 05:49:30 +02:00
var result = hash . replace ( /[^#]*#/ , '' ) . replace ( /#.*/ , '' ) ;
// Return result
return result ;
} ;
/ * *
* History . setHash ( hash )
* Sets the document hash
* @ param { string } hash
* @ return { History }
* /
History . setHash = function ( hash , queue ) {
2011-12-13 10:34:25 +01:00
// Prepare
2013-01-15 13:27:11 +01:00
var State , pageUrl ;
2011-12-13 10:34:25 +01:00
2011-05-20 05:49:30 +02:00
// Handle Queueing
if ( queue !== false && History . busy ( ) ) {
// Wait + Push to Queue
//History.debug('History.setHash: we must wait', arguments);
History . pushQueue ( {
scope : History ,
callback : History . setHash ,
args : arguments ,
queue : queue
} ) ;
return false ;
}
// Log
//History.debug('History.setHash: called',hash);
// Make Busy + Continue
History . busy ( true ) ;
// Check if hash is a state
2011-12-13 10:34:25 +01:00
State = History . extractState ( hash , true ) ;
2011-05-20 05:49:30 +02:00
if ( State && ! History . emulated . pushState ) {
// Hash is a state so skip the setHash
//History.debug('History.setHash: Hash is a state so skipping the hash set with a direct pushState call',arguments);
// PushState
History . pushState ( State . data , State . title , State . url , false ) ;
}
2013-01-15 13:27:11 +01:00
else if ( History . getHash ( ) !== hash ) {
2011-05-20 05:49:30 +02:00
// Hash is a proper hash, so apply it
// Handle browser bugs
if ( History . bugs . setHash ) {
// Fix Safari Bug https://bugs.webkit.org/show_bug.cgi?id=56249
// Fetch the base page
2011-12-13 10:34:25 +01:00
pageUrl = History . getPageUrl ( ) ;
2011-05-20 05:49:30 +02:00
// Safari hash apply
2013-01-15 13:27:11 +01:00
History . pushState ( null , null , pageUrl + '#' + hash , false ) ;
2011-05-20 05:49:30 +02:00
}
else {
// Normal hash apply
2013-01-15 13:27:11 +01:00
document . location . hash = hash ;
2011-05-20 05:49:30 +02:00
}
}
// Chain
return History ;
} ;
/ * *
* History . escape ( )
* normalize and Escape a Hash
* @ return { string }
* /
History . escapeHash = function ( hash ) {
2011-12-13 10:34:25 +01:00
// Prepare
2011-05-20 05:49:30 +02:00
var result = History . normalizeHash ( hash ) ;
// Escape hash
2013-01-15 13:27:11 +01:00
result = window . encodeURIComponent ( result ) ;
2011-05-20 05:49:30 +02:00
// IE6 Escape Bug
if ( ! History . bugs . hashEscape ) {
// Restore common parts
result = result
. replace ( /\%21/g , '!' )
. replace ( /\%26/g , '&' )
. replace ( /\%3D/g , '=' )
. replace ( /\%3F/g , '?' ) ;
}
// Return result
return result ;
} ;
/ * *
* History . getHashByUrl ( url )
* Extracts the Hash from a URL
* @ param { string } url
* @ return { string } url
* /
History . getHashByUrl = function ( url ) {
// Extract the hash
var hash = String ( url )
. replace ( /([^#]*)#?([^#]*)#?(.*)/ , '$2' )
;
// Unescape hash
hash = History . unescapeHash ( hash ) ;
// Return hash
return hash ;
} ;
/ * *
* History . setTitle ( title )
* Applies the title to the document
* @ param { State } newState
* @ return { Boolean }
* /
History . setTitle = function ( newState ) {
// Prepare
2011-12-13 10:34:25 +01:00
var title = newState . title ,
firstState ;
2011-05-20 05:49:30 +02:00
// Initial
if ( ! title ) {
2011-12-13 10:34:25 +01:00
firstState = History . getStateByIndex ( 0 ) ;
2011-05-20 05:49:30 +02:00
if ( firstState && firstState . url === newState . url ) {
title = firstState . title || History . options . initialTitle ;
}
}
// Apply
try {
document . getElementsByTagName ( 'title' ) [ 0 ] . innerHTML = title . replace ( '<' , '<' ) . replace ( '>' , '>' ) . replace ( ' & ' , ' & ' ) ;
}
catch ( Exception ) { }
document . title = title ;
// Chain
return History ;
} ;
2011-12-13 10:34:25 +01:00
// ====================================================================
2011-05-20 05:49:30 +02:00
// Queueing
/ * *
* History . queues
* The list of queues to use
* First In , First Out
* /
History . queues = [ ] ;
/ * *
* History . busy ( value )
* @ param { boolean } value [ optional ]
* @ return { boolean } busy
* /
History . busy = function ( value ) {
// Apply
if ( typeof value !== 'undefined' ) {
//History.debug('History.busy: changing ['+(History.busy.flag||false)+'] to ['+(value||false)+']', History.queues.length);
History . busy . flag = value ;
}
// Default
else if ( typeof History . busy . flag === 'undefined' ) {
History . busy . flag = false ;
}
// Queue
if ( ! History . busy . flag ) {
// Execute the next item in the queue
clearTimeout ( History . busy . timeout ) ;
var fireNext = function ( ) {
2011-12-13 10:34:25 +01:00
var i , queue , item ;
2011-05-20 05:49:30 +02:00
if ( History . busy . flag ) return ;
2011-12-13 10:34:25 +01:00
for ( i = History . queues . length - 1 ; i >= 0 ; -- i ) {
queue = History . queues [ i ] ;
2011-05-20 05:49:30 +02:00
if ( queue . length === 0 ) continue ;
2011-12-13 10:34:25 +01:00
item = queue . shift ( ) ;
2011-05-20 05:49:30 +02:00
History . fireQueueItem ( item ) ;
History . busy . timeout = setTimeout ( fireNext , History . options . busyDelay ) ;
}
} ;
History . busy . timeout = setTimeout ( fireNext , History . options . busyDelay ) ;
}
// Return
return History . busy . flag ;
} ;
2011-12-13 10:34:25 +01:00
/ * *
* History . busy . flag
* /
History . busy . flag = false ;
2011-05-20 05:49:30 +02:00
/ * *
* History . fireQueueItem ( item )
* Fire a Queue Item
* @ param { Object } item
* @ return { Mixed } result
* /
History . fireQueueItem = function ( item ) {
return item . callback . apply ( item . scope || History , item . args || [ ] ) ;
} ;
/ * *
* History . pushQueue ( callback , args )
* Add an item to the queue
* @ param { Object } item [ scope , callback , args , queue ]
* /
History . pushQueue = function ( item ) {
// Prepare the queue
History . queues [ item . queue || 0 ] = History . queues [ item . queue || 0 ] || [ ] ;
// Add to the queue
History . queues [ item . queue || 0 ] . push ( item ) ;
// Chain
return History ;
} ;
/ * *
* History . queue ( item , queue ) , ( func , queue ) , ( func ) , ( item )
* Either firs the item now if not busy , or adds it to the queue
* /
History . queue = function ( item , queue ) {
// Prepare
if ( typeof item === 'function' ) {
item = {
callback : item
} ;
}
if ( typeof queue !== 'undefined' ) {
item . queue = queue ;
}
// Handle
if ( History . busy ( ) ) {
History . pushQueue ( item ) ;
} else {
History . fireQueueItem ( item ) ;
}
// Chain
return History ;
} ;
/ * *
* History . clearQueue ( )
* Clears the Queue
* /
History . clearQueue = function ( ) {
History . busy . flag = false ;
History . queues = [ ] ;
return History ;
} ;
2011-12-13 10:34:25 +01:00
// ====================================================================
2011-05-20 05:49:30 +02:00
// IE Bug Fix
/ * *
* History . stateChanged
* States whether or not the state has changed since the last double check was initialised
* /
History . stateChanged = false ;
/ * *
* History . doubleChecker
* Contains the timeout used for the double checks
* /
History . doubleChecker = false ;
/ * *
* History . doubleCheckComplete ( )
* Complete a double check
* @ return { History }
* /
History . doubleCheckComplete = function ( ) {
// Update
History . stateChanged = true ;
// Clear
History . doubleCheckClear ( ) ;
// Chain
return History ;
} ;
/ * *
* History . doubleCheckClear ( )
* Clear a double check
* @ return { History }
* /
History . doubleCheckClear = function ( ) {
// Clear
if ( History . doubleChecker ) {
clearTimeout ( History . doubleChecker ) ;
History . doubleChecker = false ;
}
// Chain
return History ;
} ;
/ * *
* History . doubleCheck ( )
* Create a double check
* @ return { History }
* /
History . doubleCheck = function ( tryAgain ) {
// Reset
History . stateChanged = false ;
History . doubleCheckClear ( ) ;
// Fix IE6,IE7 bug where calling history.back or history.forward does not actually change the hash (whereas doing it manually does)
// Fix Safari 5 bug where sometimes the state does not change: https://bugs.webkit.org/show_bug.cgi?id=42940
if ( History . bugs . ieDoubleCheck ) {
// Apply Check
History . doubleChecker = setTimeout (
function ( ) {
History . doubleCheckClear ( ) ;
if ( ! History . stateChanged ) {
//History.debug('History.doubleCheck: State has not yet changed, trying again', arguments);
// Re-Attempt
tryAgain ( ) ;
}
return true ;
} ,
History . options . doubleCheckInterval
) ;
}
// Chain
return History ;
} ;
2011-12-13 10:34:25 +01:00
// ====================================================================
2011-05-20 05:49:30 +02:00
// Safari Bug Fix
/ * *
* History . safariStatePoll ( )
* Poll the current state
* @ return { History }
* /
History . safariStatePoll = function ( ) {
// Poll the URL
// Get the Last State which has the new URL
var
2013-01-15 13:27:11 +01:00
urlState = History . extractState ( History . getLocationHref ( ) ) ,
2011-05-20 05:49:30 +02:00
newState ;
// Check for a difference
if ( ! History . isLastSavedState ( urlState ) ) {
newState = urlState ;
}
else {
return ;
}
// Check if we have a state with that url
// If not create it
if ( ! newState ) {
//History.debug('History.safariStatePoll: new');
newState = History . createStateObject ( ) ;
}
// Apply the New State
//History.debug('History.safariStatePoll: trigger');
History . Adapter . trigger ( window , 'popstate' ) ;
// Chain
return History ;
} ;
2011-12-13 10:34:25 +01:00
// ====================================================================
2011-05-20 05:49:30 +02:00
// State Aliases
/ * *
* History . back ( queue )
* Send the browser history back one item
* @ param { Integer } queue [ optional ]
* /
History . back = function ( queue ) {
//History.debug('History.back: called', arguments);
// Handle Queueing
if ( queue !== false && History . busy ( ) ) {
// Wait + Push to Queue
//History.debug('History.back: we must wait', arguments);
History . pushQueue ( {
scope : History ,
callback : History . back ,
args : arguments ,
queue : queue
} ) ;
return false ;
}
// Make Busy + Continue
History . busy ( true ) ;
// Fix certain browser bugs that prevent the state from changing
History . doubleCheck ( function ( ) {
History . back ( false ) ;
} ) ;
// Go back
history . go ( - 1 ) ;
// End back closure
return true ;
} ;
/ * *
* History . forward ( queue )
* Send the browser history forward one item
* @ param { Integer } queue [ optional ]
* /
History . forward = function ( queue ) {
//History.debug('History.forward: called', arguments);
// Handle Queueing
if ( queue !== false && History . busy ( ) ) {
// Wait + Push to Queue
//History.debug('History.forward: we must wait', arguments);
History . pushQueue ( {
scope : History ,
callback : History . forward ,
args : arguments ,
queue : queue
} ) ;
return false ;
}
// Make Busy + Continue
History . busy ( true ) ;
// Fix certain browser bugs that prevent the state from changing
History . doubleCheck ( function ( ) {
History . forward ( false ) ;
} ) ;
// Go forward
history . go ( 1 ) ;
// End forward closure
return true ;
} ;
/ * *
* History . go ( index , queue )
* Send the browser history back or forward index times
* @ param { Integer } queue [ optional ]
* /
History . go = function ( index , queue ) {
//History.debug('History.go: called', arguments);
// Prepare
var i ;
// Handle
if ( index > 0 ) {
// Forward
for ( i = 1 ; i <= index ; ++ i ) {
History . forward ( queue ) ;
}
}
else if ( index < 0 ) {
// Backward
for ( i = - 1 ; i >= index ; -- i ) {
History . back ( queue ) ;
}
}
else {
throw new Error ( 'History.go: History.go requires a positive or negative integer passed.' ) ;
}
// Chain
return History ;
} ;
2011-12-13 10:34:25 +01:00
// ====================================================================
2011-05-20 05:49:30 +02:00
// HTML5 State Support
2011-12-13 10:34:25 +01:00
// Non-Native pushState Implementation
2011-05-20 05:49:30 +02:00
if ( History . emulated . pushState ) {
/ *
* Provide Skeleton for HTML4 Browsers
* /
// Prepare
var emptyFunction = function ( ) { } ;
History . pushState = History . pushState || emptyFunction ;
History . replaceState = History . replaceState || emptyFunction ;
2011-12-13 10:34:25 +01:00
} // History.emulated.pushState
// Native pushState Implementation
2011-05-20 05:49:30 +02:00
else {
/ *
* Use native HTML5 History API Implementation
* /
/ * *
* History . onPopState ( event , extra )
* Refresh the Current State
* /
2011-12-13 10:34:25 +01:00
History . onPopState = function ( event , extra ) {
// Prepare
var stateId = false , newState = false , currentHash , currentState ;
2011-05-20 05:49:30 +02:00
// Reset the double check
History . doubleCheckComplete ( ) ;
// Check for a Hash, and handle apporiatly
2013-03-20 00:33:21 +01:00
currentHash = History . getHash ( ) ;
2011-05-20 05:49:30 +02:00
if ( currentHash ) {
// Expand Hash
2013-01-15 13:27:11 +01:00
currentState = History . extractState ( currentHash || History . getLocationHref ( ) , true ) ;
2011-05-20 05:49:30 +02:00
if ( currentState ) {
// We were able to parse it, it must be a State!
// Let's forward to replaceState
//History.debug('History.onPopState: state anchor', currentHash, currentState);
History . replaceState ( currentState . data , currentState . title , currentState . url , false ) ;
}
else {
// Traditional Anchor
//History.debug('History.onPopState: traditional anchor', currentHash);
History . Adapter . trigger ( window , 'anchorchange' ) ;
History . busy ( false ) ;
}
// We don't care for hashes
History . expectedStateId = false ;
return false ;
}
// Ensure
2011-12-13 10:34:25 +01:00
stateId = History . Adapter . extractEventData ( 'state' , event , extra ) || false ;
2011-05-20 05:49:30 +02:00
// Fetch State
2011-12-13 10:34:25 +01:00
if ( stateId ) {
2011-05-20 05:49:30 +02:00
// Vanilla: Back/forward button was used
2011-12-13 10:34:25 +01:00
newState = History . getStateById ( stateId ) ;
2011-05-20 05:49:30 +02:00
}
else if ( History . expectedStateId ) {
// Vanilla: A new state was pushed, and popstate was called manually
newState = History . getStateById ( History . expectedStateId ) ;
}
else {
// Initial State
2013-01-15 13:27:11 +01:00
newState = History . extractState ( History . getLocationHref ( ) ) ;
2011-05-20 05:49:30 +02:00
}
// The State did not exist in our store
if ( ! newState ) {
// Regenerate the State
2013-01-15 13:27:11 +01:00
newState = History . createStateObject ( null , null , History . getLocationHref ( ) ) ;
2011-05-20 05:49:30 +02:00
}
// Clean
History . expectedStateId = false ;
// Check if we are the same state
if ( History . isLastSavedState ( newState ) ) {
// There has been no change (just the page's hash has finally propagated)
//History.debug('History.onPopState: no change', newState, History.savedStates);
History . busy ( false ) ;
return false ;
}
// Store the State
History . storeState ( newState ) ;
History . saveState ( newState ) ;
// Force update of the title
History . setTitle ( newState ) ;
// Fire Our Event
History . Adapter . trigger ( window , 'statechange' ) ;
History . busy ( false ) ;
// Return true
return true ;
} ;
History . Adapter . bind ( window , 'popstate' , History . onPopState ) ;
/ * *
* History . pushState ( data , title , url )
* Add a new State to the history object , become it , and trigger onpopstate
* We have to trigger for HTML4 compatibility
* @ param { object } data
* @ param { string } title
* @ param { string } url
* @ return { true }
* /
History . pushState = function ( data , title , url , queue ) {
//History.debug('History.pushState: called', arguments);
// Check the State
if ( History . getHashByUrl ( url ) && History . emulated . pushState ) {
throw new Error ( 'History.js does not support states with fragement-identifiers (hashes/anchors).' ) ;
}
// Handle Queueing
if ( queue !== false && History . busy ( ) ) {
// Wait + Push to Queue
//History.debug('History.pushState: we must wait', arguments);
History . pushQueue ( {
scope : History ,
callback : History . pushState ,
args : arguments ,
queue : queue
} ) ;
return false ;
}
// Make Busy + Continue
History . busy ( true ) ;
// Create the newState
var newState = History . createStateObject ( data , title , url ) ;
// Check it
if ( History . isLastSavedState ( newState ) ) {
// Won't be a change
History . busy ( false ) ;
}
else {
// Store the newState
History . storeState ( newState ) ;
History . expectedStateId = newState . id ;
// Push the newState
history . pushState ( newState . id , newState . title , newState . url ) ;
// Fire HTML5 Event
History . Adapter . trigger ( window , 'popstate' ) ;
}
// End pushState closure
return true ;
} ;
/ * *
* History . replaceState ( data , title , url )
* Replace the State and trigger onpopstate
* We have to trigger for HTML4 compatibility
* @ param { object } data
* @ param { string } title
* @ param { string } url
* @ return { true }
* /
History . replaceState = function ( data , title , url , queue ) {
//History.debug('History.replaceState: called', arguments);
// Check the State
if ( History . getHashByUrl ( url ) && History . emulated . pushState ) {
throw new Error ( 'History.js does not support states with fragement-identifiers (hashes/anchors).' ) ;
}
// Handle Queueing
if ( queue !== false && History . busy ( ) ) {
// Wait + Push to Queue
//History.debug('History.replaceState: we must wait', arguments);
History . pushQueue ( {
scope : History ,
callback : History . replaceState ,
args : arguments ,
queue : queue
} ) ;
return false ;
}
// Make Busy + Continue
History . busy ( true ) ;
// Create the newState
var newState = History . createStateObject ( data , title , url ) ;
// Check it
if ( History . isLastSavedState ( newState ) ) {
// Won't be a change
History . busy ( false ) ;
}
else {
// Store the newState
History . storeState ( newState ) ;
History . expectedStateId = newState . id ;
// Push the newState
history . replaceState ( newState . id , newState . title , newState . url ) ;
// Fire HTML5 Event
History . Adapter . trigger ( window , 'popstate' ) ;
}
// End replaceState closure
return true ;
} ;
2011-12-13 10:34:25 +01:00
} // !History.emulated.pushState
// ====================================================================
// Initialise
/ * *
* Load the Store
* /
if ( sessionStorage ) {
// Fetch
try {
History . store = JSON . parse ( sessionStorage . getItem ( 'History.store' ) ) || { } ;
}
catch ( err ) {
History . store = { } ;
}
// Normalize
History . normalizeStore ( ) ;
}
else {
// Default Load
History . store = { } ;
History . normalizeStore ( ) ;
}
/ * *
* Clear Intervals on exit to prevent memory leaks
* /
History . Adapter . bind ( window , "unload" , History . clearAllIntervals ) ;
/ * *
* Create the initial State
* /
2013-01-15 13:27:11 +01:00
History . saveState ( History . storeState ( History . extractState ( History . getLocationHref ( ) , true ) ) ) ;
2011-12-13 10:34:25 +01:00
/ * *
* Bind for Saving Store
* /
if ( sessionStorage ) {
// When the page is closed
History . onUnload = function ( ) {
// Prepare
2013-03-20 00:33:21 +01:00
var currentStore , item , currentStoreString ;
2011-12-13 10:34:25 +01:00
// Fetch
try {
currentStore = JSON . parse ( sessionStorage . getItem ( 'History.store' ) ) || { } ;
}
catch ( err ) {
currentStore = { } ;
}
// Ensure
currentStore . idToState = currentStore . idToState || { } ;
currentStore . urlToId = currentStore . urlToId || { } ;
currentStore . stateToId = currentStore . stateToId || { } ;
// Sync
for ( item in History . idToState ) {
if ( ! History . idToState . hasOwnProperty ( item ) ) {
continue ;
}
currentStore . idToState [ item ] = History . idToState [ item ] ;
}
for ( item in History . urlToId ) {
if ( ! History . urlToId . hasOwnProperty ( item ) ) {
continue ;
}
currentStore . urlToId [ item ] = History . urlToId [ item ] ;
}
for ( item in History . stateToId ) {
if ( ! History . stateToId . hasOwnProperty ( item ) ) {
continue ;
}
currentStore . stateToId [ item ] = History . stateToId [ item ] ;
}
// Update
History . store = currentStore ;
History . normalizeStore ( ) ;
2013-03-20 00:33:21 +01:00
// In Safari, going into Private Browsing mode causes the
// Session Storage object to still exist but if you try and use
// or set any property/function of it it throws the exception
// "QUOTA_EXCEEDED_ERR: DOM Exception 22: An attempt was made to
// add something to storage that exceeded the quota." infinitely
// every second.
currentStoreString = JSON . stringify ( currentStore ) ;
try {
// Store
sessionStorage . setItem ( 'History.store' , currentStoreString ) ;
}
catch ( e ) {
// Workaround for a bug seen on iPads. Sometimes the quota exceeded error comes up and simply
// removing/resetting the storage can work.
if ( /QUOTA_EXCEEDED_ERR/ . test ( e . message ) ) {
sessionStorage . removeItem ( 'History.store' ) ;
sessionStorage . setItem ( 'History.store' , currentStoreString ) ;
} else {
throw e ;
}
}
2011-12-13 10:34:25 +01:00
} ;
// For Internet Explorer
History . intervalList . push ( setInterval ( History . onUnload , History . options . storeInterval ) ) ;
2013-03-20 00:33:21 +01:00
2011-12-13 10:34:25 +01:00
// For Other Browsers
History . Adapter . bind ( window , 'beforeunload' , History . onUnload ) ;
History . Adapter . bind ( window , 'unload' , History . onUnload ) ;
2013-03-20 00:33:21 +01:00
2011-12-13 10:34:25 +01:00
// Both are enabled for consistency
}
// Non-Native pushState Implementation
if ( ! History . emulated . pushState ) {
2011-05-20 05:49:30 +02:00
// Be aware, the following is only for native pushState implementations
// If you are wanting to include something for all browsers
// Then include it above this if block
/ * *
* Setup Safari Fix
* /
if ( History . bugs . safariPoll ) {
History . intervalList . push ( setInterval ( History . safariStatePoll , History . options . safariPollInterval ) ) ;
}
/ * *
* Ensure Cross Browser Compatibility
* /
if ( navigator . vendor === 'Apple Computer, Inc.' || ( navigator . appCodeName || '' ) === 'Mozilla' ) {
/ * *
* Fix Safari HashChange Issue
* /
// Setup Alias
History . Adapter . bind ( window , 'hashchange' , function ( ) {
History . Adapter . trigger ( window , 'popstate' ) ;
} ) ;
// Initialise Alias
if ( History . getHash ( ) ) {
History . Adapter . onDomLoad ( function ( ) {
History . Adapter . trigger ( window , 'hashchange' ) ;
} ) ;
}
}
} // !History.emulated.pushState
2011-12-13 10:34:25 +01:00
2011-05-20 05:49:30 +02:00
} ; // History.initCore
2013-03-20 00:33:21 +01:00
// Try to Initialise History
if ( ! History . options || ! History . options . delayInit ) {
History . init ( ) ;
}
2011-05-20 05:49:30 +02:00
2013-03-20 00:33:21 +01:00
} ) ( window ) ;