Dynamic loading of javascript causes target to iframe corruption - javascript

I am rendering a page which dynamically creates an iframe as part of the page. Content renders in the iframe when it is created. I also have a link which "targets" the iframe for rendering additional pages.
The code that renders inside the iframe dynamically loads javascript. However, when you click the link which targets the iframe name, instead of the content rendering in the iframe, a new window is created as though target="blank". If I comment out the dynamic loading of the javascript the link target= works fine every time.
As far as I can tell I am doing the dynamic loading properly. The js code loads and executes properly. However, I'm not sure what is happening to the DOM to foul up the target. Any help would be appreciated.
page1.html:
<script type="text/javascript" src="js/jquery-1.6.2.min.js"></script>
<script type="text/javascript">
var ifr = "<iframe width='998' name='main'
id='main' src='page2.html' scrolling='no'
frameborder='1' height='100'></iframe>";
$(document).ready(function() {
$("#content").html(ifr);
$('iframe').load(function() {
this.style.height =
this.contentWindow.document.body.offsetHeight + 60 + 'px';
});
});
</script>
<body>
<div id="wrapper" style="height: 100px;">
<p>Click Me</p>
<div id="content">
</div>
</div>
</body>
page2.html:
<script type="text/javascript">
// var ScriptLoaded = true;
if (typeof ScriptLoaded == 'undefined') {
var scripts = ["js/jquery-1.6.2.min.js"];
for (i=0; i<scripts.length; i++) {
var name = scripts[i];
loadScript(name, function() {
});
}
};
window.onload = waitForLoad();
function loadScript(name,callback) {
var script = document.createElement('script');
script.type = "text/javascript";
if (script.readyState) { // IE
script.onreadystatechange = function () {
if (script.readyState == "loaded" ||
script.readyState == "completed") {
script.onreadystatechange = null;
callback();
}
}
} else { // Other
script.onload = function() {
callback();
}
}
script.src = scripts[i];
document.getElementsByTagName("head")[0].appendChild(script);
};
function waitForLoad() {
if (typeof jQuery == "undefined") {
setTimeout(waitForLoad,500);
}
};
</script>
<body>
<div class="page">
this is in an iframe
</div>

I've looked for a natural solution, but there does not seem to be one.
target="someIframe" does not like it when the iframe is dynamically created. Here is a workaround you could place after creating the iframe:
$('a[target="main"]').click(function(ev){
var src = $(this).attr('href');
$('#main').attr('src', src);
ev.preventDefault();
});
It will select every link that has the iframe as a target, and when you click on them, it will get their href, set it as the iframe's src, and prevent the link from opening in a new window.

Related

How to load an external script in an iframe that will reference the iframe's document not the parent document

I am using an iframe in order to isolate its content's CSS. Which is working great. But, I need the iframe to load an external script, specifically the script I am loading is: https://web.squarecdn.com/v1/square.js
You can see that this external script calls document often. I need it to reference the document of my iframe not the document of the parent HTML.
I have tried this and it does not accomplish this:
const insertScriptToIframe = (doc, target, src, callback) => {
var s = doc.createElement("script");
s.type = "text/javascript";
if(callback) {
if (s.readyState){ //IE
s.onreadystatechange = function(){
if (s.readyState == "loaded" ||
s.readyState == "complete"){
s.onreadystatechange = null;
callback();
}
};
} else { //Others
s.onload = function(){
callback();
};
}
}
s.src = src;
target.appendChild(s);
}
// add web payments script
const createWebPaymentsNode = () => {
const iFrame = document.createElement('iframe');
const context = iFrame.contentDocument;
const frameHead = context.getElementsByTagName('head').item(0);
insertScriptToIframe(context, frameHead, 'https://web.squarecdn.com/v1/square.js');
}
It adds the external script to the iframe. But the external script is referencing the parent document. I know it is doing this because the external script, itself, loads other scripts and iframes that are being placed in the parent HTML, instead of the iframe's.
I need to be able to do this in vanilla js. Thank you

show loading gif until script has loaded

