Close iframe cross domain - javascript

I am trying to do something similar to the Clipper application here http://www.polyvore.com/cgi/clipper
I can make the iframe appear in another website (cross domain). But I cannot make the "close" button to work.
This is what I used but it doesn't work for cross domain (basically remove the iframe element)
window.parent.document.getElementById('someId').parentNode.removeChild(window.parent.document.getElementById('someId'));
Can you help? Thanks.

You should use a library that abstracts this (e.g. http://easyxdm.net/wp/ , not tested). Fragment ID messaging may not work in all browsers, and there are better approaches, such as postMessage.
However, your example (Clipper) is using a hack called fragment id messaging. This can be cross-browser, provided the page containing your iframe is the top level. In other words, there are a total of two levels. Basically, the child sets the fragment of the parent, and the parent watches for this.
This is a similar approach to Clipper's:
parent.html
<html>
<head>
<script type="text/javascript">
function checkForClose()
{
if(window.location.hash == "#close_child")
{
var someIframe = document.getElementById("someId");
someIframe.parentNode.removeChild(someIframe);
}
else
{
setTimeout(checkForClose, 1000)
}
}
setTimeout(checkForClose, 1000);
</script>
</head>
<body>
<iframe name="someId" id="someId" src="child.html" height="800" width="600">foo</iframe>
</body>
</html>
child.html:
<html>
<head>
<script type="text/javascript">
setTimeout(function(){window.parent.location.hash = "close_child";}, 5000);
</script>
<body style="background-color: blue"></body>
</html>
EDIT2: Cross-domain and independently controlled are different. I dug into the (heavily minified/obfuscated) Polyvore code to see how it works (incidentally, it doesn't in Firefox). First remember that bookmarklets, such as the Clipper, live in the context of the page open when they start. In this case, the bookmarklet loads a script , which in turn runs an init function which generates an iframe, but also runs:
Event.addListener(Event.XFRAME, "done", cancel);
If you digg into addListener, you'll find (beautified):
if (_1ce2 == Event.XFRAME) {
if (!_1cb3) {
_1cb3 = new Monitor(function () {
return window.location.hash;
},
100);
Event.addListener(_1cb3, "change", onHashChange);
}
}
cancel includes:
removeNode(iframe);
Now, the only remaining piece is that the iframe page loads another script with a ClipperForm.init function that includes:
Event.addListener($("close"), "click", function () {
Event.postMessage(window.parent, _228d, "done");
});
So we see clearly they are using fragment ID messaging.

Try hiding the contents of the iframe, and don't worry about actually getting rid of the iframe element in the parent.

There is another implementation of the old hash hack. It's backwards compatible, easy javascript-only, and very easy to implement:
http://www.onlineaspect.com/2010/01/15/backwards-compatible-postmessage/

Related

Open <div> popup outside iFrame

Info: I was working on it for so long, I have a webpage that contains an iframe. Inside that iframe i have opened a page (application) from my own site.
Question: I'm trying to get the <div class = "ps-lightbox"> </ div> inside that iframe out of the iframe. but i cant figure it out with jQuery..
I know it sounds confusing. But I hope you understand my explanation.
Does anyone know how to fix this? You could save my day..
Screenshot of the webpage <
You can not access the elements which are not part of iframe document. But if you have iframe of your own website then window.postMessage can do the trick.
Consider below example:
mainPage.html
<html>
<head>
<script type="text/javascript">
window.addEventListener("message", function(evnet){
if(event.type === "GET_SOME_ELEMENT"){
var iframeWindow = document.getElementsById("iframe1")[0].contentWindow;
iframeWindow.postMessage("POST_SOME_ELEMENT", "TARGET_ORIGIN", {element: $(".some-element")}
}
});
<script/>
</head>
<body>
<div class="some-element"/>
<iframe id="iframe1" src="iframePage.html"/>
</body>
</html>
iframePage.html
<html>
<head>
<script type="text/javascript">
if(window.parent){
window.parent.postMessage("GET_SOME_ELEMENT", "TARGET_ORIGIN");
window.addEventListener("message", function(evnet){
if(event.type === "POST_SOME_ELEMENT"){
console.log(event.data.element);
}
});
}
<script/>
</head>
</html>
The exact question is how to do it with pure JavaScript, not with jQuery.
But I always use the solution that can be found in jQuery's source code. It's just one line of native JavaScript.
For me, it's the best, easily readable and even afaik the shortest way to get the content of the iframe.
First get your iframe
var iframe = document.getElementById('id_description_iframe');
// or
var iframe = document.querySelector('#id_description_iframe');
And then use jQuery's solution
var iframeDocument = iframe.contentDocument || iframe.contentWindow.document;
It works even in the Internet Explorer which does this trick during
the contentWindow property of the iframe object. Most other browsers
use the contentDocument property and that is the reason why we proof
this property first in this OR condition. If it is not set to try
contentWindow.document.
Select elements in iframe
Then you can usually use getElementById() or even querySelectorAll() to select the DOM-Element from the iframeDocument:
if (!iframeDocument) {
throw "iframe couldn't be found in DOM.";
}
var iframeContent = iframeDocument.getElementById('frameBody');
// or
var iframeContent = iframeDocument.querySelectorAll('#frameBody');
Call functions in the iframe
Get just the window element from iframe to call some global functions, variables or whole libraries (e.g. jQuery):
var iframeWindow = iframe.contentWindow;
// you can even call jQuery or other frameworks
// if it is loaded inside the iframe
iframeContent = iframeWindow.jQuery('#frameBody');
// or
iframeContent = iframeWindow.$('#frameBody');
// or even use any other global variable
iframeWindow.myVar = window.myVar;
// or call a global function
var myVar = iframeWindow.myFunction(param1 /*, ... */);
Note
All this is possible if you observe the same-origin policy.
This might help you
var html = $(".ps-lightbox").contents().find("body").html()
And btw, you can get access to iframe's content only from the same origin due to XSS protection
Make sure your code is inside jQuery ready event.
// This won't work
$("#iframe").contents().find('.ps-lightbox');
// This will work
$(function() {
$("#iframe").contents().find('.ps-lightbox');
})

Open pre-existing page and change parts of it

I am using javascript to open the species_county file in a new window/tab and then jquery to change the contents of an h1 tag with id speciesTitle.
open_event_test.html
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
</head>
<body>
<p>Click the button to open a new window called "MsgWindow" with some text. </p>
<button onclick="myFunction('Yada')">Yada</button>
<script>
function myFunction(species)
{
console.log(species);
var myWindow = window.open("species_county.html", species);
myWindow.document.getElementById("speciesTitle").html(species);
}
</script>
</body>
</html>
species_county.html
<!doctype html>
<head>
<meta charset="utf-8">
<title>Untitled Document</title>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
</head>
<body>
<h1 id="speciesTitle">Species in County</h1>
</body>
</html>
I get error saying the element I'm grabbing has value null. I need to replace only some content. Document.write does not work because it will not open the species_county file, it will create an entirely new page.
There are a few possible issues with your script.
1) Elements Do Not Have html Method
As mentioned by beercohol, there is no such method as html() on an HTML Element. html() is a jQuery method, which is a property of a jQuery Object. In order to use this, you need to either use jQuery to select the element, or use JavaScript's native innerHTML property.
myWindow.document.getElementById("speciesTitle").innerHTML = species;
// or
$(myWindow.document).find('#speciesTitle').html(species);
2) The Window/Frame's DOM Has Not Loaded
There's a high chance that, by the time you've run the code to change the speciesTitle element, the window that you've opened has not yet finished loading its DOM. This would result in an error along the lines of:
TypeError: myWindow.document.getElementById(...) is null
You'll need to somehow make sure that you don't run the HTML-altering code until the DOM of the new window has finished loading. Traditionally, this is a job for jQuery, but here's a quick (non-cross-browser-friendly) pure JavaScript example within your myFunction method:
function myFunction (species) {
var myWindow = window.open("species_county.html", species);
myWindow.onload = function() {
// DOM has loaded
this.document.getElementById("speciesTitle").innerHTML = species;
}
}
3) Same Origin Policy
For security reasons, most browsers won't let you make adjustments to a window of another frame unless both frames come from the same origin. If you're working locally, the chances are you're not using a domain name or an IP address which match. Chrome doesn't let you make these communications over the filesystem (not by default anyway), but I have had some success with Firefox waiving Same Origin Policy between files locally on the file system. Either way, this won't work on a legitimate browser if you are attempting to communicate with a frame from a completely different domain.
For more information, see the window.open() FAQ on the MDN website. Note the part about cross-domain:
A script loaded in a window (or frame) from a distinct origin (domain
name) cannot get nor set properties of another window (or frame) or
the properties of any of its HTML objects coming from another distinct
origin (domain name).
4) Popup Blockers
It's also likely that you may run into trouble with various browser implementations of popup blockers. These will outright refuse to open the new frame, resulting in a null window object. You will first need to 'allow' your page to open a window through some configuration in the browser (usually a popup-blocker icon makes itself apparent).
The obvious problem is that html() is not a method of the Element object returned by getElementById()
https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementById
https://developer.mozilla.org/en-US/docs/Web/API/Element
The property you're looking for is innerHTML instead.
https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML
Try this for your script:
<script>
function myFunction(species) {
console.log(species);
var myWindow = window.open("species_county.html", species);
myWindow.document.getElementById("speciesTitle").innerHTML = species;
}
</script>
html() is a jQuery function, so I suspect that's where you're getting confused. You don't really need jQuery just for this however, although you are including it.
You are trying to modify an element on a different page, and that's not possible with JavaScript in the way you're handling it.
One way to achieve your goal is to pass a $_GET parameter and use JavaScript on the newly loaded page to change the element.
Change this:
var myWindow = window.open("species_county.html", species);
myWindow.document.getElementById("speciesTitle").html(species);
To this:
var url = "species_county.html?species=" + encodeURIComponent(species);
window.open(url);
And then in species_county.html, look for the species parameter and change the title.
var mySpecies = location.search.split('species=')[1];
if(mySpecies !== undefined) {
document.getElementById("speciesTitle").innerHTML=mySpecies;
}
Unfortunately, shennan's 3rd idea was right, so I had to end up solving it like this:
open_event_test
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
</head>
<body>
<p>Click the button to open a new window called "MsgWindow" with some text.</p>
<button onclick="myFunction('Bombus balteatus')"><em>Bombus balteatus</em> Dahlbom</button>
<button onClick="myFunction('Bombus auricomus')"><em>Bombus auricomus</em> (Robertson)</button>
<script>
function myFunction(species)
{
var newhtml = '<!DOCTYPE html><head><meta charset="utf-8"><html><title>'+species+'</title></head><body><h1><em>'+species+'</em> in County</h1></body></html>';
var newWindow = window.open(species, '_blank');
newWindow.document.write(newhtml);
}
</script>
</body>
</html>

