Document ready event of another location opened using window.location in javascript - javascript

I am using below code to open a link on button click. The link is pointing to a Controller method responsible for downloading some Excel file.
// Button to download table data
$("#btnDownloadCIRResults").click(function (e) {
var All_Recs = $("#cbShowAllRecords").prop("checked") ? "YES" : "NO";
DisplayStatusMessageWind("Downloading report, please wait...", MessageType.Info, true, false);
// DownloadCIRemediationTable(string AllRecords)
window.location = '/AskNow/DownloadCIRemediationTable?AllRecords=' + All_Recs;
DisplayStatusMessageWind("Report downloaded successfully.", MessageType.Success, false, true, 1000);
e.preventDefault();
});
The Controller method queries a DB table, converts it to an Excel workbook and returns a file as download result. All is working fine and as expected, except, since this is a time consuming process, I just want to improve on user experience and update this code to show some wait message while the file is being downloaded.
The DisplayStatusMessageWind() method shows a wait message. However, it doesn't know or care about the load complete event of the window.location = '/AskNow/DownloadCIRemediationTable?AllRecords=' + All_Recs; code.
How can I make the completion message appear only after the file download is completed:
DisplayStatusMessageWind("Report downloaded successfully.", MessageType.Success, false, true, 1000);

By assigning a new location with window.location = "<NEWURL>"; you're requesting asynchronously to replace the current page. What will happen, is that the next line is immediately executed (DisplayStatusMessage()). When all events are handled, the page will finally be replaced. The new page (URL) will load and you'll have no control whatsoever about how or what will happen next.
What you should do is use window.open("<NEWURL>", '_blank') MOZ and then on the new page send a signal via localStorage, which can be read and written by all pages of the same domain. These are some hints, to write the actual code is your job.
On this page, in on("click") event:
// local scope
var ukey;
// polling function
function waitOtherIsReady()
{
if (localStorage.getItem(ukey) === true)
{
// other page experienced ready event
localStorage.removeItem(ukey); // clean-up
// TODO: do your stuff
} else {
setTimeout(waitOtherIsReady, 500);
}
}
// create unique key and deposit it in localStorage
ukey = "report_" + Math.random().toString(16);
localStorage.setItem(ukey, false);
// pass key to other page
window.open("URL?ukey=" + ukey, "_blank");
// start polling until flag is flipped to true
setTimeout(waitOtherIsReady, 500);
On the other page:
$(() => {
// get ukey from URL
var ukey = new URL(window.location.href).searchParams.get("ukey");
// page is now ready, flip flag to signal ready event
localStorage.setItem(ukey, true);
});

Related

Chrome Extension: Clear Chrome Local Storage on Page Reload (not on Update)

I'm working on a Chrome Extension that will sort a DOM structure. On the extension popup I'm using a button that will activate / desactivate the sort. In order to save the state of my button I'm saving via Chrome Local Storage the "state" of my button this way:
function save_button_state() {
var buttonStateText = $("#js-toggleSorting").html();
var buttonStateAttribute = $("#js-toggleSorting").attr("data-click-state");
var sortMessage = $(".message").html();
chrome.storage.local.set(
{
buttonStateText: buttonStateText,
buttonStateAttribute: buttonStateAttribute,
sortMessage: sortMessage,
},
function () {
console.log(
`Saved State is: ${buttonStateText} and Saved Attribute is: ${buttonStateAttribute} and Saved Message is: ${sortMessage}`
);
}
);
}
That save the dom node and keep the information in order to save it when the popup is closed. Then, in order to get that info back from the local storage I'm using this function:
function get_button_state() {
chrome.storage.local.get(
["buttonStateText", "buttonStateAttribute", "sortMessage"],
function (data) {
$(".message").html(data.sortMessage);
$("#js-toggleSorting").html(data.buttonStateText);
$("#js-toggleSorting").attr(
"data-click-state",
data.buttonStateAttribute
);
console.log(
`Get State is ${data.buttonStateText} and Get Attribute is ${data.buttonStateAttribute}`
);
}
);
}
And then when the document is ready I'm processing the button onclick event changing the dom from "Sorting" to "Not Sorting" this way:
$(document).ready(() => {
get_button_state();
//Some Code to pass parameters from the extension to a content script
$("#js-toggleSorting").on("click", function () {
$(".message").html("");
if ($(this).attr("data-click-state") == 1) {
$(this).attr("data-click-state", 0);
$(this).html("SORT INCOMING CHATS");
$(".message").append("<p>STATUS: NOT SORTING CHATS</p>");
sortFunction(false);
} else {
$(this).attr("data-click-state", 1);
$(this).html("STOP SORTING INCOMING CHATS");
$(".message").append("<p>STATUS: SORTING CHATS</p>");
sortFunction(true);
}
save_button_state();
});
});
Then in the Chrome Extension background js file I'm trying to clear the local storage when I reload the page (the same page, with the same URL):
chrome.tabs.onUpdated.addListener(function (changeInfo) {
if (changeInfo.url === undefined) {
chrome.storage.local.clear();
}
});
But apparently this will not only clear the local storage when I reload the page but when something change in it causing a lot of buggy behavior on the popup (basically changing very randomly the text of the button when the user interact/click on it everytime they open the popup). My question is which is the right way to clear the local storage just when the page is reloaded / refreshed. I'm sorry if this seems pretty obvious but I'm new on the Chrome Extension development world.