I would like to display a loader icon gif until the script is totally loaded, is it possible?
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
<img src="loading.gif">
I think it is necessary to use jquery. but I do not know how to do. could someone help me?
Display image <img src="loading.gif">
On dom-ready remove element.
$(document).ready(function () {
$('img').remove();
})
Can be done even without jQuery:
<img id="loading" src="loading.gif">
<script onload="javascript:document.getElementById('loading').style.display='none'" src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"> </script>
Yes you can. You can use this function which loads a script and fires a callback. In your callback, you hide the gif.
function loadScript( url, callback) {
var script = document.createElement( "script" )
script.type = "text/javascript";
if(script.readyState) { //IE
script.onreadystatechange = function() {
if ( script.readyState === "loaded" || script.readyState === "complete" ) {
script.onreadystatechange = null;
callback();
}
};
} else { //Others
script.onload = function() {
callback();
};
}
script.src = url;
document.getElementsByTagName( "head" )[0].appendChild( script );
}
So, in your HTML place your gif showing up initially. And, add this code at your onload function:
<script>
window.onload = function(){
loadScript('https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js', function(){
$('.gif-selector').hide();
})
}
</script>
Edit: This is a more complex solution than others provided, maybe if your goal is something pretty simple you should use some of the others. Still, this function works great and I have used it in many of my projects. It's best thing is that you can chose to load the script whenever you want to

history.js stops firing statechange event after pressing back button 2 times