Capture click event from child frame into parent frame

Right, this is weird. I have done loads of Googling and found hundreds of articles which seem to point me in the right direction, but none seem to work.
Here's the latest incarnation of what I am trying to do:
Parent Page
<html>
<head>
<script>
$('#mainWindow').ready(function () {
$('#mainWindow').contents().find('#clickThis').live('click', function () {
alert('Click detected!');
});
});
</script>
</head>
<body>
<iframe id="mainWindow" name="mainWindow" src="myMainPage.aspx" style="border: 0; position:fixed; top:0; left:0; right:0; bottom:0; width:100%; height:100%"></iframe>
<iframe src="myOtherPage.aspx"></iframe>
</body>
</html>
Framed Page (sitting in mainWindow)
<html>
' LOADS OF STUFF INCLUDING:
<li id="clickThis">Click This</li>
</html>
So obviously what I am trying to do here is to run some code in the Parent Page when a user clicks the button in the child frame.
It needs to work live so if the page in the frame changes it is still captured by the parent (this element exists on all pages which will be loaded into the frame)
I have been able to run codes across iFrames, from parent to iFrame and from iFrame to iFrame following various other searches, but running from iFrame to parent is causing issues.
The above code does nothing, and neither do any of the other options I have tried!!
*Edit should add, all files are on the same server, the cross domain issue is not a problem
This works fine for me:
$('#myframe').contents().on('click', function () {
$('#result').text('clicked');
});​
If you want to capture the events just on a part of the content document, use .contents().find(<selector>) (<selector> being e.g. 'body') instead of just .contents()
Something to watch out for: All of your pages must exist under the same document.domain or the JavaScript calls will fail. You will see this surfaced as a security exception in your console. This happens due to same-origin policy.
Way, way way too late of an answer, but I had the same issue and the code below works in all browsers:
$('#myiframe').on('load', function () {
$(this).contents().on('click', function () {
alert('Click detected!');
})
})
It attaches onclick handler but only after iframe has been loaded. I am leaving this here for posterity and for anybody else looking for an answer to the same question.
In case you want to capture some data as well inside the iframe, this will work for you:
There is an iframe element with multiple images in it. The iframe is rendered inside of parent page. Whichever image you click inside the iframe, that image's id is sent back to the parent.
Inside iframe:
<script>
function vote(e) {
myMessage = e;
window.parent.postMessage(myMessage, "*");
return false;
}
</script>
<body><div>
<img id="Image1" src="file:///Users/abcd/Desktop/1.jpeg" width="200px" onclick="vote(this.id)">
</div></body>
Inside parent page:
</script>
function displayMessage (evt) {
var message = "You voted " + evt.data;
document.getElementById("received-message").innerHTML = message;
}
if (window.addEventListener) {
// For standards-compliant web browsers
window.addEventListener("message", displayMessage, false);
}
else {
window.attachEvent("onmessage", displayMessage);
}
</script>
<body><div id="received-message"></div></body>
This will not only capture the click but also capture the id of the element being clicked inside the iframe. Also, this does not require JQuery.
why are you using find to bind an event listener to an element that might not be there? Why not just try:
$('#mainWindow').ready(function()
{
$('#clickThis').on('click',function()
{
alert($(this).html());//or something
});
});
BTW: since your code is inside the ready callback of #mainWindow, there is no need to use $('#mainWindow') a second time, as this will scan the DOM tree a second time. Just use this or $(this), as it will reference the mainWindow element anyway.
If on doesn't work for you, don't forget to check if you have the latest version of jQuery included, too. That, too can be the reason why on isn't working.

