Force iframe to (re)load document when only src hash changes - javascript

I have an iframe that's supposed to load different modules of a web application.
When the user clicks a navigation menu in the top window, it's passes a new url to the iframe. The trouble is, the new url doesn't actually point to a new page, it only uses a changed hash.
i.e.:
User clicks "dashboard", iframe src set to application.html#/dashboard
User clicks "history", iframe src set to application.html#/history
This means that the iframe does not actually load the src url again because hash changes don't require it to. The application inside the iframe is an angular app which loads the required modules dynamically using requireJS. We need this functionality to remain.
I need to force the frame source to load again even though only the hash changed. It's possible that I instead find a way to rewrite our angular app to dynamically unload/load the modules on push state events but that introduces several layers of issues for the app, plus some IE trouble.
I've tried:
Setting iframe src and calling it's location.reload, but that reloads the originally loaded url
Setting the iframe location.href/hash and calling reload, same issue
Blanking the src attribute and then setting the new url - no effect
The only solution I can find is to set the src to a blank screen, then onload set it to the new url:
var appIFrame = document.getElementById('appIFrame');
appIFrame.src = 'about:blank';
appIFrame.onload = function(){
appIFrame.src = '// set the real source here';
appIFrame.onload = false;
}
This works, yet it seems inefficient because there's an extra step.

Maybe add a dynamic GET parameter – f.e. the current timestamp, which you can get from the JavaScript Date object – to the iframe URL.
Instead of assigning application.html#/dashboard as src value, assign application.html?1234567890#/dashboard from your outside page (with 1234567890 replaced by the current timestamp, obviously).

I don't have a specific answer for you. However, the following script may proved useful (I wrote this about a year or so ago). The following script deals with re-adjusting iframe height when the document changes. This script was tested cross-browser. It does deal with the issues you're experience but indirectly. There is a lot of commenting with the Gist:
https://gist.github.com/say2joe/4694780

Here my solution (based on this stackoverflow answer):
var $ = function(id) { return document.getElementById(id); };
var hashChangeDetector = function(frame, callback) {
var frameWindow = frame.contentWindow || frame.contentDocument;
// 'old' browser
if (! "onhashchange" in window) {
var detecter = function(callback) {
var previousHash = frameWindow.location.hash;
window.setTimeout(function() {
if (frameWindow.location.hash != previousHash) {
previousHash = frameWindow.location.hash;
callback(previousHash);
}
}, 100);
};
}
else // modern browser ?
{
var detecter = function(callback) {
frameWindow.onhashchange = function () {
callback(frameWindow.location.hash);
}
};
}
detecter(callback);
};
hashChangeDetector($('myframe'), function(hash) {
alert ('detecting hash change: ' + hash);
});
You can test this here: http://paulrad.com/stackoverflow/iframe-hash-detection.html

Related

How can I run a script in another (newly-opened) tab?

I am trying to run a script in a new tab. The code I use is this:
$ = jQuery;
function openAndPush(url, id) {
var win = window.open('https://example.com' + url + '?view=map');
var element = $('<script type="text/javascript">console.log("Starting magic...");var region_id='+id+';$=jQuery;var p=$(\'div["se:map:paths"]\').attr(\'se:map:paths\');if(p){console.log("Found! pushing..."); $.get(\'https://localhost:9443/addPolygon\', {id: region_id, polygon: p}, function(){console.log("Done!")})}else{console.log("Not found!");}</script>').get(0);
setTimeout(function(){ win.document.body.appendChild(element);
console.log('New script appended!') }, 10000);
}
Considering the following:
I was inspired in this answer, but used jQuery instead.
I run this code in an inspector/console, from another page in https://example.com (yes, the actual domain is not example.com - but the target url is always in the same domain with respect to the original tab) to avoid CORS errors.
When I run the function (say, openAndPush('/target', 1)) and then inspect the code in another inspector, one for the new window, the console message Starting magic... is not shown (I wait the 10 seconds and perhaps more). However the new DOM element (this script I am creating) is shown in the Elements tab (and, in the first console/inspector, I can see the New script appended! message).
(In both cases jQuery is present, but not occupying the $ identifier, which seems to be undefined - so I manually occupy it)
What I conclude is that my script is not being executed in the new window.
What am I missing? How can I ensure the code is being executed?
Instead of embedding script element in the document, do this.
wrap the code that you want to run in another tab, into a function.
bind that wrapped function to the new tab's window
Call that function
Here's the code that I ran in my console and it worked for me i.e another tab was opened and alert was displayed.
function openAndPush(url, id) {
var win = window.open('https://www.google.com');
win.test = function () {
win.alert("Starting magic...");
}
win.test();
setTimeout(function () {
win.document.body.appendChild(element);
console.log('New script appended!')
}, 10000);
}
Found that my error consisted on the origin document being referenced when creating a new script node, instead of the target document (i.e. win.document). What I needed is to change the code to reference the new document and create the node directly, no jQuery in the middle at that point. So I changed my code like this:
function openAndPush(url, id) {
var win = window.open('https://streeteasy.com' + url + '?view=map');
var element = win.document.createElement('script');
element.type='text/javascript';
element.innerHTML = 'console.log("Starting magic...");var region_id='+id+';$=jQuery;var p=$(\'div[se\\\\:map\\\\:paths]\').attr(\'se:map:paths\');if(p){console.log("Found! pushing..."); $.get(\'https://localhost:9443/addPolygon\', {id: region_id, polygon: p}, function(){console.log("Done!")})}else{console.log("Not found! searched in: ", document);}'
setTimeout(function(){ win.document.body.appendChild(element); console.log('New script appended!') }, 10000);
}
With this code something is essentially happening: The JS code is being parsed (and its node created) in the context of the new document. Older alternatives involved the origin console (since the origin document was implicitly referenced).
It's bad practice to send scripts to another webpage. You can pass some query params using a complicated URL and handle it by a source code from another webpage, it's much better:
function doMagicAtUrlByRegionId (url, regionId) {
window.open(`https://example.com${url}?view=map&magic=true&region_id=${regionId}`);
}

