Let's say I have a page that slides right and left and loads new content using ajax. A variable gets set to leftslide or rightslide depending on the slide direction.
When you click the back button in the browser the html5 popstate event enables and loads the last page using ajax. Now using the variable I can determin what direction to slide in when the back button is pushed if (var direction == 'leftslide'){ direction = 'rightslide'} (to get a nice "back" effect).
But if you push the backbutton twice the slide will not always be correct sine it will just reverse the slide instead of looking at the slide of that paticular page.
So is there any way to save a variables history and get the order back using javascript?
thanks for reading and thinking about this :)
var history = [];
var position = 1;
function onSlide() {
history.push(currentPage);
};
function lastPage() {
return history.length && history[history.length - 2];
};
function currentPage() {
return history.length && history[history.length - 1];
};
function goBack() {
if (history.length) {
position += 1;
navigateTo(history[history.length - position]);
}
}
function goForward() {
if (history.length) {
position -= 1;
navigateTo(history[history.length - position]);
}
}
Get the idea?
Basically, we are saving each page we visit in an array. When we navigate through history, we keep track of our position in history by updating an integer variable that indicates which slot in the history array to check.
This might help: ajax history libraries. Never used ajax or javascript before
This sounds to me more like you want to save some state in a cookie which you can then read anytime the page is loaded. You can make a cookie scoped to the entire site, a branch of the site or just a path.
Related
I have a POST request that pulls data from a server, according to parameters that are adjustable by the user through number inputs. Simply listening to the change event is not enough, because I want the data to refresh while using the mousewheel to change the input value.
Calling my refresh function with something like that is clearly not optimal:
$('input').on('mousewheel', function(){
refresh_data();
});
There would be a lot of requests, and because they are asynchronous, I can never know for sure if the last request to be completed will be the last one I send.
What I currently use is a setInterval to watch an object containing intervals (rounded timestamps) where a refresh has been requested. Inside the mousewheel listener, I add the next interval to the object, as a property/key, to avoid duplicates.
Something like that:
var i = 100;
var obj = [];
$('input').on('mousewheel', function(){
// add next interval to obj;
obj[Math.ceil(Date.now()/i)*i] = true;
});
var check = setInterval(function(){
// refresh if current interval is in obj
var t = Math.floor(Date.now()/i)*i;
if(obj[t]){
delete obj[t]; // remove from obj
refresh_data();
}
}, i);
I tested this code, on my machine, with i=50, and the object is always empty, which is what we want, but with i=30, as soon as I go too fast with the wheel, I see some old intervals accumulating in the object. This is caused by the setInterval is skipping beats, so whatever value I choose for i, some user with less CPU could "skip the wrong beat", and end up finishing his mouse wheel movement seeing a result that does not match the parameters value.
I feel like maybe i'm making this more complicated than it has to be, so i'm asking:
What would be the best way to pull data from server onmousewheel, considering:
1: I want to constantly refresh the data as the user rolls the wheel over an input field.
2: I want to be 100% sure the presented data after the wheel movement is always accurate.
Try using the document offsetheight like the below code. This will work when user scrolls down and reaches the end of scroll. The behavior is kind of like a recycler view of android.
window.onscroll = function(ev) {
if ((window.innerHeight + window.pageYOffset ) >=
document.body.offsetHeight) {
alert("you're at the bottom of the page");
}
};
Hi have the following jQuery event:
listItems.on('click', function(ev) {
ev.preventDefault();
reload = true;
var url = jQuery(this).attr('data-url');
history.pushState({}, '', url);
...
}
Which load dynamic content via AJAX and pushes history state to modify URL in browser from www.domain.com to www.domain.com/page-x. The only reason im loading content via AJAX is that i need to animate page transitions. If you go to www.domain.com/page-x you get normal HTML page.
The problem occurs if after clicking listItem user clicks back button on it's browser. The url changes back from www.domain.com/page-x to www.domain.com, but the page doesn't reload. Thats why i added this event:
window.onpopstate = function() {
if (reload == true) {
reload = false;
location.reload();
}
}
It reloads page after url has changed, if current page was loaded via AJAX. It's working fine, but now i have other problem:
User in frontpage clicks list item;
Browser URL changes, content is loaded via AJAX and animated;
User clicks BACK browser button;
Browser URL changes to previous and page is reloaded;
User clicks FORWARD browser button, URL changes but nothing happens;
Any ideas/solutions would be highly appreciated.
Rather than using reload=true you should rely on event.state so that when popstateoccurs you get a snapshot of what you recorded for that URL.
Manipulating the browser history (MDN)
For example:
listItems.on('click', function(ev) {
ev.preventDefault();
var url = jQuery(this).attr('data-url');
history.pushState({ action: 'list-item-focused' }, '', url);
...
}
And then:
window.onpopstate = function(e) {
/// this state object will have the action attribute 'list-item-focused'
/// when the user navigates forward to the list item. Meaning you can
/// do what you will at this point.
console.log(e.state.action);
}
You probably should avoid a full page reload when the user hits back, and instead animate your content back that used to be there, that way you aren't trashing the page. You can then tell the difference between the landing page and the list pages by checking for event.state.action and code your responses accordingly.
window.onpopstate = function(e) {
if ( e.state.action == 'list-item-focused') {
/// a list item is focused by the history item
}
else {
/// a list item isn't focused, so therefore landing page
/// obviously this only remains true whilst you don't
/// make further pushed states. If you do, you will have to
/// extend your popstate to take those new actions into account.
}
}
I'm sure you are aware of this, but pushState isn't fully cross-browser, so you should also anticipate what could happen for users if these methods aren't supported.
further enhancements
Also, as you are using jQuery, it makes it quite easy for your to store further useful information in the state object, that may help you enhance your reactions in popstate:
history.pushState({
action: 'list-item-focused',
listindex: jQuery(this).index()
});
You have to bear in mind that any data you store is serialised, meaning that it will most likely be converted to some form of non-interactive string or binary data. This means you can't store references to elements or other "live" instances; hence the fact I'm storing the list items index instead.
With the above you now know what action was occurring and on what list item, which you can retrieve at the other end by using:
listItems.eq(event.state.listindex)
what to do onload?
MDN has this to say about what you should do onload.
Reading the current state
When your page loads, it might have a non-null state object. This can happen, for example, if the page sets a state object (using pushState() or replaceState()) and then the user restarts her browser. When your page reloads, the page will receive an onload event, but no popstate event. However, if you read the history.state property, you'll get back the state object you would have gotten if a popstate had fired.
You can read the state of the current history entry without waiting for a popstate event using the history.state property like this:
Put simply they are just recommending that you should listen out for the current history.state when the page loads, and act accordingly based on what your state.action describes. This way you support both events that are triggered within a page's life-time i.e. popstate and when a user directly jumps to a history state which causes a page load.
So with the above in mind it would probably be best to structure things like so:
window.onpopstate = function(e) {
reactToState(e.state);
}
window.onload = function(){
history.state && reactToState(history.state);
}
var reactToState = function(state){
if ( state.action == 'list-item-focused') {
/// a list item is focused by the history item
}
else {
/// a list item isn't focused, so therefore landing page
/// obviously this only remains true whilst you don't
/// make further pushed states. If you do, you will have to
/// extend your popstate to take those new actions into account.
}
}
I've used inline event listeners for simplicity and because your examples do too, however it would be advised to use the addEventListener and attachEvent (IE only) methods instead... or better still, because you are using jQuery.
jQuery(window)
.on('popstate', function(e) {
reactToState(e.originalEvent.state);
}
.on('load', function(){
history.state && reactToState(history.state);
}
;
switching states
Obviously in order to move between two states, you need to know what both those states are; this means having some way to record your current state — so you can compare with the new state and act accordingly. As you only have two states this may not be imperative, but I'd urge you to always be thinking forward about the possibility of having more complexity than you currently have.
I do not know the layout of your code, so it makes it tricky recommending where you should place variables and other such items. However, no matter how you store the information, it doesn't change the fact that it will make your life and code easier if you do:
var reactToState = function(state){
var currentState = reactToState.currentState || {};
if ( !currentState.action ) { currentState.action = 'start'; }
if ( state.action == 'list-item-focused') {
if ( currentState.action == 'start' ) {
/// here we know we are shifting from the start page to list-item
}
}
else {
if ( currentState.action == 'list-item-focused' ) {
/// here we know we are shifting from the list-item to the start
}
}
/// as the state will be global for your application there is no harm
/// in storing the current state on this function as a "static" attribute.
reactToState.currentState = state;
}
Better yet, if you're not averse to switch statements, you can make the above more readable:
var reactToState = function(state){
/// current state with fallback for initial state
var currentState = reactToState.currentState || {action: 'start'};
/// current to new with fallback for empty action i.e. initial state
var a = (currentState.action||'start');
var b = (state.action||'start');
switch ( a + ' >> ' + b ) {
case 'start >> list-item-focused':
/// animate and update here
break;
case 'list-item-focused >> start':
/// animate and update here
break;
}
/// remember to store the current state again
reactToState.currentState = state;
}
Like the title says, I'd like to be able to perform a different onstatechange event if the pushState function is called, instead of the back function. Or, if the go function is negative or positive.
Example:
if History.pushState() or History.go(1) are called, I want the statechange event's callback to be forwardPushState
if History.back() or History.go(-1) are called, I want the statechange event's callback to be backwardsPushState
A state is some data related to a page (as the user see it in the browser). If the user wants to be in a certain page, this page is the same, either he is coming from back button click or from forward button click.
PushState pushes a new state in the stack. It has no relationship with back and go. Back and go are functions to navigate over pushed states in stack. I say this because in your edit, it looks like you are thinking that pushState and go(1) are equivalent.
Maybe if you want to know from which direction the user is coming, you should analyse onstatechange event to know if it takes any parameter that stores direction, which is not a trivial task IMO.
The main thing I think, is that it has no relationship with the go (-1) or go(1) or back.
Maybe this will work for you.
<html>
<body onunload="disableBackButton()">
<h1>You should not come back to this page</h1>
</body>
<script>
function disableBackButton()
{
window.history.forward();
}
setTimeout("disableBackButton()", 0);
</script>
</html>
The code above will make it difficult to use the back button to load this page.
Second try
The code below was taken from another stack overflow.
detect back button click in browser
window.onload = function () {
if (typeof history.pushState === "function") {
history.pushState("jibberish", null, null);
window.onpopstate = function () {
history.pushState('newjibberish', null, null);
// Handle the back (or forward) buttons here
// Will NOT handle refresh, use onbeforeunload for this.
};
}
else {
var ignoreHashChange = true;
window.onhashchange = function () {
if (!ignoreHashChange) {
ignoreHashChange = true;
window.location.hash = Math.random();
// Detect and redirect change here
// Works in older FF and IE9
// * it does mess with your hash symbol (anchor?) pound sign
// delimiter on the end of the URL
}
else {
ignoreHashChange = false;
}
};
}
}
I am writing a single page javascript application using the HTML5 History API. The application loads content via Ajax and internally maintains state information on the foreground screen using a screen stack.
I want to enable navigation with the back button, but I never want to the forward button to be enabled.
A couple quick bits of information:
The user should only ever be able to go back, never forward
Pressing browser back button closes the current page screen user is on and reloads the previous one
Project is targeted towards the latest version of Chrome only, so other browsers implementations are not important
I am using native JavaScript and jQuery only, I would like to do this without History.js
When I load a new screen I run the following line:
history.pushState(screenData, window.document.title, "#");
I bind to the popstate event via jQuery:
$(window).bind("popstate", function(event) {
if (event.originalEvent.state != null) {
// Logic that loads the previous screen using my screen stack
}
});
My application's history management is working, however when I go back the forward button is enabled. I need to figure out how to remove data from history on the popstate event.
Can I do this with replaceState? I'm not sure how to go about doing this...
The accepted answer solves the problem to disable the forward button, but creates a new annoying issue "the page navigated back to" is inserted in duplicate into the history (as indicated in the answers comments).
Here is how solve the question "diabled forward button" and to avoid the "duplicate" back-button-issue.
//setup the popstate EventListener that reacts to browser history events
window.addEventListener("popstate",function(event){
// In order to remove any "forward"-history (i.e. disable forward
// button), this popstate's event history state (having been navigated
// back to) must be insert _again_ as a new history state, thereby
// making it the new most forwad history state.
// This leaves the problem that to have this popstate event's history
// state to become the new top, it would now be multiple in the history
//
// Effectively history would be:
// * [states before..] ->
// * [popstate's event.state] ->
// * [*newly pushed _duplicate_ of popstate's event.state ]
//
// To remove the annoyance/confusion for the user to have duplicate
// history states, meaning it has to be clicked at least twice to go
// back, we pursue the strategy of marking the current history state
// as "obsolete", as it has been inserted _again_ as to be the most
// forward history entry.
//
// the popstate EventListener will hence have to distinguish 2 cases:
//
// case A) "popstate event is _not_ an obsolete duplicate"...
if( typeof event.state == "object"
&& event.state.obsolete !== true)
{
//...so we _firstly_ mark this state as to from now on "obsolete",
// which can be done using the history API's replaceState method
history.replaceState({"obsolete":true},"");
// and _secondly_ push this state _one_more_time_ to the history
// which will solve the OP's desired "disable forward button" issue
history.pushState(event.state,"");
}
// case B: there is the other case that the user clicked "back" and
// encounters one of the duplicate history event entries that are
// "obsolete" now.
if( typeof event.state == "object"
&& event.state.obsolete === true)
{
//...in which case we simply go "back" once more
history.back()
// by this resolving the issue/problem that the user would
// be counter-intuively needing to click back multiple times.
// > we skip over the obsolete duplicates, that have been the
// the result of necessarily pushing a history state to "disable
// forward navigation"
}
},false);
Bad Part
To really disable the forward button, you would have to be able to delete browser history, which is not allowed by all javascript implementations because it would allow sites to delete the entire history, which would never be in the interest of the user.
Good Part
This is a bit tricky, but I guess it could work if you want to do custom history. You could just use pushState in the popstate event to make your actual page the topmost history entry. I assume the way you handle your history, your window will never unload. This allows you to keep track of the user history yourself:
var customHistory = [];
Push every page you load with history.pushState(screenData, window.document.title, "#");, like you did before. Only you add the state to your custom history, too:
history.pushState(screenData, window.document.title, "#");
customHistory.push({data: screenData, title: window.document.title, location: '#'});
now if you have a popstate event, you just pop you custom history and push it to the topmost entry:
window.onpopstate = function(e) {
var lastEntry = customHistory.pop();
history.pushState(lastEntry.data, lastEntry.title, lastEntry.location);
// load the last entry
}
Or in jQuery
$(window).on('popstate', function(e) {
var lastEntry = customHistory.pop();
history.pushState(lastEntry.data, lastEntry.title, lastEntry.location);
// load the last entry
});
Just use following jquery to disable forward button:
$( document ).ready( function(){
history.pushState(null, document.title, location.href);
});
NOTE:
This code was tested and worked fine without showing any problems, however
I would incentivize developers to test it more before going to production with the code.
If HTML5 history.replaceState() is used anywhere in your application,
the code below might now work.
I created a custom function in order to disable the forward button.
Here is the code (it doesn't work with the hash routing strategy):
<script>
(function() {
// function called after the DOM has loaded
disableForwardButton();
})();
function disableForwardButton() {
var flag, loop = false;
window.addEventListener('popstate', function(event) {
if (flag) {
if (history.state && history.state.hasOwnProperty('page')) {
loop = true;
history.go(-1);
} else {
loop = false;
history.go(-1);
}
} else {
history.pushState({
page: true
},
null,
null
);
}
flag = loop ? true : !flag;
});
window.onclick = function(event) {
flag = false;
};
}
</script>
As Redrif pointed out in the comments of the accepted answer, the problem is that you have to double click the back button in order to navigate back to the page which is tedious and impractical.
Code explanation: each time you click the back button you need to create an additional history element so that the the current page which you are located on
points to the newly created history page. In that way there is no page to go forward to since the pushState is the last state (picture it as the last element in the array) therefore your forward button will always be disabled.
The reason why it was mandatory to introduce the loop variable is because you can have a scenario where you go back to a certain page and the pushState code occurs which creates the last history element and instead going back again you choose to click on some link again and again go back the previous page which now creates an additional history element. In other words, you have something like this:
[page1, page2, page2, page2]
now, once on page2 (index 3 element) and you click the back button again you will get to the page2 again index 1 element and you do not want that. Remember that you can have an array of x page2 elements hence the loop false variable was introduced to resolve that particular case, with it you jump all the way from page2 to page 1 no matter how many page2 elements are their in the array.
Is it possible check if there is a value for history.go(-1)? I know you can't access history.previous directly.
I am trying to stay away from document.referrer because I know it can be blocked in some instances.
Here is what I am trying to do. I have an error page, on this page I would like to either have a BACK button (if it's not the only item in history) or a close button (if it is).
if (history.length) {
//There is history to go back to
history.go(-1);
}
Actually, history.length is always one or more, since the current page counts. Also, if you have a forward history (i.e. you used the back button), those pages also count. So you need a more complicated check:
if( (1 < history.length) && document.referrer ) {
There is no cross-browser approach to accomplish this. Document.Referrer may be set even if no history entry exists.
I came up with the following "hack". It utilizes the onbeforeunload event to detect whether the browser starts leaving the page or not. If it does not in a certain timespan it'll just redirect to the fallback.
window.goBack = function goBack(fallback){
var useFallback = true;
window.onbeforeunload = function(){
useFallback = false;
}
window.history.back();
setTimeout(function(){
if (useFallback){ window.location.href = fallback; }
}, 100);
}
You can call this function using goBack("fallback.example.org").
One of the use cases is that you may want to add a back button to any page and also want to make sure that this back button works even if the user goes directly to this page (e.g. by bookmark, direct link etc).
So either it does perform a history.back() or if there is no entry, it'll redirect to a fallback.
If the history has a length greater than 0, then it contains at least one history point.
if (history.length)
function test() {
document.URL = document.referrer;
}