I am trying to put the Back Button Redirect Script function to good use. I have a plugin which plays background (user-initiated) music on my site. As long as the user clicks forward, the music streams continuously and nearly uninterrupted from page to page, without restarting. If the user clicks the back button (or refreshes), the music stops and they must manually press play to restart the stream. The author says they have no way to resolve it. I'm not giving up just yet.
My thought is, why not use JavaScript to record the browser's previous page URL, then capture the back button trigger and send the user "forward" to that URL, thus keeping the music stream intact while honoring the user's desire to go back a page?
Conceptually, being a supernoob at JavaScript, I patched this together from different sources on here and codingbeautydev...
$(window).bind("onpopstate", function (e) {
const previousPage = document.getElementById("previous-page");
previousPage.textContent = document.referrer;
window.history.pushState({ page: 1 }, "", "");
window.onpopstate = function (event) {
if (event) {
window.location.href = previousPage;
}
};
});
My first thought is there are surely some syntex errors in there at my doing and potentially much more that need be modified, but I'm hoping someone can easily touch up my rough sketch. Additionally, beyond making this work, I see the limits of this allowing only 1-page of history, and I'm curious if there's a way to nest it into a stack of a few pages to which could be visited in reverse order, all the while moving "forward". First things first though, then on to bigger and better.
Thanks guys! 😀
Mark
You cannot change the default behavior of the browsers's back or forward button unless your app uses URL hashes to navigate, but from my understanding of your question the user actually goes from say .com/paper to .com/boxes and not .com/paper#page1 to .com/paper#page2.
One possible option you could try is using the following (from here):
window.addEventListener('pageshow', function (event) {
if (event.persisted || performance.getEntriesByType("navigation")[0].type === 'back_forward') {
// User got here from using the Back or Forward button
}
});
This will trigger when the user got on the page this code runs on using the back or forward window button, also if the user goes from /boxes back to /paper.
You can try to save the current state of the music playing on the background (which song, timestamp, audio level, etc) in local storage (at max) every second or so, and get the stored values inside the function above to continue the music the user was last listening to when he left the previous page. Not the most elegant solution, but all I think of right now that might actually work.
Edit:
The code you requested. Chrome & Safari will block/ignore it due to history manipulation, except when an user interacts with the page first. It's not how history should be used. Don't use it in production, but play with it all you want. Also, here's an simple example how history can be used.
window.history.pushState({}, '', window.location.pathname);
let previousPage = document.referrer;
window.addEventListener('popstate', (e) => {
window.location.assign(previousPage)
});
Related
So lately I have been learning JS and trying to interact with webpages, scraping at first but now also doing interactions on a specific webpage.
For instance, I have a webpage that contains a button, I want to press this button roughly every 30 seconds and then it refreshes (and the countdown starts again). I wrote to following script to do this:
var klikCount = 0;
function getPlayElement() {
var playElement = document.querySelector('.button_red');
return playElement;
}
function doKlik() {
var playElement = getPlayElement();
klikCount++;
console.log('Watched ' + klikCount);
playElement.click();
setTimeout(doKlik, 30000);
}
doKlik()
But now I need to step up my game, and every time I click the button a new window pops up and I need to perform an action in there too, then close it and go back to the 'main' script.
Is this possible through JS? Please keep in mind I am a total javascript noob and not aware of a lot of basic functionality.
Thank you,
Alex
DOM events have an isTrusted property that is true only when the event has been generated by the user, instead of synthetically, as it is for the el.click() case.
The popup is one of the numerous Web mechanism that works only if the click, or similar action, has been performed by the user, not the code itself.
Giving a page the ability to open infinite amount of popups has never been a great idea so that very long time ago they killed the feature in many ways.
You could, in your own tab/window, create iframes and perform actions within these frames through postMessage, but I'm not sure that's good enough for you.
Regardless, the code that would work if the click was generated from the user, is something like the following:
document.body.addEventListener(
'click',
event => {
const outer = open(
'about:blank',
'blanka',
'menubar=no,location=yes,resizable=no,scrollbars=no,status=yes'
);
outer.document.open();
outer.document.write('This is a pretty big popup!');
// post a message to the opener (aka current window)
outer.document.write(
'<script>opener.postMessage("O hi Mark!", "*");</script>'
);
// set a timer to close the popup
outer.document.write(
'<script>setTimeout(close, 1000)</script>'
);
outer.document.close();
// you could also outer.close()
// instead of waiting the timeout
}
);
// will receive the message and log
// "O hi Mark!"
addEventListener('message', event => {
console.log(event.data);
});
Every popup has an opener, and every different window can communicate via postMessage.
You can read more about window.open in MDN.
User can achieve this by running 'Reload Window' available via editor's 'Command Palette'. That however from an extension authoring point of view is not as straight forward as prompting the user right away.
Desired outcome is to take current implementation in this pull request from [screenshot]
to [screenshot]
.
Once you prompt the user to confirm using vscode.window.showInformationMessage(...) to confirm, you can run vscode.commands.executeCommand("workbench.action.reloadWindow") to reload the window.
Accepted answer posted by user ajshort was crucial to figuring out how to call arbitrary editor commands, however, tying it together with user action still wasn't clear to me.
See function bellow for resolving the user action (click).
/** Prompts user to reload editor window in order for configuration change to take effect. */
function promptToReloadWindow() {
const action = 'Reload';
vscode.window
.showInformationMessage(
`Reload window in order for change in extension \`${EXTENSION_ID}\` configuration to take effect.`,
action
)
.then(selectedAction => {
if (selectedAction === action) {
vscode.commands.executeCommand('workbench.action.reloadWindow');
}
});
}
I'm developing a social network website and I'm working with php/mysql/jquery and I want users to have the most user-friendly website experience. That's why I try to avoid a save (submit) button at all. So, when they're on their own profile and enter information like what languages they speak or where do they live, I want to auto-create a page entry, if it doesn't exists already.
For example if somebody speaks german and that entry isn't available in the database yet (it's called pages with rows title,text,date_added). This is most likely comperable with a Facebook page you can create on Facebook, but this should be about everything (languages, companies, locations, activities).
So my specific problem is now, how to implement this feature the most smartest way. I mean on what event should this save be triggered, because I obviously can't save a new entry on every letter that has been typed. I've already implemented an autocomplete function, so they can choose from entries that already exist, but as mentioned I want them to create new entries (pages), too. Most preferably without "flooding" the database with new entries.
The things I thought of:
When a user switches from one input to another and input has been
made. Can this even be registered by JQuery? But here's the
question, what if he leaves the page without switching to another
input.
Save all data only then if he leaves the current page. I think
there is this onunload function in JQuery.
Get confirmation from the user: "This entry hasn't been made yet.
Do you want to save this entry?". But when should I ask this? I
can't predict when the user is finished with typing.
You could do the following:
1) Every time the user presses a button or clicks inside an input field - register a timeout using setTimeout (if the timeout was already set - clear it). The actual timeout in ms is up to you, it depends on the users and the data they are entering - maybe half a second or a whole second will be fine.
2) Inside the timeout - submit the data to the backend
3) And as you mentioned - if the user attempts to leave the page with a timeout waiting to happen - ask him if he really wants to leave. Keep in mind you are limited in what you can do from the unload handler itself, so it is best just to return him to the page and invoke the save then (as AJAX will probably not work from that handler, or at least it will not be consistent in all browsers).
var to = null;
$(':input').on('click contextmenu keyup blur', function () {
if(to) { clearTimeout(to); }
to = setTimeout(function () {
$.ajax(...send data to server...).done(function () {
// possibly show a message indicating the data was saved
})
}, 1000);
});
window.onbeforeunload = function() {
if(to) {
return "Are you sure you want to navigate away, you have unsaved data?";
}
}
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.
I am needing to detect when a user is inactive (not clicking or typing) on the current page for more than 30 minutes.
I thinking it might be best to use event blubbling attached to the body tag and then just keep resetting a timer for 30 minutes, but I'm not exactly sure how to create this.
I have jQuery available, although I'm not sure how much of this will actually use jQuery.
Edit: I'm more needing to know if they are actively using the site, therefore clicking (changing fields or position within a field or selecting checkboxes/radios) or typing (in an input, textarea, etc). If they are in another tab or using another program, then my assumption is they are not using the site and therefore should be logged out (for security reasons).
Edit #2: So everyone is clear, this is not at all for determining if the user is logged in, authenticated or anything. Right now the server will log the user out if they don't make a page request within 30 minutes. This functionality to prevent the times when someone spends >30 minutes filling in a form and then submitting the form only to find out that they haven't been logged out. Therefore, this will be used in combination with the server site to determine if the user is inactive (not clicking or typing). Basically, the deal is that after 25 minutes of idle, they will be presented with a dialog to enter their password. If they don't within 5 minutes, the system automatically logs them out as well as the server's session is logged out (next time a page is accessed, as with most sites).
The Javascript is only used as a warning to user. If JavaScript is disabled, then they won't get the warning and (along with most of the site not working) they will be logged out next time they request a new page.
This is what I've come up with. It seems to work in most browsers, but I want to be sure it will work everywhere, all the time:
var timeoutTime = 1800000;
var timeoutTimer = setTimeout(ShowTimeOutWarning, timeoutTime);
$(document).ready(function() {
$('body').bind('mousedown keydown', function(event) {
clearTimeout(timeoutTimer);
timeoutTimer = setTimeout(ShowTimeOutWarning, timeoutTime);
});
});
Anyone see any problems?
Ifvisible.js is a crossbrowser lightweight solution that does just that. It can detect when the user switches to another tab and back to the current tab. It can also detect when the user goes idle and becomes active again. It's pretty flexible.
You can watch mouse movement, but that's about the best you're going to get for indication of a user still being there without listening to the click event. But there is no way for javascript to tell if it is the active tab or if the browser is even open. (well, you could get the width and height of the browser and that'd tell you if it was minimized)
I just recently did something like this, albeit using Prototype instead of JQuery, but I imagine the implementation would be roughly the same as long as JQuery supports custom events.
In a nutshell, IdleMonitor is a class that observes mouse and keyboard events (adjust accordingly for your needs). Every 30 seconds it resets the timer and broadcasts an state:idle event, unless it gets a mouse/key event, in which case it broadcasts a state:active event.
var IdleMonitor = Class.create({
debug: false,
idleInterval: 30000, // idle interval, in milliseconds
active: null,
initialize: function() {
document.observe("mousemove", this.sendActiveSignal.bind(this));
document.observe("keypress", this.sendActiveSignal.bind(this));
this.timer = setTimeout(this.sendIdleSignal.bind(this), this.idleInterval);
},
// use this to override the default idleInterval
useInterval: function(ii) {
this.idleInterval = ii;
clearTimeout(this.timer);
this.timer = setTimeout(this.sendIdleSignal.bind(this), ii);
},
sendIdleSignal: function(args) {
// console.log("state:idle");
document.fire('state:idle');
this.active = false;
clearTimeout(this.timer);
},
sendActiveSignal: function() {
if(!this.active){
// console.log("state:active");
document.fire('state:active');
this.active = true;
this.timer = setTimeout(this.sendIdleSignal.bind(this), this.idleInterval);
}
}
});
Then I just created another class that has the following somewhere in it:
Event.observe(document, 'state:idle', your-on-idle-functionality);
Event.observe(document, 'state:active', your-on-active-functionality)
Ifvisible is a nice JS lib to check user inactivity.
ifvisible.setIdleDuration(120); // Page will become idle after 120 seconds
ifvisible.on("idle", function(){
// do something
});
Using jQuery, you can easily watch mouse movement, and use it to set a variable indicating activity to true, then using vanilla javascript, you can check this variable every 30 minutes (or any other interval) to see if its true. If it's false, run your function or whatever.
Look up setTimeout and setInterval for doing the timing. You'll also probably have to run a function every minute or so to reset the variable to false.
Here my shot:
var lastActivityDateTime = null;
function checkActivity( )
{
var currentTime = new Date();
var diff = (lastActivityDateTime.getTime( ) - currentTime.getTime( ));
if ( diff >= 30*60*1000)
{
//user wasn't active;
...
}
setTimeout( 30*60*1000-diff, checkActivity);
}
setTimeout( 30*60*1000, checkActivity); // for first time we setup for 30 min.
// for each event define handler and inside update global timer
$( "body").live( "event_you_want_to_track", handler);
function handler()
{
lastActivityDateTime = new Date();
// rest of your code if needed.
}
If it's a security issue, doing this clientside with javascript is absolutely the wrong end of the pipe to be performing this check. The user could easily have javascript disabled: what does your application do then? What if the user closes their browser before the timeout. do they ever get logged out?
Most serverside frameworks have some kind of session timeout setting for logins. Just use that and save yourself the engineering work.
You can't rely on the assumption that people cannot log in without javascript, therefore the user has javascript. Such an assumption is no deterrent to any determined, or even modestly educated attacker.
Using javascript for this is like a security guard handing customers the key to the bank vault. The only way it works is on faith.
Please believe me when I say that using javascript in this way (and requiring javascript for logins!!) is an incredibly thick skulled way to engineer any kind of web app.
Without using JS, a simpler (and safer) way would simply be to have a lastActivity timestamp stored with the user's session and checking it on page load. Assuming you are using PHP (you can easily redo this code for another platform):
if(($_SESSION['lastAct'] + 1800) < time()) {
unset($_SESSION);
session_destroy();
header('Location: session_timeout_message.php');
exit;
}
$_SESSION['lastAct'] = time();
and add this in your page (optional, the user will be logged out regardless of if the page is refreshed or not (as he logs out on next page log)).
<meta http-equiv="refresh" content="1801;" />
You can add and remove classes to the document depending on the user active status.
// If the window is focused, a mouse wheel or touchmove event is detected
$(window).on('focus wheel touchmove', function() {
$( 'html' ).addClass('active').removeClass('inactive');
});
// If the window losses focus
$(window).on('blur', function() {
$( 'html' ).addClass('inactive').removeClass('active');
});
After that, you can check every while if the html has the "active" class and send an AJAX request to check the session status and perform the action you need:
setInterval( function() {
if ( $( 'html' ).hasClass('active') ) {
//Send ajax request to check the session
$.ajax({
//your parameters here
});
}
}, 60000 ); /* loops every minute */
If your concern is the lost of information for the user after a login timeout; another option would be to simply store all the posted information upon the opening of a new session (a new session will always be started when the older session has been closed/scrapped by the server) when the request to a page is made before re-routing to the logging page. If the user successfully login, then you can use this saved information to return the user back to where he was. This way, even if the user walk away a few hours, he can always return back to where he was after a successful login; without losing any information.
This require more work by the programmer but it's a great feature totally appreciated by the users. They especially appreciate the fact that they can fully concentrate about what they have to do without stressing out about potentially losing their information every 30 minutes or so.