I am wondering, if there is a way to know(using events) that the path is changed? I looked into both onpopstate & onhashchange. Both are not what I am looking for. My current requirements are, in a single page application:
When the url is changed from one to something else, I want a function to be called.
When the page is loaded initially, the function for the current page to be called.
Are these possible with native API's in the browser?
Listen for the load event, which fires when the page has finished loading:
function code(){
console.log('page loaded')
}
window.addEventListener('load', code)
To listen for pushState changes, you can override the default pushState behavior:
function trigger() {
console.log('state change')
}
var pushState = history.pushState;
history.pushState = function(state) {
if (typeof history.onpushstate == "function") {
history.onpushstate({state: state});
}
trigger()
return pushState.apply(history, arguments);
};
history.pushState(null, null, '/endpoint')
Related
I need to fire a function when the DOM ready or page view changed on single-page applications, here is my code and it works for the first time the user visits the site (DOM ready), but it didn't work when a user switches SPA pages (view changed).
I need a common method for most websites and SPA projects since this code needs to be executed on each of our client's websites(sort of like Google Analytics tracking code), and this code need executed every time when the end-users load or switch the pages. Is there any pure Javascript that can detect when the SPA changes pages?
function docReady(fn) {
if (document.readyState === "complete" || document.readyState === "interactive") {
setTimeout(fn, 1);
} else {
document.addEventListener("DOMContentLoaded", fn);
}
}
docReady(function() {
_AcodeInit();
});
function _AcodeInit()
{
...
}
Option 1: Listening to Changes in URL
As SPAs use routing, you could ideally listen to url changes.
One crude way of doing this is to keep polling using setInterval and track the changes to the url. If the url has changed you could run your handler. However this is wasteful.
Given the lack of options you can listen to all events which could cause transitions, (e.g. mouse clicks, keyboard Enter/Space key press etc.) and in the handler check if the url has changed. Use requestAnimationFrame so that we don't execute handler prematurely.
let currentUrl = location.href;
const checkPageTransition = () => {
requestAnimationFrame(() => {
if (currentUrl !== location.href) {
console.log("url changed");
}
currentUrl = location.href;
}, true);
};
document.body.addEventListener("click", checkPageTransition);
document.body.addEventListener("keyup", e => {
if (e.code === "Enter" || e.code === "Space") checkPageTransition()
});
Option 2: Listening to popstate event
If you are using modern SPAs, they would most likely be using history.pushState and history.popState to manage routing. We could then listen to the window.popstate event. But there are limitations to this event.
From: https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onpopstate
Note: Calling history.pushState() or history.replaceState() won't trigger a popstate event. The popstate event is only triggered by performing a browser action, such as clicking on the back button (or calling history.back() in JavaScript), when navigating between two history entries for the same document.
However we can monkey patch the history.pushState function so that it always fires the history.onpushstate function.
(function(history) {
var pushState = history.pushState;
history.pushState = function(state) {
if (typeof history.onpushstate == "function")
{
history.onpushstate({
state: state
});
}
return pushState.apply(history, arguments);
}
})(window.history);
You would need to similarly patch the replaceState function as well.
I have a situation where I need to force a reload() on the server-side for a timer.
The issue we're encountering is the browser history stack allows users to navigate back and forth without hitting the server and we're losing the time spent on a tracked task. Kind of hacky, I know.
I thought something like this might be a start (it isn't working):
window.addEventListener('pageload', function () {
console.log('Page loaded via back/for button');
});
The caveat, is I can only reload() when the browser back/forward functions were used. The URI will change constantly via the UI, in which case it already invokes a request to the server.
EDIT | I tried this as well (capture the back/next event and force a reload or AJAX request to server to capture context change)
(function($) {
window.addEventListener('popstate', function(event) {
console.log(event);
});
// Second solution...
window.addEventListener('popstate', function () {
console.log('URI changed');
});
const pushUrl = (href) => {
history.pushState({}, '', href);
window.dispatchEvent(new Event('popstate'));
};
})(jQuery);
Also not working, what am I missing?
Any ideas?
Is there a definitive JavaScript method for checking whether or not a web page has loaded completely? Completely, meaning 100% complete. HTML, scripts, CSS, images, plugins, AJAX, everything!
As user interaction can effect AJAX, let's assume there is no further user interaction with the page, apart from the initial page request.
What you're asking for is pretty much impossible. There is no way to determine whether everything has loaded completely. Here's why:
On a lot of webpages, AJAX only starts once the onload (or DOMReady) event fires, making the method of using the onload event to see if the page has loaded impossible.
You could theoretically tell if the webpage was performing an AJAX request by overriding window.XMLHttpRequest, but you still couldn't tell if plugins like Flash were still loading or not.
On some sites, like Twitter.com, the page only loads once and simply makes AJAX requests to the server every time the user clicks a link. How do you tell if the page has finished loading on a page like that?
In fact, the browser itself can't be completely certain whether the page has completely finished loading, or whether it's about to make more AJAX requests.
The only way to know for sure that everything loaded is to have every single piece of code on the page that loads something tell your code that it has finished once it loads.
A hacky, incomplete solution: You could try overriding XMLHttpRequest with a function that wraps the existing XMLHttpRequest and returns it. That would allow you to tell if a AJAX event is currently taking place. However, that solution wouldn't work for seeing if plugins are loaded, and you wouldn't be able to tell the difference between AJAX events that are triggered at page load and AJAX requests that happen periodically, like the ones on Stack Overflow that change the Stack Exchange icon on the top-left if you have new notifications.
Try something like this:
(function(oldHttpRequest){
// This isn't cross-browser, just a demonstration
// of replacing XMLHttpRequest
// Keep track of requests
var requests_running = 0;
// Override XMLHttpRequest's constructor
window.XMLHttpRequest = function() {
// Create an XMLHttpRequest
var request = new oldHttpRequest();
// Override the send method
var old_send = request.send;
request.send = function () {
requests_running += 1;
old_send.apply(request, arguments);
};
// Wait for it to load
req.addEventListener("load", function() {
requests_running -= 1;
}, false);
// Return our modified XMLHttpRequest
return request;
};
window.addEventListener("load", function() {
// Check every 50 ms to see if no requests are running
setTimeout(function checkLoad() {
if(requests_running === 0)
{
// Load is probably complete
}
else
setTimeout(checkLoad, 50);
}, 50);
}, false);
})(window.XMLHttpRequest)
The:
window.onload
event will fire at this point.
window.onLoad = function(){
//Stuff to do when page has loaded.
}
or
<body onLoad="functionCall()">
Basically ADW and Keith answer the question, but I would suggest not to use window.onload but:
if (window.addEventListener) {
window.addEventListener("load", myfunction, false);
} else {
window.attachEvent("onload", myfunction);
}
function myfunction() {
...
}
Using a combination of window.onload, document.readyState, and callbacks for AJAX requests, you should be able to do what you want. Simply make sure the window has loaded, the DOM is ready for manipulation, and keep track of AJAX requests.
For AJAX in particular, depending on how many requests you make: Increment a variable each time you make a request, and when the variable === the total amount of requests, fire a function. If you don't happen to know the amount of AJAX requests, but know which one would be last, simply have a callback function fire when it finishes.
When all is set and true, fire a final function to do what you want, knowing everything should be loaded.
In regards to Flash and Silverlight applications (not sure if window.onload or document.ready keeps track of those), you could also record the amount of data loaded withing the application, and when the loaded data === the total data, have the application fire a function or increment a variable to the page.
window.onload = function() {
var time = window.setInterval(function() {
if(document.readyState == "interactive") {
increment();
window.clearInterval(time);
}
}, 250);
}
var total = 10, current = 0;
var increment = function() {
current += 1;
if(current === total) { weAreDone(); }
}
function weAreDone() {
// Everything should be done!
}
Here is the non intrusive js function I scripted, using events on load. In this case, I fire events on js script load as this is my js autoloader function, but you can just add event on other items using the same principle. Provided this script looks after js scripts loaded in a dedicated div tag.
function scriptLoaded(e) {
var oLoadedScript = e.target || e.srcElement;
alert ('loaded : ' + oLoadedScript.src);
return false;
}
/**
* Import js lib and fire function ControlData on events
* #param js_librairies
* #returns {Boolean}
*/
function init(){
// lib import
// Locate js in the div
var myscript_location = document.getElementById('js_script_goes_here');
// DEBUG
if (undefined == myscript_location)
alert('div not found');
else
alert('found div : ' + myscript_location);
// to prevent js script from catching in dev mode
var force_js_reload = "?version=1" ;
for (var i=0; i < js_librairies.length ; ++i) {
var my_script = document.createElement('script');
my_script.defer = false;
my_script.src = relative_path + js_librairies[i] + force_js_reload ;
my_script.type = 'text/javascript';
// DEBUG
my_script.onload = scriptLoaded;
myscript_location.appendChild(my_script);
}
return false;
}
/**
* Start non intrusive js
* #param func
*/
function addLoadEvent(func) {
var oldonload = window.onload;
if (typeof window.onload != 'function') {
window.onload = func;
} else {
window.onload = function() {
if (oldonload) {
oldonload();
}
func();
};
}
}
//ONLOAD
addLoadEvent(init);
function r(f){/in/(document.readyState)?setTimeout(r,9,f):f()}
Courtesy: Smallest DOMReady code, ever - Dustin Diaz
Update: And for IE
function r(f){/in/.test(document.readyState)?setTimeout('r('+f+')',9):f()}
P.S: window.onload is a very different thing
I have a very very simple bit of code in my (test) Chrome extension:
function test()
{
alert("In test!");
}
chrome.tabs.onUpdated.addListener(function(tabid, changeinfo, tab) {
var url = tab.url;
if (url !== undefined) {
test();
}
});
My question is, why is test() firing twice? And more importantly, how do I make it fire just once?
Have a look at what the different states are when the event is dispatched. I presume, that it is getting dispatched once when the state is "loading" or when the state is "complete". If that is the case, then your problem would be fixed with:
function test()
{
alert("In test!");
}
chrome.tabs.onUpdated.addListener(function(tabid, changeinfo, tab) {
var url = tab.url;
if (url !== undefined && changeinfo.status == "complete") {
test();
}
});
I was also confused by this and tried to find the answer. Only after some experimentation did I figure out why I was receiving multiple "complete" update events for what I thought was a single page "update".
If your page has iframes, each will trigger a "complete" event and bubble up to the parent content script. So more complex pages will trigger a slew of onUpdated events if they have iframes.
When you write the following code:
chrome.tabs.onUpdated.addListener(function(tabid, changeinfo, tab) {
var url = tab.url;
if (url !== undefined) {
test();
}
});
You're calling addListener and telling it to call test() not immediately but rather whenever the tab is updated. Tab update events are broadcast by the Chrome browser itself, which in turn, causes your test() code to run.
I know this is old but anyway... it's happening to me too and maybe this can help someone. Looking into the Tab object that the handler function is receiving, I saw that the favIconUrl property is different in both calls so I guess it has something to do with that although I have no idea about the reason behind this.
I thought it was a bug but after a second thought and some testing I discard the bug theory.
What I know so far is that if two properties are changed, the event is triggered twice with the changeInfo object containing one property each time. In other words if for instance the properties that change are status and favIconUrl, then
changeInfo = {status:"complete"};
and then in the next call
changeInfo = {favIconuUrl : ".... whatever ..."};
Although I'm not sure why our code is firing twice, I had a similar problem and this answer helped me out a lot.
https://stackoverflow.com/a/49307437/15238857
Vaibhav's answer was really smart in incorporating validation in the form of the details object of the request.
When logging the details object I noticed a trend that the original firing had a frameId of 0, and the second firing was always something else. So instead of positively validating that you're in the correct submission of the URL, I like using an if statement to return and bail out when you have the duplicate entry.
I'm using onCompleted here, but it should work for onUpdated as well.
chrome.webNavigation.onCompleted.addListener(details => {
//need to make sure that this only triggers once per navigation
console.log(details);
if (details.frameId !== 0) return;
});
I solved this problem by checking the title of the tab when updated:
chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
var title = changeInfo.title;
if (title !== undefined) {
doSomething();
}
});
Twice is due to status being loading once and status being completed once.
if you are unconcerned about the status, you may also try the onCreated event. That will be fired just once!
I have a custom web part placed on one of the application page in SharePoint. This page it seems already have a function which gets executed on windows beforeunload Javascript event.
My problem is that I too need to execute some client side code (to prompt user for any unsaved changes in my web part) on windows beforeunload event.
How can I achieve this? I mean let the default event be fired as well as call my function also?
Appreciate any help.
This should be possible by checking for an existing handler assigned to the onbeforeunload event and, if one exists, saving a reference to it before replacing with your own handler.
Your web part might emit the following script output to do this:
<script type="text/javascript">
// Check for existing handler
var fnOldBeforeUnload = null;
if (typeof(window.onbeforeunload) == "function") {
fnOldBeforeUnload = window.onbeforeunload;
}
// Wire up new handler
window.onbeforeunload = myNewBeforeUnload;
// Handler
function myNewBeforeUnload() {
// Perform some test to determine if you need to prompt user
if (unsavedChanges == true) {
if (window.confirm("You have unsaved changes. Click 'OK' to stay on this page.") == true) {
return false;
}
}
// Call the original onbeforeunload handler
if (fnOldBeforeUnload != null) {
fnOldBeforeUnload();
}
}
</script>
This should allow you to inject your own logic into the page and make your own determination as to what code executes when the page unloads.