Is it possible to override javascript in an iFrame from the parent? If so how?

I'm using the Telerik RadSpell control in one of our touchscreen applications. I've managed to style it just right however the darn thing uses window.alert and window.confirm for prompting the user if they want to keep changes etc.
I want to disable these alerts without having to pull apart and modify the telerik controls.
The issue is that the spellcheck dialog uses an iframe and I can't seem to override the window.confirm function inside the iframe.
Sample Code to test overriding confirm.
<!-- mainpage.htm -->
<html>
<head>
<script type="text/javascript">
window.confirm = function(msg){ alert(msg); }
confirm("Main Page Confirm");
</script>
</head>
<body>
<iframe src="./iframepage.htm" >
</iframe>
</body>
</html>
<!-- iframepage.htm -->
<html>
<head>
<script type="text/javascript">
confirm("iframe confirm");
</script>
</head>
<body>
Some content.
</body>
</html>
Results in
Is it possible to override the javascript in an iframe from the parent? If so how?
I just shared an easier solution in the first forum, which demonstrates how to override the cancelHandler and hide the confirm dialog.
For your convenience I am pasting the solution below:
I would propose an easier way to disable the popup and it is to override the cancelHandler function. To do that follow the steps below:
1) Create a JS file named dialog.js in the root of the web application and populate it with the following function:
Telerik.Web.UI.Spell.SpellDialog.prototype.cancelHandler = function (e) {
if (this._cancel.disabled) {
return $telerik.cancelRawEvent(e);
}
//changes will be applied only if spell handler response is received, text has changed
//and the user confirms
this.closeDialog(this._spellProcessor && this._spellProcessor.textChanged() && true);
return $telerik.cancelRawEvent(e);
}
2) Save the file and set the DialogsScriptFile property of RadSpell to point to this file, e.g.
3) Test the solution.
I hope this helps.
You can get a reference to the innerwindow using javascript IFF the frame is from the same exact domain as the parent.
//Get iframe element by getElementById, frames[0], or whatever way you want
var myFrame = document.getElementById("myFrame");
//Get the window of that frame, overwrite the confirm
myFrame.contentWindow.confirm = function(msg){ alert("I overwrote it! : " + msg); }
You should be able to:
document.getElementById('iframe').contentWindow.confirm = [this is confirm in the iframe];
Perhaps something like this might work nicely for you:
document.getElementById('iframe').contentWindow.confirm = window.confirm;
This would link the confirm of the iframe to the confirm of the parent, which is nice if you already have some handling for confirms in the parent.
Note that you also will want to add some handling for possible undefined objects.
var iframe = document.getElementById('iframe');
//iframe exists
if(iframe){
var iframe_window = document.getElementById('iframe').contentWindow;
//window exists (won't if frame hasn't loaded)
if(iframe_window){
iframe_window.confirm = window.confirm;
}
}
You can take a look at the following resources, which could be helpful for your scenario:
http://www.telerik.com/community/forums/aspnet-ajax/spell/how-do-i-turn-off-the-confirm-dialog.aspx
and
http://www.telerik.com/help/aspnet-ajax/spell-client-check-finished.html
They show how to remove the RadSpell confirm and alert popups.
Best regards,
Rumen