I want to display a loader while I download a file through a C# controller method

I want to display a loader while I download a file. I am calling a method "DownloadFile" in my controller, it works properly. I tried to use the "onbeforeunload" Javascript trigger to display my loader. It works super fine on the all website but I have a problem with the download. It call a method in a controller, so the window expects a View to be returned. I am returning a File() so the loader is looping to the infinite and never stops, even when the file is downloaded !
I have tried several Javascript/Jquery triggers but none of them worked. Do you have any idea of how to trigger the end of a C# method execution to stop the display of the loader ?
Javascript :
$(window).on('beforeunload', function () {
displayBusyIndicator();
});
Display busy indicator :
function displayBusyIndicator() {
$("#AjaxLoader").show();
}
function hideBusyIndicator() {
$("#AjaxLoader").hide();
}
C# :
public async System.Threading.Tasks.Task<ActionResult> DownloadCertificates([FromQuery]string certificateName)
{
// instantiate a CertficatesService Class which contains key vault client connexion
CertificatesService service = new CertificatesService();
// Instantiates download service
Download download = new Download();
// Instantiates memory stream object
System.IO.MemoryStream ms = new System.IO.MemoryStream();
var user = await Dynamic365APICalls.getClientInformation(Request.Headers["X-Ms-Client-Principal-Name"], config.Value.Dynamic365ApiUrl, config.Value.EspaceClientSubKey);
if (user.Value[0].Mobilephone.ToString() != null)
{
var SASToken = SASManager.GetAccountSASToken(config.Value.StorageConnection);
// Gets the client certificates using session variable stored during landing page connexion
// which contains the company name of the client
CertificatePasswordModel certificatePassword = await service.GetCertificatePassword(certificateName, config.Value.ApiKeyVaultUrl, config.Value.EspaceClientSubKey);
service.SendPasswordBySMS(user.Value[0].Mobilephone.ToString(), certificatePassword.Password, config.Value.TwilioAccount, config.Value.TwilioAuthToken, config.Value.TwilioAccountPhoneNumber);
var fileStream = await download.DownloadFile(SASToken, config.Value.AccountName, certificateName, config.Value.StorageConnection, config.Value.PFXContainer);
await fileStream.DownloadToStreamAsync(ms);
System.IO.Stream blobStream = fileStream.OpenReadAsync().Result;
return File(blobStream, fileStream.Properties.ContentType, certificateName + ".pfx");
}
else
{
throw new System.ArgumentException("Please enter your mobile phone to access this service");
}
}
I expect the loader to be diplayed during the download and to stop when download ends

Why does the code inside xmlhttprequest work when async flag is set to false but not when it is set to true