Avoid wrong interpretation of source with document.createElement for dynamic sources

I have script that I would like visitors on my website to run when they load a web page. It looks like this:
window.onload = function(){
var pxl=document.createElement('img');
pxl.setAttribute('src', 'http://localhost:8080/getTrackingPixel')
document.body.appendChild(pxl);
}
Most of the times the source returns an image and it works fine. However, sometimes it returns this:
<html><body style="background-color:transparent"></body></html>
And I can't really change the fact that it might sometimes not return an image. How do I change the javascript so that it can handle the html response without any errors? It might be possible for me to predict when it happens though - but I haven't managed to find a good way to request the source and return the html either.
You can achieve it by using the javascript Image object which, unlike the createElement approach, allows you to fetch the src url before inserting the img in the DOM.
The onload event of the Image object won't fire if the loaded content isn't an img.
Here it is :
window.onload = function(){
var pxl = new Image();
pxl.onload = function(){
// is IMG
document.body.appendChild(pxl);
}
pxl.onerror = function(){
// is not IMG
// Meaning in your case : <html><body style="background-color:transparent"></body></html>
}
pxl.src = 'http://localhost:8080/getTrackingPixel';
}
(Note that your code also missed the semicolon ";" line 4)

PJax - how do I turn off the modified behaviour

I've got PJax up and running on my test site - it works a treat. However it relies heavily on a lot of javascript widgets and hence leaks memory.
Since I don't have time right now to re-write every widget, I thought that a simple solution would be to do a normal page load after, say 20 pjax page transitions. A simple plan....but it doesn't seem to be possible.
$.pjax.disable();
....still fetches the content via AJAX, but doesn't change the page.
$(document).pjax();.
...doesn't change the behaviour
$.pjax.handleClick = function (event, container, options) { return; };
...doesn't change the behaviour
$.pjax.state.timeout = 0;
...doesn't change the behaviour
delete $.pjax;
...breaks navigation
$.pjax.defaults.timeout=0;
...doesn't change the behaviour
How do I suspend pjax?
If you add a listener for pjax:beforeSend, you can capture the requested URL, set location.href yourself and return false to cancel the pjax behavior. That is how I'm doing it with the following code:
var pageLoadCounter = 0;
var MAX_PAGE_LOADS = 20;
$(".pjaxContainer").on("pjax:beforeSend", function (e, xhr, settings) {
if (++pageLoadCounter > MAX_PAGE_LOADS) {
// URI can be found at https://github.com/medialize/URI.js
var uri = URI(settings.url);
// Remove _pjax from query string before reloading
uri.removeSearch("_pjax");
location.href = uri.toString();
return false;
}
});
I've discovered that changing the id of the pjax container div gives me the desired result - although this seems like a bit of a kludge. It would also be possible by changing the timeout of the ajax request to 0 - but I still need to work out how to do this.
I did ask on the PJax github page about this but so far have not received a response.

Load list of images in sequential order

