@@ -15,28 +15,8 @@ define([
1515
1616 // TODO consider queueing navigation activity until previous activities have completed
1717 // so that end users don't have to think about it. Punting for now
18- $ . navigate = function ( url , data ) {
19- var href , state ,
20- hash = path . parseUrl ( url ) . hash ,
21- isPath = path . isPath ( hash ) ,
22- resolutionUrl = isPath ? path . getLocation ( ) : $ . mobile . getDocumentUrl ( ) ;
23-
24- // #/foo/bar.html => /foo/bar.html
25- // #foo => #foo
26- hash = isPath ? hash . replace ( "#" , "" ) : hash ;
27-
28- // make the hash abolute with the current href
29- href = path . makeUrlAbsolute ( hash , resolutionUrl ) ;
30-
31- if ( isPath ) {
32- href = path . resetUIKeys ( href ) ;
33- }
34-
35- state = $ . extend ( data , {
36- url : url ,
37- hash : hash ,
38- title : document . title
39- } ) ;
18+ $ . navigate = function ( url , data , noEvents ) {
19+ var state ;
4020
4121 // NOTE we currently _leave_ the appended hash in the hash in the interest
4222 // of seeing what happens and if we can support that before the hash is
@@ -50,7 +30,12 @@ define([
5030 // We then trigger a new popstate event on the window with a null state
5131 // so that the navigate events can conclude their work properly
5232 history . ignoreNextHashChange = true ;
53- window . location . hash = url ;
33+ window . location . hash = path . cleanHash ( url ) ;
34+
35+ state = $ . extend ( data , {
36+ url : url ,
37+ title : document . title
38+ } ) ;
5439
5540 if ( $ . support . pushState ) {
5641 popstateEvent = new $ . Event ( "popstate" ) ;
@@ -59,22 +44,14 @@ define([
5944 state : null
6045 } ;
6146
62- // replace the current url with the new href and store the state
63- // Note that in some cases we might be replacing an url with the
64- // same url. We do this anyways because we need to make sure that
65- // all of our history entries have a state object associated with
66- // them. This allows us to work around the case where $.mobile.back()
67- // is called to transition from an external page to an embedded page.
68- // In that particular case, a hashchange event is *NOT* generated by the browser.
69- // Ensuring each history entry has a state object means that onPopState()
70- // will always trigger our hashchange callback even when a hashchange event
71- // is not fired.
72- window . history . replaceState ( state , document . title , href ) ;
47+ $ . navigate . squash ( url , data ) ;
7348
7449 // Trigger a new faux popstate event to replace the one that we
7550 // caught that was triggered by the hash setting above.
76- history . ignoreNextPopState = true ;
77- $ ( window ) . trigger ( popstateEvent ) ;
51+ if ( ! noEvents ) {
52+ history . ignoreNextPopState = true ;
53+ $ ( window ) . trigger ( popstateEvent ) ;
54+ }
7855 }
7956
8057 // record the history entry so that the information can be included
@@ -83,6 +60,40 @@ define([
8360 history . add ( url , state ) ;
8461 } ;
8562
63+ $ . navigate . squash = function ( url , data ) {
64+ var state , href ,
65+ hash = url ,
66+ isPath = path . isPath ( hash ) ,
67+ resolutionUrl = isPath ? path . getLocation ( ) : $ . mobile . getDocumentUrl ( ) ;
68+
69+ // make the hash abolute with the current href
70+ href = path . makeUrlAbsolute ( hash , resolutionUrl ) ;
71+
72+ if ( isPath ) {
73+ href = path . resetUIKeys ( href ) ;
74+ }
75+
76+ state = $ . extend ( data , {
77+ url : url ,
78+ hash : hash ,
79+ title : document . title
80+ } ) ;
81+
82+ // replace the current url with the new href and store the state
83+ // Note that in some cases we might be replacing an url with the
84+ // same url. We do this anyways because we need to make sure that
85+ // all of our history entries have a state object associated with
86+ // them. This allows us to work around the case where $.mobile.back()
87+ // is called to transition from an external page to an embedded page.
88+ // In that particular case, a hashchange event is *NOT* generated by the browser.
89+ // Ensuring each history entry has a state object means that onPopState()
90+ // will always trigger our hashchange callback even when a hashchange event
91+ // is not fired.
92+ window . history . replaceState ( state , document . title , href ) ;
93+
94+ return state ;
95+ } ;
96+
8697 // This binding is intended to catch the popstate events that are fired
8798 // when execution of the `$.navigate` method stops at window.location.hash = url;
8899 // and completely prevent them from propagating. The popstate event will then be
@@ -91,6 +102,8 @@ define([
91102 // TODO grab the original event here and use it for the synthetic event in the
92103 // second half of the navigate execution that will follow this binding
93104 $ ( window ) . bind ( "popstate.history" , function ( event ) {
105+ var hash , state ;
106+
94107 // Partly to support our test suite which manually alters the support
95108 // value to test hashchange. Partly to prevent all around weirdness
96109 if ( ! $ . support . pushState ) {
@@ -112,16 +125,32 @@ define([
112125 return ;
113126 }
114127
115- // account for initial page load popstate, and other popstates triggered
116- // by other parts of the application (ie, during the refactor)
128+ // account for direct manipulation of the hash. That is, we will receive a popstate
129+ // when the hash is changed by assignment, and it won't have a state associated. We
130+ // then need to squash the hash. See below for handling of hash assignment that
131+ // matches an existing history entry
117132 if ( ! event . originalEvent . state ) {
133+ hash = path . parseLocation ( ) . hash ;
134+
135+ // squash a hash with replacestate
136+ if ( path . isPath ( hash ) ) {
137+ state = $ . navigate . squash ( hash ) ;
138+ }
139+
140+ // record the new hash as an additional history entry
141+ // to match the browser's treatment of hash assignment
142+ history . add ( hash , state ) ;
143+
144+ // do not alter history, we've added a new history entry
145+ // so we know where we are
118146 return ;
119147 }
120148
121- // If this is a popstate that comes from the back or forward buttons
122- // make sure to set the state of our history stack properly
149+ // If all else fails this is a popstate that comes from the back or forward buttons
150+ // make sure to set the state of our history stack properly, and record the directionality
123151 history . direct ( {
124- url : event . originalEvent . state . hash ,
152+ url : ( event . originalEvent . state || { } ) . hash || hash ,
153+
125154 either : function ( historyEntry , direction ) {
126155 event . historyState = historyEntry ;
127156 event . historyState . direction = direction ;
@@ -135,6 +164,8 @@ define([
135164 // TODO add a check here that `hashchange.navigate` is bound already otherwise it's
136165 // broken (exception?)
137166 $ ( window ) . bind ( "hashchange.history" , function ( event ) {
167+ var hash = path . parseLocation ( ) . hash ;
168+
138169 // If pushstate is supported the state will be included in the popstate event
139170 // data and appended to the navigate event. Late check here for late settings (eg tests)
140171 if ( $ . support . pushState ) {
@@ -144,18 +175,30 @@ define([
144175 // If the hashchange has been explicitly ignored or we have no history at
145176 // this point skip the history managment and the addition of the history
146177 // entry to the event for the `navigate` bindings
147- if ( history . ignoreNextHashChange || history . stack . length == 0 ) {
178+ if ( history . ignoreNextHashChange ) {
148179 history . ignoreNextHashChange = false ;
149180 return ;
150181 }
151182
152183 // If this is a hashchange caused by the back or forward button
153184 // make sure to set the state of our history stack properly
154185 history . direct ( {
155- url : path . parseLocation ( ) . hash ,
186+ url : hash ,
187+ // When the url is either forward or backward in history include the entry
188+ // here
156189 either : function ( historyEntry , direction ) {
157190 event . hashchangeState = historyEntry ;
158191 event . hashchangeState . direction = direction ;
192+ } ,
193+
194+ // When we don't find a hash in our history clearly we're aiming to go there
195+ // record the entry as new history
196+ neither : function ( ) {
197+ history . add ( hash , {
198+ hash : hash ,
199+ title : document . title ,
200+ url : location . href
201+ } ) ;
159202 }
160203 } ) ;
161204 } ) ;
@@ -202,18 +245,24 @@ define([
202245 this . stack = this . stack . slice ( 0 , this . activeIndex + 1 ) ;
203246 } ,
204247
205- find : function ( url , stack ) {
206- var entry , i , length = this . stack . length , newActiveIndex ;
248+ find : function ( url , stack , earlyReturn ) {
249+ stack = stack || this . stack ;
250+
251+ var entry , i , length = stack . length , index ;
207252
208253 for ( i = 0 ; i < length ; i ++ ) {
209- entry = this . stack [ i ] ;
254+ entry = stack [ i ] ;
210255
211256 if ( decodeURIComponent ( url ) === decodeURIComponent ( entry . url ) ) {
212- return i ;
257+ index = i ;
258+
259+ if ( earlyReturn ) {
260+ return index ;
261+ }
213262 }
214263 }
215264
216- return undefined ;
265+ return index ;
217266 } ,
218267
219268 direct : function ( opts ) {
@@ -224,11 +273,18 @@ define([
224273 // NOTE the preference for backward history movement is driven by the fact that
225274 // most mobile browsers only have a dedicated back button, and users rarely use
226275 // the forward button in desktop browser anyhow
227- newActiveIndex = this . find ( opts . url , this . stack . slice ( 0 , a - 1 ) . reverse ( ) ) ;
228-
229- // If nothing was found in backward history check forward
276+ newActiveIndex = this . find ( opts . url , this . stack . slice ( 0 , a ) ) ;
277+
278+ // If nothing was found in backward history check forward. The `true`
279+ // value passed as the third parameter causes the find method to break
280+ // on the first match in the forward history slice. The starting index
281+ // of the slice must then be added to the result to get the element index
282+ // in the original history stack :( :(
283+ //
284+ // TODO this is hyper confusing and should be cleaned up
230285 if ( newActiveIndex === undefined ) {
231- newActiveIndex = this . find ( opts . url , this . stack . slice ( a + 1 ) ) ;
286+ newActiveIndex = this . find ( opts . url , this . stack . slice ( a + 1 ) , true ) ;
287+ newActiveIndex = newActiveIndex === undefined ? newActiveIndex : newActiveIndex + a + 1 ;
232288 }
233289
234290 // save new page index, null check to prevent falsey 0 result
@@ -239,6 +295,10 @@ define([
239295 ( opts . either || opts . isBack ) ( this . getActive ( ) , 'back' ) ;
240296 } else if ( newActiveIndex > a ) {
241297 ( opts . either || opts . isForward ) ( this . getActive ( ) , 'forward' ) ;
298+ } else if ( newActiveIndex === a ) {
299+ opts . same ? opts . same ( this . getActiveIndex ) : null ;
300+ } else if ( opts . neither ) {
301+ opts . neither ( this . getActive ( ) ) ;
242302 }
243303 } ,
244304
@@ -247,8 +307,9 @@ define([
247307 ignoreNextHashChange : false
248308 } ;
249309
250- // NOTE Set the initial url history state, so that we have a history entry to match
251- history . add ( path . parseLocation ( ) . pathname + path . parseLocation ( ) . search , { } ) ;
310+
311+ // Record the initial page with a replace state where necessary
312+ history . add ( location . href , { } ) ;
252313} ) ( jQuery ) ;
253314
254315//>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
0 commit comments