I am fairly new to the use of javascript and would appreciate any help.
I have an application where the browser must use xmlhttprequest to receive a response from the server (true/false for testing purposes) and based on the response, the client will open a file selection dialog for the user to select a local file for uploading.
When I create the XMLHttpRequest with the async flag set to FALSE, when the client receives a "true" response from the server, a file selection dialog box opens (for both Chrome, IE).
When I create the XMLHttpRequest with the async flag set to TRUE ( as recommended), when the client receives a "true" response from the server, the same code path is followed, however a file selection dialog box never opens and no errors are displayed in the debuggers for Chrome, HOWEVER it still work in IE.
Here is the code:
...
// user has clicked button to upload a file
$scope.uploadFile = function () {
request = new XMLHttpRequest();
request.open("GET", "some-url", true);// false
request.onreadystatechange = $scope.checkUploadPermissions;
request.send();
}
// the callback
// If the user has permission (based on various factors not shown here)
// we open the dialog
// Otherwise we inform them that they are not allowed
$scope.checkUploadPermissions = function () {
// simplified for brevity
if (request.readyState == 4 && request.status == 200) {
// for this sample, we are only checking if the server returned true/false
var hasPerms = request.responseText;
if (hasPerms === "true") {
$scope.openFileSelector();
}
else {
alert("You do not have permission to upload a file.");
}
}
}
// if the user has permission to proceed, we trigger a click on a hidden element
$scope.openFileSelector = function () {
angular.element("#presentUpload").trigger("click");
}
...
I would like to reiterate that this code works perfectly when the async flag set to FALSE but not when it is set to TRUE.
How can I have this work properly when setting the flag to TRUE.
Thank you in advance.
File upload is a feature in a browser that can only be initiated as the direct result of a user action (usually while your JS code is processing a mouse click or keyboard event). It cannot be initiated asynchronously by a timer or via some async callback.
So, when you set your first Ajax call to sync, then your JS click on a hidden element appears to the browser as it is still inside the hidden element click event and thus the upload is allowed. When your first Ajax call is set to async, the user click event is over by the time you try to click the hidden element and the browser will not bring up the upload dialog.
See Trigger click on input=file on asynchronous ajax done() for details.

How to logout my application when I closed the window?

