It is a dummy banking project. The following addEventListener function is used to check if the login password matches with existing array. If yes, then corresponding account details are displayed.
The problem is that when login details are entered correct, the correct information is shown on the screen for one second and then the screen is set to the previous state.
I think there is some problem with the login button. Hence I commented all the code in the callback function and tried logging simple string to console.log(). Even there it appears for a second and then vanishes. In fact, the code execution stays in effect only for a moment once the button is pressed.
The code:
const loginUser = function(){
let inputLogin = inputLoginUsername.value;
let inputPswd = inputLoginPin.value;
for(let [index,[userId,passWord]] of identifyUser.entries()){
if(userId === inputLogin && passWord == inputPswd){
displayMovements(accounts[index].movements);
break;
}else {
continue;
}
}
};
btnLogin.addEventListener('click',loginUser);
I have a web site that contains several pages where the user can add and edit information. In order to provide a consistent UI, I have the following JavaScript function...
function setWindowBeforeUnload(changed) {
$(window).on("beforeunload", function() {
if (confirmLeave && changed && changed()) {
return "You haven't saved the information. If you leave this page, the information will be lost.";
}
});
}
confirmLeave is a global variable that specifies if we are to ask them for confirmation before navigating away (which we don't if we are navigating to another page after a successful save). changed is a function that checks if the entity has changed.
This is called from a details page (say the customer page) as follows...
$(document).ready(function() {
setWindowBeforeUnload(customerChanged);
});
function customerChanged() {
// Checks the data and returns true or false as appropriate
}
This all worked fine until recently, when a change in Chrome broke it.
I have searched for hours, and found loads of people suggesting code like this...
addEventListener('beforeunload', function(event) {
event.returnValue = 'You have unsaved changes.';
});
...which works fine as it is, except that it fires the warning whenever they leave the page, irrespective of whether or not the data has changed.
As soon as I try to add any logic (such as my checking code in the first sample), it doesn't work...
function setWindowBeforeUnload(changed) {
addEventListener('beforeunload', function(event) {
if (confirmLeave && changed && changed()) {
event.returnValue = 'You have unsaved changes.';
}
});
}
With this code, I can navigate away from the page without getting a warning.
Is there any way to reproduce my original behaviour now?
You can use logic in the handler, you just can't have a custom message any more.
See the code below. Use the "Run code snippet" to simulate navigation. Run the snippet, run it again no confirm. Toggle the button to "false" run the snippet and get a confirm.
var test = true;
function otherTest() {
return true;
}
addEventListener('beforeunload', function(event) {
if(!test || !otherTest()) {
event.returnValue = 'You have unsaved changes.';
}
});
document.getElementById('theButton').addEventListener('click', function(event) {
test = !test;
this.innerHTML = test.toString();
});
<p>Click the button to turn the confirm on and off</p>
<button id="theButton">true</button>
When the page loads I set: var lock = 0;
I change lock to 1 if I want to provide a warning to the user if they are about to close their page and have pending actions. I sometimes change it to 2 if I need a different warning. If lock is set to 0 then I don't want a warning message to appear as the user closes their page.
This shows my custom warning message just fine every time the user moves away from the page, regardless of lock setting.
window.onbeforeunload = function(){
return 'Are you sure you want to leave this page?';
}
This only shows when lock is 1 or 2 but doesn't show my custom warning message.
window.onbeforeunload = function(){
if(lock == 1) return 'Are you sure you want to leave this page?';
else if(lock == 2) return 'Bad things will happen if you close this page!';
}
Is there a way to have a conditional custom warning message? It seems like if I have anything other than 'return' inside the function then it uses the default message.
Tarting from Chrome 51, A window’s onbeforeload property no longer supports a custom string.
Reference: https://developers.google.com/web/updates/2016/04/chrome-51-deprecations?hl=en#remove-custom-messages-in-onbeforeload-dialogs
Using the HTML5 window.history API, I can control the navigation pretty well on my web app.
The app currently has two states: selectDate (1) and enterDetails (2).
When the app loads, I replaceState and set a popState listener:
history.replaceState({stage:"selectDate",...},...);
window.onpopstate = function(event) {
that.toStage(event.state.stage);
};
When a date is selected and the app moves to stage 2 I push state 2 onto the stack:
history.pushState({stage:"enterDetails",...},...);
This state is replaced anytime details change so they are saved in the history.
There are three ways to leave stage 2:
save (AJAX submit)
cancel
back button
The back button is handled by the popstate listener. The cancel button pushes stage 1 so that the user can go back to the details they were entering the back button. These both work well.
The save button should revert back to stage 1 and not allow the user to navigate back to the details page (since they already submitted). Basical, y it should make the history stack be length = 1.
But there doesn't seem to be a history.delete(), or history.merge(). The best I can do is a history.replaceState(stage1) which leaves the history stack as: ["selectDate","selectDate"].
How do I get rid of one layer?
Edit:
Thought of something else, but it doesn't work either.
history.back(); //moves history to the correct position
location.href = "#foo"; // successfully removes ability to go 'forward',
// but also adds another layer to the history stack
This leaves the history stack as ["selectDate","selectDate#foo"].
So, as an alternative, is there a way to remove the 'forward' history without pushing a new state?
You may have moved on by now, but... as far as I know there's no way to delete a history entry (or state).
One option I've been looking into is to handle the history yourself in JavaScript and use the window.history object as a carrier of sorts.
Basically, when the page first loads you create your custom history object (we'll go with an array here, but use whatever makes sense for your situation), then do your initial pushState. I would pass your custom history object as the state object, as it may come in handy if you also need to handle users navigating away from your app and coming back later.
var myHistory = [];
function pageLoad() {
window.history.pushState(myHistory, "<name>", "<url>");
//Load page data.
}
Now when you navigate, you add to your own history object (or don't - the history is now in your hands!) and use replaceState to keep the browser out of the loop.
function nav_to_details() {
myHistory.push("page_im_on_now");
window.history.replaceState(myHistory, "<name>", "<url>");
//Load page data.
}
When the user navigates backwards, they'll be hitting your "base" state (your state object will be null) and you can handle the navigation according to your custom history object. Afterward, you do another pushState.
function on_popState() {
// Note that some browsers fire popState on initial load,
// so you should check your state object and handle things accordingly.
// (I did not do that in these examples!)
if (myHistory.length > 0) {
var pg = myHistory.pop();
window.history.pushState(myHistory, "<name>", "<url>");
//Load page data for "pg".
} else {
//No "history" - let them exit or keep them in the app.
}
}
The user will never be able to navigate forward using their browser buttons because they are always on the newest page.
From the browser's perspective, every time they go "back", they've immediately pushed forward again.
From the user's perspective, they're able to navigate backwards through the pages but not forward (basically simulating the smartphone "page stack" model).
From the developer's perspective, you now have a high level of control over how the user navigates through your application, while still allowing them to use the familiar navigation buttons on their browser. You can add/remove items from anywhere in the history chain as you please. If you use objects in your history array, you can track extra information about the pages as well (like field contents and whatnot).
If you need to handle user-initiated navigation (like the user changing the URL in a hash-based navigation scheme), then you might use a slightly different approach like...
var myHistory = [];
function pageLoad() {
// When the user first hits your page...
// Check the state to see what's going on.
if (window.history.state === null) {
// If the state is null, this is a NEW navigation,
// the user has navigated to your page directly (not using back/forward).
// First we establish a "back" page to catch backward navigation.
window.history.replaceState(
{ isBackPage: true },
"<back>",
"<back>"
);
// Then push an "app" page on top of that - this is where the user will sit.
// (As browsers vary, it might be safer to put this in a short setTimeout).
window.history.pushState(
{ isBackPage: false },
"<name>",
"<url>"
);
// We also need to start our history tracking.
myHistory.push("<whatever>");
return;
}
// If the state is NOT null, then the user is returning to our app via history navigation.
// (Load up the page based on the last entry of myHistory here)
if (window.history.state.isBackPage) {
// If the user came into our app via the back page,
// you can either push them forward one more step or just use pushState as above.
window.history.go(1);
// or window.history.pushState({ isBackPage: false }, "<name>", "<url>");
}
setTimeout(function() {
// Add our popstate event listener - doing it here should remove
// the issue of dealing with the browser firing it on initial page load.
window.addEventListener("popstate", on_popstate);
}, 100);
}
function on_popstate(e) {
if (e.state === null) {
// If there's no state at all, then the user must have navigated to a new hash.
// <Look at what they've done, maybe by reading the hash from the URL>
// <Change/load the new page and push it onto the myHistory stack>
// <Alternatively, ignore their navigation attempt by NOT loading anything new or adding to myHistory>
// Undo what they've done (as far as navigation) by kicking them backwards to the "app" page
window.history.go(-1);
// Optionally, you can throw another replaceState in here, e.g. if you want to change the visible URL.
// This would also prevent them from using the "forward" button to return to the new hash.
window.history.replaceState(
{ isBackPage: false },
"<new name>",
"<new url>"
);
} else {
if (e.state.isBackPage) {
// If there is state and it's the 'back' page...
if (myHistory.length > 0) {
// Pull/load the page from our custom history...
var pg = myHistory.pop();
// <load/render/whatever>
// And push them to our "app" page again
window.history.pushState(
{ isBackPage: false },
"<name>",
"<url>"
);
} else {
// No more history - let them exit or keep them in the app.
}
}
// Implied 'else' here - if there is state and it's NOT the 'back' page
// then we can ignore it since we're already on the page we want.
// (This is the case when we push the user back with window.history.go(-1) above)
}
}
There is no way to delete or read the past history.
You could try going around it by emulating history in your own memory and calling history.pushState everytime window popstate event is emitted (which is proposed by the currently accepted Mike's answer), but it has a lot of disadvantages that will result in even worse UX than not supporting the browser history at all in your dynamic web app, because:
popstate event can happen when user goes back ~2-3 states to the past
popstate event can happen when user goes forward
So even if you try going around it by building virtual history, it's very likely that it can also lead into a situation where you have blank history states (to which going back/forward does nothing), or where that going back/forward skips some of your history states totally.
A simple solution:
var ismobilescreen = $(window).width() < 480;
var backhistory_pushed = false;
$('.editbtn').click( function()
{
// push to browser history, so back button will close the editor
// and not navigate away from site
if (ismobilescreen && !backhistory_pushed)
{
window.history.pushState('forward', null, window.location);
backhistory_pushed = true;
}
}
Then:
if (window.history && window.history.pushState)
{
$(window).on('popstate', function()
{
if (ismobilescreen && backhistory_pushed && $('.editor').is(':visible'))
{
// hide editor window (we initiate a click on the cancel button)
$('.editor:visible .cancelbtn').click();
backhistory_pushed = false;
}
});
}
Results in:
User opens editor DIV, the history state is saved.
User hits back button, history state is taken into account.
Users stays on page!
Instead of navigating back, the editor DIV is closed.
One issue: If you use a "Cancel" button on your DIV and this hides the editor, then the user has to click the mobile's back button two times to go back to the previous URL.
To solve this problem you can call window.history.back(); to remove the history entry by yourself which actually deletes the state as requested.
For example:
$('.btn-cancel').click( function()
{
if (ismobilescreen && backhistory_pushed)
{
window.history.back();
}
}
Alternatively you could push a URL into the history that holds an anchor, e.g. #editor and then push to history or not if the anchor exists in the recent URL or not.
Is there a way to provide a hook such as onHistoryBack? I'm currently using history.js with
History.Adapter.bind (window, 'statechange', function () {});
But I have no way to ask if user presed history.back() or if it's result of a History.pushState() call.. any idea?
The way I did this was to set a variable set to true on each click on the actual site. The statechange event would then check this variable. If it was true, they had used a link on the site. If it was false, they had clicked the browsers back button.
For example:
var clicked = false;
$(document).on('click','a',function(){
clicked = true;
// Do something
});
History.Adapter.bind (window, 'statechange', function () {
if( clicked ){
// Normal link
}else{
// Back button
}
clicked = false;
});
Hope that helps :)
All of the states are stored in History.savedStates. Each time back is pushed another state is added. So in theory you could test History.savedStates to see if History.savedStates[History.savedStates.length - 2] == currentState. That would indicate the user went from step a, to step b, back to step a. However the user could get there other ways than the back button - so you may need to use this in combination with user events.
You can also use the History.getStateByIndex method to return a saved state.