I'm working on a website which uses ExpressionEngine to create a list of images with img1, img2, img3 etc as the ID and creates an array with their sources imgAddresses[1], imgAddresses[2], imgAddresses[3] etc.
I'm attempting to create a function which loads the first image, then (when the first is completely loaded), load the second, third etc. The following is what I have so far:
function loadImage(counter) {
var i = document.getElementById("img"+counter);
if(counter==imgAddresses.length) { return; }
i.onload = function(){
loadImage(counter+1)
};
i.src = imgAddresses[counter];
}
document.onload=loadImage(0);
It works when refreshing the page, but not when accessing the page via the URL. As far as I can tell from research, this is because the onload event is not fired when a cached image is loaded, and refreshing the page clears the cache, whereas accessing the page via the URL does not.
Research suggests that assigning the src of the image after declaring the onload event would get around this, but it does not seem to have solved it in this case. I was thinking that this may be because the onload event is recursive in this case.
Does anyone have any ideas on how to make sure the browser is loaded a fresh copy of the image, rather than a cached version? Or whether there is a better way to write this function? Thanks for any help!
EDIT: One solution that I have found is to change the img source assignment to:
i.src = imgAddresses[counter] + '?' + new Date().getTime();
This forces the user to load a fresh copy each time, which I guess is not so much a solution, but a workaround
The only thing I can say is that you are not attaching the document.onload handler correctly. I cannot tell if it will fix your issue because image.onload is not reliable in all browsers, however the onload should be set to a function reference and that's not what you are doing.
Instead, you should have something like:
function loadImage(counter) {
//initialize the counter to 0 if no counter was passed
counter = counter || 0;
var i = document.getElementById("img"+counter);
if(counter==imgAddresses.length) { return; }
i.onload = function(){
loadImage(counter+1)
};
i.src = imgAddresses[counter];
}
document.onload = loadImage; //instead of loadImage(0);
You can tell how the browser will manage the cached resources
Take a look to HTML5 cache approach:
HTML5: The cache manifest file
This way you can avoid the browser cache for the specified resources.

How to run a javascript function using the # in the url?

hi this all started when i ran a function (lets call it loadround) that altered the innerHTML of an iframe. now once loadframe was loaded there were links in the iframe that once clicked would change the iframe page. the only problem is when i click the back button the loadround page was gone. i've thought about this numerous times to no avail. so i tried this code.
loadround
then
function loadround(a,b){
window.location.hash = "#loadround('"+a+"','"+b+"')";
var code = "<(h2)>"+a+"</(h2)><(h2)>"+b+"</(h2)>"
var iFrame = document.getElementById('iframe');
var iFrameBody;
iFrameBody = iFrame.contentDocument.getElementsByTagName('body')[0]
iFrameBody.innerHTML = code;
}
(the brackets in the h2 are intentional)
then i would try to reload the function by possibly an onload function but for now i was testing with a simple href as followed.
function check(){
var func = location.hash.replace(/#/, '')
void(func);
}
check
unfortunately the check code doesn't work and im almost certain there is an easier way of doing this. i tried changing the src of the iframe instead of the innerhtml and there was the same problem. thanks in advance
The modern browsers are starting to support the event window.onhashchange
In the meantime you can use the workaround proposed by Lekensteyn or maybe you can find something useful here: JavaScript/jQuery - onhashchange event workaround
You are misunderstanding the function void, which just make sure the return value is undefined. That prevents the browser from navigating away when you put it in a link. You can test that yourself by pasting the next addresses in your browser:
javascript:1 // note: return value 1, browser will print "1" on screen
javascript:void(1) // note: undefined return value, browser won't navigate away
It's strongly discouraged to execute the hash part as Javascript, as it's vulnerable to XSS without proper validating it. You should watch the hash part, and on modification, do something.
An example; watch every 50 milliseconds for modifications in the hash part, and insert in a element with ID targetElement an heading with the hash part. If the hash part is not valid, replace the current entry with home.
var oldHash = '';
function watchHash(){
// strip the first character (#) from location.hash
var newHash = location.hash.substr(1);
if (oldHash != newHash) {
// assume that the parameter are alphanumeric characters or digits
var validated = newHash.match(/^(\w+)$/);
// make sure the hash is valid
if (validated) {
// usually, you would do a HTTP request and use the parameter
var code = "<h1>" + validated[1] + "</h1>";
var element = document.getElementById("targetElement");
element.innerHTML = code;
} else {
// invalid hash, redirect to #home, without creating a new history entry
location.replace("#home");
}
// and set the new state
oldHash = newHash;
}
}
// periodically (every 50 ms) watch for modification in the hash part
setInterval(watchHash, 50);
HTML code:
Home
About Me
Contact
<div id="targetElement">
<!-- HTML will be inserted here -->
</div>

Categories