In my chat application i am having the logout button and it works fine.
Now I need to logout the application when I closed the browser window also..How can I achieve this...
Thanks in advance...
There is no exact way to do this with the clientside. There is no event that is fired when the page is exited. It should be done with the Session End event on the server.
You can try to use onbeforeunload or unload, but race conditions will prevent that from happening. AND they do not fire for browsers crashing, lost internet connection, etc.
I dealt with this issue recently in my angularJS app - The main issue was that I don't want to log you out if you refresh, but I do want to if you close the tab.. Ajax requests with onbeforeunload/onunload aren't guaranteed to wait for response, so here is my solution:
I set a sessionStorage cookie on login that is just a bool - set to true when I get login response
sessionStorage.setItem('activeSession', 'true');
Obviously, on logout, we set this flag to false
Either when controller initializes or using window.onload (in my app.js file) - I check for this activeSession bool.. if it is false, I have this small if statement - where if conditions are met I call my logout method ONLOAD instead of onunload
var activeSession = sessionStorage.activeSession;
if (sessionStorage.loggedOutOnAuth) {
console.log('Logged out due to expiry already')
}
else if (!activeSession) {
sessionStorage.loggedOutOnAuth = true;
_logout()
}
Basically, the "loggedOutAuth" bool let's me know that I just expired you on page load due to the absence of an activeSession in sessionStorage so you don't get stuck in a loop
This was a great solution for me since I didn't want to implement a heartbeat/websocket
Add your logout code to the on onunload event.
window.onunload = function () {
//logout code here...
}
In JQuery you can use the .unload() function. Remember that you don't have much time so you may send the Ajax request but the result may not reach the client.
Another trick is to open a small new window and handle the logout there.
window.open("logout url","log out","height=10,width=10,location=no,menubar=no,status=no,titlebar=no,toolbar=no",true);
If you want to disable closing the window (or at least warn the user), you can use this code:
window.onbeforeunload = function(event) {
//if you return anything but null, it will warn the user.
//optionally you can return a string which most browsers show to the user as the warning message.
return true;
}
Another trick is to keep pinging the client every few seconds. If no reply comes back, assume the user has closed the window, browser has crashed or there is a network issue that ended the chat session anyway. On the client side, if you don't receive this ping package, you can assume that network connection or server has a problem and you can show the logout warning (and optionally let the user login again).
Some websites are using the following script to detect whether window is closed or not.
if(window.screenTop > 10000)
alert("Window is closed");
else
alert("Window stillOpen");
You need to add the correct action instead of alert()
also take a look HERE - I think this is somthing you need to detect the window closing
I got the Solution by,
window.onunload = function () {
//logout code here...
}
Thanks for all who supported me...
Another approach is some sort of "keepalive": the browser page "pings" the server with a small ajax request every minute or so. If the server doesn't get the regular pings, the session is closed and can no longer be used.
As an optimization, the pings can be skipped if we have made another request to the server in the interim.
Advantages:
still works with multiple windows open
no problem with F5 / refresh
can provides some usage statistics to the server
Disadvantages:
when the window is closed, there is a delay before the user is logged out
uses a little network bandwidth
additional load on the server
users might have concerns about the page constantly "phoning home"
more difficult to implement
I've never actually done this in a web app, and not sure if I would; just putting it out there as an alternative. It seems like a good option for a chat app, where the server does need to know if you are still there.
Rather than polling / pinging, another possibility is to keep a "long running request" open while the page is open. A chat app needs some such socket to receive new messages and notifications. If the page is closed, the socket is closed too, and the server can notice that it has been closed. It then waits a brief time for the client to establish a new socket, and if it doesn't we assume the page is closed and delete the session. This would require some slightly unusual software on the server.
I was with this problem here and I come with a different solution:
checkSessionTime();
$interval(checkSessionTime, 2000);
function checkSessionTime() {
var now = (new Date()).getTime();
if (!$localStorage.lastPing) {
$localStorage.lastPing = now;
}
if ($localStorage.lastPing < now - 5000) {
$localStorage.lastPing = undefined;
AuthService.logout();
} else {
$localStorage.lastPing = now;
}
}
I like this solution cause it doesnt add overhead pinging the server nor rely on the window unload event. This code was put inside the $app.run.
I am using angular with a JWT auth, this way to me to log out just mean to get rid of the auth token. However, if you need to finish up the session server-side you can just build the Auth service to do one ping when finishing the session instead of keep pinging to maitain session alive.
This solutionsolves my case cause my intetion is just to prevent unwanted users to access someones account when they closed the tab and went away from the PC.
After lots of search I wrote the below customized code in javascript and server side code for session kill in c#.
The below code is extended in case of same website is open in multiple tabs so the session is alive till one tab of website is open
//Define global varible
var isCloseWindow = false;
//Jquery page load function to register the events
$(function () {
//function for onbeforeuload
window.onbeforeunload = ConfirmLeave;
//function for onload
window.onload = ConfirmEnter;
//mouseover for div which spans the whole browser client area
$("div").on('mouseover', (function () {
//for postback from the page make isCloseWindow global varible to false
isCloseWindow = false;
}));
//mouseout event
$("div").on('mouseout', (function () {
//for event raised from tabclose,browserclose etc. the page make isCloseWindow global varible to false
isCloseWindow = true;
}));
});
//Key board events to track the browser tab or browser closed by ctrl+w or alt+f4 key combination
$(document).keydown(function (e) {
if (e.key.toUpperCase() == "CONTROL") {
debugger;
isCloseWindow = true;
}
else if (e.key.toUpperCase() == "ALT") {
debugger;
isCloseWindow = true;
}
else {
debugger;
isCloseWindow = false;
}
});
function ConfirmEnter(event) {
if (localStorage.getItem("IsPostBack") == null || localStorage.getItem("IsPostBack") == "N") {
if (localStorage.getItem("tabCounter") == null || Number(localStorage.getItem("tabCounter")) == 0) {
//cookie is not present
localStorage.setItem('tabCounter', 1);
} else {
localStorage.setItem('tabCounter', Number(localStorage.getItem('tabCounter')) + 1);
}
}
localStorage.setItem("IsPostBack", "N");
}
function ConfirmLeave(event) {
if (event.target.activeElement.innerText == "LOGOUT") {
localStorage.setItem('tabCounter', 0);
localStorage.setItem("IsPostBack", "N");
} else {
localStorage.setItem("IsPostBack", "Y");
}
if ((Number(localStorage.getItem('tabCounter')) == 1 && isCloseWindow == true)) {
localStorage.setItem('tabCounter', 0);
localStorage.setItem("IsPostBack", "N");
**Call Web Method Kill_Session using jquery ajax call**
} else if (Number(localStorage.getItem('tabCounter')) > 1 && isCloseWindow == true) {
localStorage.setItem('tabCounter', Number(localStorage.getItem('tabCounter')) - 1);
}
}
//C# server side WebMethod
[WebMethod]
public static void Kill_Session()
{
HttpContext.Current.Session.Abandon();
}
For this issue I tried 2 solutions: window.onbeforeunload event and sessionStorage
Since window.onbeforeunload is not only for closing the browser but also redirect, tab refresh, new tab, it was not a robust solution. Also there are cases which the event does not happen: closing the browser through the command line, shutting down the computer
I switched to using sessionStorage. When the user logs in I set a sessionStorage variable to 'true'; when the application is loaded I would check to see if this variable is there, otherwise I would force the user to log in. However I need to share the sessionStorage variable across tabs so that a user is not forced to log in when they open a new tab in the same browser instance, I was able to do this by leveraging the storage event; a great example of this can be found here
tabOrBrowserStillAliveInterval;
constructor() {
// system should logout if the browser or last opened tab was closed (in 15sec after closing)
if (this.wasBrowserOrTabClosedAfterSignin()) {
this.logOut();
}
// every 15sec update browserOrTabActiveTimestamp property with new timestamp
this.setBrowserOrTabActiveTimestamp(new Date());
this.tabOrBrowserStillAliveInterval = setInterval(() => {
this.setBrowserOrTabActiveTimestamp(new Date());
}, 15000);
}
signin() {
// ...
this.setBrowserOrTabActiveTimestamp(new Date());
}
setBrowserOrTabActiveTimestamp(timeStamp: Date) {
localStorage.setItem(
'browserOrTabActiveSessionTimestamp',
`${timeStamp.getTime()}`
);
}
wasBrowserOrTabClosedAfterSignin(): boolean {
const value = localStorage.getItem('browserOrTabActiveSessionTimestamp');
const lastTrackedTimeStampWhenAppWasAlive = value
? new Date(Number(value))
: null;
const currentTimestamp = new Date();
const differenceInSec = moment(currentTimestamp).diff(
moment(lastTrackedTimeStampWhenAppWasAlive),
'seconds'
);
// if difference between current timestamp and last tracked timestamp when app was alive
// is more than 15sec (if user close browser or all opened *your app* tabs more than 15sec ago)
return !!lastTrackedTimeStampWhenAppWasAlive && differenceInSec > 15;
}
How it works:
If the user closes the browser or closes all opened your app tabs then after a 15sec timeout - logout will be triggered.
it works with multiple windows open
no additional load on the server
no problem with F5 / refresh
Browser limitations are the reason why we need 15sec timeout before logout. Since browsers cannot distinguish such cases: browser close, close of a tab, and tab refresh. All these actions are considered by the browser as the same action. So 15sec timeout is like a workaround to catch only the browser close or close of all the opened your app tabs (and skip refresh/F5).
I posted this originally here but I will repost here for continuity.
There have been updates to the browser to better tack the user when leaving the app. The event 'visibilitychange' lets you tack when a page is being hidden from another tab or being closed. You can track the document visibility state. The property document.visibilityState will return the current state. You will need to track the sign in and out but its closer to the goal.
This is supported by more newer browser but safari (as we know) never conforms to standards. You can use 'pageshow' and 'pagehide' to work in safari.
You can even use new API's like sendBeacon to send a one way request to the server when the tab is being closed and shouldn't expect a response.
I build a quick port of a class I use to track this. I had to remove some calls in the framework so it might be buggy however this should get you started.
export class UserLoginStatus
{
/**
* This will add the events and sign the user in.
*/
constructor()
{
this.addEvents();
this.signIn();
}
/**
* This will check if the browser is safari.
*
* #returns {bool}
*/
isSafari()
{
if(navigator && /Safari/.test(navigator.userAgent) && /Chrome/.test(navigator.userAgent))
{
return (/Google Inc/.test(navigator.vendor) === false);
}
return false;
}
/**
* This will setup the events array by browser.
*
* #returns {array}
*/
setupEvents()
{
let events = [
['visibilitychange', document, () =>
{
if (document.visibilityState === 'visible')
{
this.signIn();
return;
}
this.signOut();
}]
];
// we need to setup events for safari
if(this.isSafari())
{
events.push(['pageshow', window, (e) =>
{
if(e.persisted === false)
{
this.signIn();
}
}]);
events.push(['pagehide', window, (e) =>
{
if(e.persisted === false)
{
this.signOut();
}
}]);
}
return events;
}
/**
* This will add the events.
*/
addEvents()
{
let events = this.setupEvents();
if(!events || events.length < 1)
{
return;
}
for(var i = 0, length = events.length; i < length; i++)
{
var event = events[i];
if(!event)
{
continue;
}
event[1].addEventListener(event[0], event[3]);
}
}
/**
*
* #param {string} url
* #param {string} params
*/
async fetch(url, params)
{
await fetch(url,
{
method: 'POST',
body: JSON.stringify(params)
});
}
/**
* This will sign in the user.
*/
signIn()
{
// user is the app
const url = '/auth/login';
let params = 'userId=' + data.userId;
this.fetch(url, params);
}
/**
* This will sign out the user.
*/
signOut()
{
// user is leaving the app
const url = '/auth/logout';
let params = 'userId=' + data.userId;
if(!('sendBeacon' in window.navigator))
{
// normal ajax request here
this.fetch(url, params);
return;
}
// use a beacon for a more modern request the does not return a response
navigator.sendBeacon(url, new URLSearchParams(params));
}
}

