I have an <iframe> with content designed (and allowed) to be modified by JS code.
At some point I want to clear iframe's content (i.e. load its HTML from the document pointed by src attribute) and start operating on a new DOM as soon as it's loaded.
It seems very convenient to use the iframe.contentWindow.location.reload() function. It reloads the entire inner document and replaces iframe.contentWindow and iframe.contentDocument objects with new ones.
The problem is I can't listen to DOMContentLoaded event on a newly created document. If I add a listener to iframe.contentDocument right after calling reload(), it seems to attach to the old document object which is about to be destroyed. If I wait for some time before adding listener, I have a chance to set it after the event has fired and miss it.
Observing the readyState property of the document doesn't help because it can be set to "complete" when the document hasn't reloaded yet as well as when it has reloaded and finished loading its content.
The best solution I could come up with as far is quite ugly and requires active polling:
function reloadIframe(iframe) {
iframe.contentWindow.dirty = true;
iframe.contentWindow.location.reload();
}
function documentReady(iframe) {
return new Promise(resolve => {
setInterval(() => {
if (
!iframe.contentWindow.dirty && // check that this is not the old window instance
iframe.contentDocument.readyState != "loading"
) {
resolve();
}
}, CHECK_READY_INTERVAL);
});
}
Is the a simpler way to get notified when a window has finished reloading?
Note: I am able to remove an <iframe> element itself and create the new one, but this approach had even more problems.
Due to cross-origin limitations, this will only work assuming you are running the source and parent frames from the same domain, however, you should be able to use:
function documentReady(iframe) {
var doc = iframe.contentDocument || iframe.contentWindow.document;
if ( doc.readyState == 'complete' ) {
iframe.contentWindow.onload = function() {
return true;
};
} else {
return false;
}
}
This will essentially get the iframe document and check whether the document and all sub-resources have finished loading. If they have, it will then call onload to verify that it has finished loading. The function will return true or false depending on whether the iframe has successfully loaded. If wanted, you could also run the function in a loop after requesting the iframe to reload, until the iframe has completed loading.
Related
Please take a look at the following code:
this.detachedWin = window.open(window.origin + "/#/tab","Detached","menubar=0,toolbar=0,personalbar=0,status=0");
this.doc = this.detachedWin.document;
setInterval(() => {
console.log(this.doc === this.detachedWin.document,this.detachedWin.document.readyState);
this.doc = this.detachedWin.document;
}, 10);
JSFiddle
If you take a look at your console log, you see something like this:
I don't underestand why the ready state of document is "complete" at the first time and what is the reason it changes to loading after that?
I expect that the ready state to be "loading" for the first time.
First "complete" happens with blank window, and then it changes location to provided URL.
Note that remote URLs won't load immediately. When window.open()
returns, the window always contains about:blank. The actual fetching
of the URL is deferred and starts after the current script block
finishes executing. The window creation and the loading of the
referenced resource are done asynchronously.
developer.mozilla.org
Simplified window opening flow
window.open()
Immediately loaded, because nothing to load: "loading"
Immediately become interactive, because it's empty: "interactive"
Blank window is ready: "completed"
Set window.location explicitly or implicitly
Redirect to URL: "loading"
Window become interactive: "interactive"
Window is ready: "completed"
Full flow with all steps for window opening you can find in specification.
To see how it works, check example:
const openMe = () => {
this.detachedWin = window.open("", "Detached","menubar=0,toolbar=0,personalbar=0,status=0");
setTimeout(() => {
console.log("loading happen now")
this.detachedWin.location = window.origin + "/#/tab"
}, 1000);
this.doc = this.detachedWin.document;
setInterval(() => {
console.log(this.doc === this.detachedWin.document, this.detachedWin.document.readyState);
this.doc = this.detachedWin.document;
}, 10);
}
<button onclick="openMe()">Open window</button>
I'm working on an issue for the 2048 game which is a webapp that's been ported to Android:
https://github.com/uberspot/2048-android/issues/15
The JavaScript seems to work everywhere except for a button that toggles a style change in the app. The JavaScript works in a browser, just not in the app. I think it comes down to some code in nightmode.js which begins:
window.onload = function() {
var a = document.getElementById("night");
a.onclick = function() {<<code that toggles day and night colors>>}
Does anyone have a solution to why this JavaScript isn't getting run?
Edit: When on Desktop Chrome and the screen is resized all the way down, the button continues to work. But switching the device mode in Chrome reveals that the thing never works for mobile devices. Since this behavior happens even without the WebView, it looks like just a javascript problem now.
The onload event is fired when everything is loaded. This can take some time.
Plus, window.onload might get overwritten later on...
You can try this instead:
var ready = function ( fn ) {
// Sanity check
if ( typeof fn !== 'function' ) return;
// If document is already loaded, run method
if ( document.readyState === 'complete' ) {
return fn();
}
// Otherwise, wait until document is loaded
// The document has finished loading and the document has been parsed but sub-resources such as images, stylesheets and frames are still loading. The state indicates that the DOMContentLoaded event has been fired.
document.addEventListener( 'interactive', fn, false );
// Alternative: The document and all sub-resources have finished loading. The state indicates that the load event has been fired.
// document.addEventListener( 'complete', fn, false );
};
// Example
ready(function() {
// Do night-mode stuff...
});
see: http://gomakethings.com/a-native-javascript-equivalent-of-jquerys-ready-method/
I'm building a dynamic website that loads all pages inside a "body" div via jquery's load(). The problem is I have a script looped with setInterval inside the loaded PHP page, the reason being I want the script loaded only when that page is displayed. Now I discovered that the scripts keep running even after "leaving" the page (loading something else inside the div without refresh) and if I keep leaving / returning the loops stack up flooding my server with GET requests (from the javascript).
What's a good way to unload all JS once you leave the page? I could do a simple dummy var to not load scripts twice, but I would like to stop the loop after leaving the page because it's causing useless traffic and spouting console errors as elements it's supposed to fill are no longer there.
Sorry if this has already been asked, but it's pretty hard to come up with keywords for this.
1) why don't you try with clearInterval?
2) if you have a general (main) function a( ) { ... } doing something you can just override it with function a() { }; doing nothing
3) if you null the references to something it will be garbage collected
no code provided, so no more I can do to help you
This really sounds like you need to reevaluate your design. Either you need to drop ajax, or you need to not have collisions in you method names.
You can review this link: http://www.javascriptkit.com/javatutors/loadjavascriptcss2.shtml
Which gives information on how to remove the javascript from the DOM. However, modern browsers will leave the code in memory on the browser.
Since you are not dealing with real page loads/unloads I would build a system that simulates an unload event.
var myUnload = (function () {
var queue = [],
myUnload = function () {
queue.forEach(function (unloadFunc) {
undloadFunc();
});
queue = [];
};
myUnload.add = function (unloadFunc) {
queue.push(unloadFunc);
};
return myUnload;
}());
The code that loads the new pages should just run myUnload() before it loads the new page in.
function loadPage(url) {
myUnload();
$('#page').load(url);
}
Any code that is loaded by a page can call myUnload.add() to register a cleanup function that should be run when a new page is loaded.
// some .js file that is loaded by a page
(function () {
var doSomething = function () {
// do something here
},
timer = setInterval(doSomething, 1000);
// register our cleanup callback with unload event system
myUnload.add(function () {
// since all of this code is isolated in an IIFE,
// clearing the timer will remove the last reference to
// doSomething and it will automatically be GCed
// This callback, the timer var and the enclosing IIFE
// will be GCed too when myUnload sets queue back to an empty array.
clearInterval(timer);
});
}());
I have an iframe that has a onload event. This event called a function (iframe_load) that I placed into a server side script. It appears that when my screen is launched, the onload event is triggered before the server side script has loaded and I get an error as the function is not found.
I have got around this by changing the onload event to call a checking function (iframe_check_load) in the client side script. This checks for the existence of parameter in the server side script, where if found it will then call the original function (iframe_load).
However ideally I would prefer not to have this checking function and to keep the client side code to a minimum. Is there a way I can add some code to the onload event to do this check, without having to use the checking function?
My current code:
function iframe_check_load(ctrl) {
if(typeof iframe_flag != "undefined"){
iframe_load();
}
}
<IFRAME id=iFrame2 onload=iframe_check_load() ></IFRAME>
I am sure there must be better ways to do all of this, please go easy as I'm still learning JS!
Since that there's no guarantee that the script is loaded before the frame, or vice versa, at least one checking must be performed in order to know if the external script is already available when the frame is loaded.
If the frame is loaded before the external script is available, you can use ONLOAD attribute on the element that load the external script to notify that it's already loaded. This will ensure that the iframe_load is always called. Assuming that there's no network error.
<SCRIPT>
//the saved "ctrl" parameter for iframe_load if
//the frame is loaded before custom_scripts.js.
var ctrlParam; //undefined as default
//script loaded handler for custom_scripts.js
function customScriptLoaded() {
//call iframe_load only if ctrlParam is not undefined.
//i.e.: frame is already loaded.
//will do nothing otherwise. leave it to iframe_check_load.
if (typeof ctrlParam != "undefined") {
iframe_load(ctrlParam);
}
}
//check whether it's safe to call iframe_load.
//assuming that "iframe_flag" is defined by custom_scripts.js.
function iframe_check_load(ctrl) {
if (typeof iframe_flag != "undefined") {
//custom_scripts.js already loaded.
//call iframe_load now.
iframe_load(ctrl);
} else {
//custom_scripts.js not yet loaded.
//save parameter and defer call to iframe_load.
//iframe_load will be called by customScriptLoaded.
//ctrl parameter must not be undefined in order to work.
console.log('Error: ctrl parameter of iframe_check_load is undefined. iframe_load will never be called.');
ctrlParam = ctrl;
}
}
</SCRIPT>
<!--Note: Don't forget to duble-quotes attribute values-->
<SCRIPT id="custom_scripts" type="text/javascript" src="htmlpathsub/custom/custom_scripts.js" UserSuppliedFullPath="1" onload="customScriptLoaded()"></SCRIPT>
<!--Using "this" (the IFRAME element) as the "ctrl" parameter-->
<IFRAME id="iFrame2" onload="iframe_check_load(this)"></IFRAME>
I have written the following javascript function which hangs up because it never seems to be able to find the canvas_frame element on a loaded GMail page (the compose page). This is begin called via the XUL of a Firefox add-on. Any thoughts on what might be going on?
init : function () {
var frame, interval;
frame = document.getElementById('canvas_frame');
interval = setInterval(function() {
if (frame) {
if (frame.contentDocument) {
clearInterval(interval);
GLOBALS.doc = frame.contentDocument;
onContentReady();
}
}
}, 500);
}
You should prefer to wait for a load event on the frame, rather than polling. But my guess is that the canvas_frame element hasn't been created yet, so you need to fetch it each time inside the polling loop. Otherwise the frame variable is always null.