How to properly use postMessage to do cross-domain messaging with html5 and modern browsers ? I still get errors

I'm sure something is wrong here but I can't quite put a finger on it... The example here behaves just fine with no notices or errors on the console, so it means my browser supports cross domain messaging with html5 (of course it does, it's Chrome 14..).
My code does more or less the following: A script loaded in WebsiteA.com created an iframe and appends it to WebsiteA.com's document. The frame appended will contain WebsiteB.com and when it's loaded, it must send a message to it's parent, WebsiteA.com, to tell it that WebsiteB.com is ready to receive some JSON. When WebsiteA.com get's this message, it sends back the JSON.
So WebsiteA.com has a line just before </body> just like this: <script scr="http://WebsiteB.com/load-my-iframe.js" type="text/javascript"></script>, and inside the load-my-iframe.js, I have the following:
var child = document.createElement('iframe');
child.src = 'http://WebsiteB.com/child.html';
var body = document.getElementsByTagName('body')[0]
body.appendChild(child);
var SomeJSON = {"something" : "that WebsiteB should have"};
window.onmessage = function (e) {
if (e.origin === 'http://WebsiteB.com' && e.data === 'ready') {
e.source.postMessage(SomeJSON, 'http://WebsiteB.com');
}
}
So that creates the iframe element, appends it to WebsiteA.com's document and waits for it to say it's ready (I guess...). On WebsiteB.com, I have the file child.html that is src of the iframe loaded in WebsiteA.com's document, and that file has the following:
<!DOCTYPE html>
<html lang="pt">
<head>
<title>WebsiteB.com</title>
<script type="text/javascript">
window.onload = function () {
window.parent.postMessage('ready','*');
}
window.addEventListener('message', receiveMessage, false);
function receiveMessage(event) {
if (event.data.something === "that WebsiteB should have") {
document.write('It worked!');
}
}
</script>
</head>
<body>
</body>
</html>
And now the weird stuff:
Sadly I do not own WebsiteA.com and WebsiteB.com, but I managed to have this working between one top level domain and a subdomain (that ends with .no.de). It really does work, BUT in Google Chrome 14's console I get the classic Unsafe JavaScript attempt to access frame with URL http://WebsiteA.com/ from frame with URL http://WebsiteB.com/child.html. Domains, protocols and ports must match.. The example from html5demos works just fine without this error.
Why do I get this error and how do I get rid of it ?
I've just tried your code and this seems to work in Chrome. It uses jsfiddle and jsbin to pass messages between parent and child windows.
http://jsbin.com/oxesef/4/edit#preview
This is not the answer but could help you.
I use http://easyxdm.net/wp/ to avoid that kind of problems.
Carl

Categories