Using gBrowser and observerService to find a tab on startup

I'm trying to create a firefox addon that will look for a certain page on startup and grab some info from it. I'm having trouble finding the page at load. Here's what I have so far:
var myfancyaddon = {
onLoad: function() {
var observerService = Components.classes["#mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
observerService.addObserver(function restored() {
observerService.removeObserver( restored, "sessionstore-windows-restored");
var browser = myfancyaddon.findMySite();
if (browser) {
alert("tree falling in the woods"); // THIS LINE NEVER RUNS
browser.contentWindow.addEventListener("load", function tab_loaded(){
browser.contentWindow.removeEventListener("load", tab_loaded(), false);
alert("mysite loaded!");
}, false);
}
}, "sessionstore-windows-restored", false);
},
findMySite: function() {
var browsers = gBrowser.browsers;
for ( var i = 0; i < browsers.length; i++ ) {
var browser = browsers[i];
if (!browser.currentURI.spec) continue;
if ( browser.currentURI.spec.match('^https?://(www\.)?mysite\.com/') ) return browser;
}
return null;
}
};
window.addEventListener("load", function ff_loaded(){
window.removeEventListener("load", ff_loaded, false); //remove listener, no longer needed
myfancyaddon.onLoad();
},false);
after some investigation it seems the currentURI.spec is "about:blank" for a short time before it becomes mysite.com. Any ideas?
Instead of filtering first and then adding the load listener, you could use gBrowser.addEventListener("DOMContentLoaded", myfunction, false); to listen for page loads on all tab documents and then only run your code based on the url.
https://developer.mozilla.org/en/XUL_School/Intercepting_Page_Loads
The "sessionstore-windows-restored" notification is sent when the tabs from the previous session have been restored and the loading in these tabs has been started (sometimes: "Don't load tabs until selected" option means that the load isn't even started in the background tabs). But the location of these tabs is still about:blank until the server is contacted because the address loaded might redirect or the server might be unreachable (meaning an internal redirect to about:neterror). Firefox only changes browser location when content is definitely being served from the new location.
It should be indeed better to intercept page loads rather than waiting for session restore.

Categories