One of my customer has a lot of webistes based on lots of CMS (Wordpress, Drupal and so on). He wanted a generic directory, where professionals can register and share information. It is "generic", as it can be installed on any website (declined according to the website's business).
I developed it inside an iframe, as this can be installed on any CMS, and there were other good reasons to do it this way (CSS, SEO...). But as iframe-browsing is transparent for the navigation bar, I needed to do some tricks to allow visitors to copy/paste professionals's urls, share and fav them. I used history.js to update the navbar's URL, and when direct-accessing the iframe I redirect the user to the right "main website"'s page.
But my problem come when clicking the "back" button. For some reasons, the "statechange" event is not properly fired when clicking "back" a second time.
I developed a demo, you can see my problem live here, and you can download it here.
As you can see on the log div, when you click B and C, you'll get:
Push: /a.html
Push: /b.html
statechange trigered
Push: /c.html
statechange trigered
If you go back once, you'll get:
statechange trigered
Pop: /b.html
And if you go back a second time, you'll get:
Push: /c.html
statechange trigered
Instead of:
statechange trigered
Pop: /a.html
So my question:
What is going on here ?
main.html
<iframe id="my-test" src="a.html" width="500" height="500"></iframe>
<div id="log"></div>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery-browser/0.0.6/jquery.browser.min.js"></script>
<script src="history.js/scripts/bundled/html4+html5/jquery.history.js"></script>
<script type="text/javascript">
var test_url = 'http://so.ocarina.fr/test';
// used to avoid listening events trigered by the script itself
var manual_state_change = true;
// used to avoid pushing the previous page
var is_back = false;
$(document).ready(function () {
$('#my-test').load(function () {
// when iframe has loaded, we replace the navbar's url by
// the one from iframe's source.
if (!window.is_back) {
window.manual_state_change = false;
var History = window.History;
if (History.enabled) {
var url = $('#my-test').get(0).contentWindow.location.href;
if (url.indexOf('blank') > 0) {
return;
}
if (url.indexOf('test') > 0) {
url = url.substring(url.indexOf('test') + 4);
if (url.length === 0) {
url = '/';
}
}
$('#log').append('Push: ' + url + '<br/>');
var title = $("#my-test").contents().find("title").html();
document.title = title;
History.pushState({url: decodeURIComponent(url + '')}, title, window.test_url + decodeURIComponent(url + ''));
window.manual_state_change = true;
}
}
window.is_back = false;
}).trigger('load'); // triggered once to replace main.html by a.html
var History = window.History;
if (History.enabled) {
History.Adapter.bind(window, 'statechange', function () {
// if user clicks back, we should change the iframe's location
// to the backward url from history stack.
$('#log').append('statechange trigered <br/>');
if (window.manual_state_change === true) {
window.manual_state_change = false;
var state = History.getState();
var url = state.data.url;
$('#log').append('Pop: ' + url + '<br/>');
window.is_back = true;
$('#my-test').attr('src', window.test_url + url);
$('#my-test')[0].contentWindow.location = window.test_url + url;
window.manual_state_change = true;
}
});
}
});
</script>
a.html
<title>Test A</title>
<script type="text/javascript">
if (top.location === self.location) {
window.location = 'main.html';
}
</script>
<div id="content">
<p>This is test A</p>
Go to test B
</div>
b.html
<title>Test B</title>
<script type="text/javascript">
if (top.location === self.location) {
window.location = 'main.html';
}
</script>
<div id="content">
<p>This is test B</p>
Go to test C
</div>
c.html
<title>Test C</title>
<script type="text/javascript">
if (top.location === self.location) {
window.location = 'main.html';
}
</script>
<div id="content">
<p>This is test C</p>
<p>Go back to A using your backward button...</p>
</div>
I finally found my mistake: when clicking on my iframe's links, browser already add an entry to the history, so I shouldn't add an entry using pushState, but simply replace the current history entry using replaceState.
Bonus: we don't need to handle statechange, browser does anything as naturally as usual. Cool!
See the working demo here.
My code becomes:
<iframe id="my-test" src="a.html" width="500" height="500"></iframe>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery-browser/0.0.6/jquery.browser.min.js"></script>
<script src="history.js/scripts/bundled/html4+html5/jquery.history.js"></script>
<script type="text/javascript">
var test_url = 'http://so.ocarina.fr/test2';
$(document).ready(function () {
$('#my-test').load(function () {
// when iframe has loaded, we replace the navbar's url by
// the one from iframe's source.
var History = window.History;
if (History.enabled) {
var url = $('#my-test').get(0).contentWindow.location.href;
if (url.indexOf('blank') > 0) {
return;
}
if (url.indexOf('test') > 0) {
url = url.substring(url.indexOf('test2') + 5);
if (url.length === 0) {
url = '/';
}
}
var title = $("#my-test").contents().find("title").html();
document.title = title;
History.replaceState({}, title, window.test_url + decodeURIComponent(url + '')); // YES!
}
}).trigger('load'); // triggered once to replace main.html by a.html
});
</script>

Conditional loading of jQuery

I am testing with pure JavaScript if browser seems to support HTML5 and if so, I want to load jQuery and then process the rest of page. If not, some redirection will occur.
<script type="text/javascript">
var canvas = document.createElement('canvas');
if (canvas && canvas.getContext && canvas.getContext('2d')) {
var s = document.getElementsByTagName('script')[0];
var jq = document.createElement('script');
jq.type = 'text/javascript';
jq.src = 'js/jquery.js';
s.parentNode.insertBefore(jq, s);
}
else {
// ... redirection ...
}
</script>
<script type="text/javascript">
$(function () {
//...
}
</script>
But the code above is not working properly, because I got error
Uncaught ReferenceError: $ is not defined
which is clearly saying that jQuery library has not been loaded.
Why? What is wrong with conditional script loading in my code above?
This is a case where it may make sense to use document.write(). You'd need to put this code in the <body> instead of the <head>:
<script type="text/javascript">
var canvas = document.createElement('canvas');
if (canvas && canvas.getContext && canvas.getContext('2d')) {
document.write( '<script src="js/jquery.js"><\/script>' );
}
else {
// ... redirection ...
}
</script>
<script type="text/javascript">
$(function () {
//...
}
</script>
Or, you may be able to use an ordinary <script> tag to load jQuery, but put it after your conditional redirection:
<script>
var canvas = document.createElement('canvas');
if( !( canvas && canvas.getContext && canvas.getContext('2d') ) ) {
// ... redirection ...
}
</script>
<script src="js/jquery.js"></script>
<script>
$(function () {
//...
}
</script>
With either of these approaches, the order of execution is:
The first <script>.
The loading of jquery.js, whether done with document.write() or a simple <script> tag.
The final script.
When you insert a script tag like you are, it will be loaded in the background, not immediately and thus your next script will run before jQuery is loaded. You will need to attach a listener such that you know when jQuery is successfully loaded and you can then run your scripts that use jQuery.
Here's an article that describes how to know when a dynamically loaded script is loaded: http://software.intel.com/en-us/blogs/2010/05/22/dynamically-load-javascript-with-load-completion-notification.
FYI, in your specific case, you also could just have a static script tag that loads jQuery, but place your script that detects whether to redirect or not BEFORE the jQuery script tag. That would be the simplest option.
<script type="text/javascript">
var canvas = document.createElement('canvas');
if (!canvas || !canvas.getContext || !canvas.getContext('2d')) {
// redirect here or whatever
}
</script>
<script type="text/javascript" src="jquery.js">
</script>
<script type="text/javascript">
$(function () {
//...
}
</script>
finally working like a charm, I'm relieved myself !
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>JS Bin</title>
<script type="text/javascript">
window.onload = function(){
var jqu = "$(console.log('worked'));";
var canvas = document.createElement('canvas');
if (canvas && canvas.getContext && canvas.getContext('2d')) {
var s = document.getElementsByTagName('head')[0];
var jq = document.createElement('script');
jq.setAttribute('type','text/javascript');
jq.innerHTML = jqu;
var jqLoad = document.createElement('script');
jqLoad.setAttribute('type','text/javascript');
jqLoad.setAttribute('src','jquery-1.10.0.js');
jqLoad.setAttribute('id','jqloader');
s.appendChild(jqLoad);
document.getElementById('jqloader').onload = function(){
console.log('loaded');
s.appendChild(jq);
}
}
else {
// ... redirection ...
}
console.log(document);
}
</script>
</head>
<body>
</body>
</html>
jsbin Demo
explanation :
1- using dom functions to append or insert elements are always the best (dynamic and safer more than anything else), and document.write is not recommended over that.
2- at parse-time, whatever functions you have in your script will be evaluated thus you will get an error if you have the script and not loaded the library yet.
3- loading the library and executing the relevant script in the same tag is not recommended. better do the script in another tag (after loading is done completely) to ensure it will work.
4- events for document.onload ensures that the document is loaded and the doms exist so you can append children to them. as for the document.getElementById('jqloader').onload it was just to insure that the jquery library is loaded completely and added to the document, and only then the script will be added after and evaluated.
As others have said, the reason you're getting an error is because you've loaded jQuery asynchronously and it hasn't loaded yet.
There are two ways to accomplish what you want.
You can poll for window.jQuery, or you can use an asynchronous loader callback.
Since you only load jQuery only when you detect canvas support, you won't have to worry about supporting old browsers.
var async_script_load = function (s, callback) {
var script;
script = document.createElement("script");
script.async = "async";
if (s.scriptCharset) {
script.charset = s.scriptCharset;
}
script.src = s.url;
// Attach handlers for all browsers
script.onload = script.onreadystatechange = function () {
if (!script.readyState || /loaded|complete/.test(script.readyState)) {
// Handle memory leak in IE
script.onload = script.onreadystatechange = null;
// Remove the script
if (head && script.parentNode) {
head.removeChild(script);
}
// Dereference the script
script = undefined;
callback(200, "success");
}
};
// Use insertBefore instead of appendChild to circumvent an IE6 bug.
// This arises when a base node is used (#2709 and #4378).
head.insertBefore(script, head.firstChild);
};
async_loader({url:'http://tempuri.org/jquery.min.js'},function() {
//call jquery here.
});
For a polling method, it's as simple as:
var checkJq = function() {
if(window.jQuery) {
//do jQuery
} else {
setTimeout(checkJq,100);
}
}
setTimeout(checkJq,100);

Can one set an iframe's src and manipulate its contents afterwards?

My goal is to have a parent page change the src of an iframe from blank to its proper url (as to utilize an onload handler in the iframe at a given point, but that's beside the point) and then manipulate the iframe's contents. However, javascript seems oblivious to any elements of an iframe that aren't on its src when the DOM loads. Is there any way around this?
The setTimeouts are intended to allow the DOM and iframe to load.
edit:fixed some stuff.
Here's the containing page:
<html><head>
<script type="text/javascript">
var done = false;
var theIframe;
window.onload = function () {
setTimeout('stuff()', 2000);
clearTimeout('stuff()');
}
function stuff() {
if (!done) {
theIframe = window.myiframe;
theIframe.src = 'http://localhost/TestStuff/redirectIframe.jsp';
done = true;
stuff();
} else {
theIframe.setMe = true;
}
}
</script>
</head>
<body>
<iframe src="" width="500" height="500" id="myiframe" name="myiframe">
</iframe>
</body>
And here's the iframe:
<html>
<head>
<script type="text/javascript">
var setMe = false;
window.onload = setInterval('checker()', 1000);
function checker() {
alert('hi');
if (setMe) {
window.onload = null;
top.location = 'http://www.google.com';
alert('foundit');
} else alert('a');
}
</script>
</head>
<body></body>
</html>
Any ideas?
In this piece of code:
theIframe.src = ...;
stuff();
you're calling stuff() immediately, contrary to what you have described, so in fact you're not allowing any time for the page to load. Maybe you're confused about how setTimeout works: it just schedules a single execution after the specified time, it doesn't automatically delay all calls to a function.
Also, you can use clearTimeout only with a previous ID returned by setTimeout, not with code as you have now.
Try something like this:
window.onload = function() {
loadFrame();
}
function loadFrame() {
theIframe = ...;
theIframe.src = ...;
setTimeout(setSomething, 2000);
}
function setSomething() {
theIframe.setMe = true;
}

Categories