Skip to content
This repository was archived by the owner on Oct 8, 2021. It is now read-only.

Commit 3217489

Browse files
committed
prefer backward history movement, tests to accompany
1 parent c9ba67f commit 3217489

File tree

3 files changed

+84
-25
lines changed

3 files changed

+84
-25
lines changed

js/navigation/events/navigate.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ define([ "jquery",
4343
// Users that want to fully normalize the two events
4444
// will need to do history management down the stack and
4545
// add the state to the event before this binding is fired
46+
// TODO consider allowing for the explicit addition of callbacks
47+
// to be fired before this value is set to avoid event timing issues
4648
state: event.hashchangeState || {}
4749
});
4850
},

js/navigation/navigate.js

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,14 @@ define([
4242
// NOTE we currently _leave_ the appended hash in the hash in the interest
4343
// of seeing what happens and if we can support that before the hash is
4444
// pushed down
45-
46-
// set the hash to be squashed by replace state or picked up by
47-
// the navigation special event
48-
history.ignoreNextHashChange = true;
49-
5045
// IMPORTANT in the case where popstate is supported the event will be triggered
5146
// directly, stopping further execution - ie, interupting the flow of this
5247
// method call to fire bindings at this expression. Below the navigate method
5348
// there is a binding to catch this event and stop its propagation.
5449
//
5550
// We then trigger a new popstate event on the window with a null state
5651
// so that the navigate events can conclude their work properly
52+
history.ignoreNextHashChange = true;
5753
window.location.hash = url;
5854

5955
if( $.support.pushState ) {
@@ -75,7 +71,8 @@ define([
7571
// is not fired.
7672
window.history.replaceState( state, document.title, href );
7773

78-
// Trigger a new faux popstate event to
74+
// Trigger a new faux popstate event to replace the one that we
75+
// caught that was triggered by the hash setting above.
7976
$( window ).trigger( popstateEvent );
8077
}
8178

@@ -93,6 +90,12 @@ define([
9390
// TODO grab the original event here and use it for the synthetic event in the
9491
// second half of the navigate execution that will follow this binding
9592
$( window ).bind( "popstate", function( event ) {
93+
// Partly to support our test suite which manually alters the support
94+
// value to test hashchange. Partly to prevent all around weirdness
95+
if( !$.support.pushState ){
96+
return;
97+
}
98+
9699
if( history.ignoreNextHashChange ) {
97100
history.ignoreNextHashChange = false;
98101
event.stopImmediatePropagation();
@@ -121,9 +124,10 @@ define([
121124

122125

123126
history.direct({
124-
currentUrl: path.parseLocation().hash ,
125-
either: function( historyEntry ) {
127+
url: path.parseLocation().hash ,
128+
either: function( historyEntry, direction ) {
126129
event.hashchangeState = historyEntry;
130+
event.hashchangeState.direction = direction;
127131
}
128132
});
129133
});
@@ -170,27 +174,43 @@ define([
170174
this.stack = this.stack.slice( 0, this.activeIndex + 1 );
171175
},
172176

173-
direct: function( opts ) {
174-
var back, forward, newActiveIndex, prev = this.getActive(), a = this.activeIndex;
175-
176-
// check if url is in history and if it's ahead or behind current page
177-
$.each( this.stack, function( i, historyEntry ) {
178-
//if the url is in the stack, it's a forward or a back
179-
if ( decodeURIComponent( opts.currentUrl ) === decodeURIComponent( historyEntry.url ) ) {
180-
//define back and forward by whether url is older or newer than current page
181-
back = i < this.activeIndex;
182-
forward = !back;
183-
newActiveIndex = i;
177+
find: function( url, stack ) {
178+
var entry, i, length = this.stack.length, newActiveIndex;
179+
180+
for ( i = 0; i < length; i++ ) {
181+
entry = this.stack[i];
182+
183+
if ( decodeURIComponent( url ) === decodeURIComponent( entry.url ) ) {
184+
return i;
184185
}
185-
});
186+
}
187+
188+
return undefined;
189+
},
190+
191+
direct: function( opts ) {
192+
var back, forward, entry, newActiveIndex, prev = this.getActive(), a = this.activeIndex;
193+
194+
// First, take the slice of the history stack before the current index and search
195+
// for a url match. If one is found, we'll avoid avoid looking through forward history
196+
// NOTE the preference for backward history movement is driven by the fact that
197+
// most mobile browsers only have a dedicated back button, and users rarely use
198+
// the forward button in desktop browser anyhow
199+
newActiveIndex = this.find( opts.url, this.stack.slice(0, a - 1).reverse() );
200+
201+
// If nothing was found in backward history check forward
202+
if( newActiveIndex === undefined ) {
203+
newActiveIndex = this.find( opts.url, this.stack.slice(a + 1) );
204+
}
186205

187206
// save new page index, null check to prevent falsey 0 result
188207
this.activeIndex = newActiveIndex !== undefined ? newActiveIndex : this.activeIndex;
189208

190-
if ( back ) {
191-
( opts.either || opts.isBack )( this.getActive() );
192-
} else if ( forward ) {
193-
( opts.either || opts.isForward )( this.getActive() );
209+
// invoke callbacks where appropriate
210+
if ( newActiveIndex < a ) {
211+
( opts.either || opts.isBack )( this.getActive(), 'back' );
212+
} else if ( newActiveIndex > a ) {
213+
( opts.either || opts.isForward )( this.getActive(), 'forward' );
194214
}
195215
},
196216

tests/unit/navigation/navigate_method.js

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ $.testHelper.setPushState();
2323
}
2424

2525
$.navigate.history.stack = [];
26+
$.navigate.history.activeIndex = 0;
2627
}
2728
});
2829

@@ -60,7 +61,7 @@ $.testHelper.setPushState();
6061
}
6162

6263
// Test the inclusion of state for both pushstate and hashchange
63-
// _ --nav--> #foo {state} --nav--> #bar --back--> #foo {state} --foward--> #bar {state}
64+
// --nav--> #foo {state} --nav--> #bar --back--> #foo {state} --foward--> #bar {state}
6465
asyncTest( "navigating backward should include the history state", function() {
6566
$.testHelper.eventTarget = $( window );
6667

@@ -88,4 +89,40 @@ $.testHelper.setPushState();
8889
}
8990
]);
9091
});
92+
93+
// --nav--> #foo {state} --nav--> #bar --nav--> #foo {state} --back--> #bar --back--> #foo {state.direction = back}
94+
asyncTest( "navigation back to a duplicate history state should prefer back", function() {
95+
$.testHelper.eventTarget = $( window );
96+
97+
$.testHelper.eventSequence( "navigate", [
98+
function() {
99+
$.navigate( "#foo" );
100+
},
101+
102+
function() {
103+
$.navigate( "#bar" );
104+
},
105+
106+
function() {
107+
$.navigate( "#foo" );
108+
},
109+
110+
function() {
111+
equal( $.navigate.history.activeIndex, 2, "after n navigation events the active index is correct" );
112+
window.history.back();
113+
},
114+
115+
function( timedOut, data ) {
116+
equal( $.navigate.history.activeIndex, 1, "after n navigation events, and a back, the active index is correct" );
117+
equal( data.state.direction, "back", "the direction should be back and not forward" );
118+
window.history.back();
119+
},
120+
121+
function( timedOut, data ) {
122+
equal( $.navigate.history.activeIndex, 0 );
123+
equal( data.state.direction, "back", "the direction should be back and not forward" );
124+
start();
125+
}
126+
]);
127+
});
91128
})( jQuery );

0 commit comments

Comments
 (0)