PhantomJS + jQuery -> Can't get image - javascript

PROBLEM: the function inside page.evaluate doesn't find any img (therefore, console.log(images.length) outputs 0); however, there are many images in the page, and some even have ids.
QUESTION: What's going on? Why $('img') doesn't find anything?
UPDATE 1: This is a <frame> problem. I had to switch to the frame in order to make the jQuery script correctly work.
DETAILS: I'm running a phantomjs script to access a webpage (link) and fetch all available images. It first saves a screenshot of the page just for comparison, and then it should through every <img> tag (using jQuery $('img')) and get the image dimensions and, using phantomjs's page.clipRect, it saves each image inside a folder.
var page = require('webpage').create();
var url = 'http://www.receita.fazenda.gov.br/pessoajuridica/cnpj/cnpjreva/cnpjreva_solicitacao.asp';
page.open(url, function (status) {
console.log("Status: " + status);
if (status === "success") {
page.render('example.png');
}
// Asynchronous call!
page.includeJs('http://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js', function () {
console.log('\n Evaluate Page \n');
// Sandboxed
var images = page.evaluate(function () {
var images = [];
function getImgDimensions($i) {
return {
top: $i.offset().top,
left: $i.offset().left,
width: $i.width(),
height: $i.height(),
}
}
$('img').each(function () {
var img = getImgDimensions($(this));
images.push(img);
});
return images;
});
console.log(images.length);
images.forEach(function (imageObj, index, array) {
page.clipRect = imageObj;
page.render('images/' + index + '.png');
});
// Exit the session
phantom.exit();
});
});

I've looked at the site. The img that you want is inside of an iframe. You first need to switch to it.
Use for example:
page.switchToChildFrame(0);
to switch to the first child frame. Do this before you call page.includeJs().
If you want to do something in the parent page afterwards, you would have to change back with page.switchToParentFrame();.

Related

jQuery each not functioning correctly

I have a slider with 10 slider elements. However, only 7 out of 10 elements are rendered, given my data structure contains 20 sets. The site is hosted here
The code in question
function populateCarousell(cdata) {
var x = 0; //debug
jQuery(".wslide-slides .wslide-slide").each(function() {
var single = cdata.shift();
var jcurrSlide = jQuery(this);
jcurrSlide.find(".wslide-caption-text").text(single.title);
jcurrSlide.find("a").attr('href', "https://carousell.com/p/" +single.id);
jcurrSlide.css({'background-image':Base64.decode('dXJs')+'('+single.primary_photo_full_url+')'});
jcurrSlide.css({'background-image':'contain'});
jcurrSlide.css({'background-position':'50% 50%'});
jcurrSlide.css({'background-repeat': 'no-repeat'});
x++; //debug
jcurrSlide.find(".wslide-slide-inner2").removeAttr('style').find("img").css({'display':'none'});
});
alert(x); //Outputs 7
}
which is activated by (to ensure page fully loaded)
function caroDataCallback(data) {
if(document.readyState != "complete" ) {
setTimeout(function() { caroDataCallback(data); }, 2000);
}
else{
populateCarousell(data);
}
}
Upon examination in Chrome, the results is
That's because your page is not fully loaded when you call populateCarousell(cdata) function in your javascript file. Try instead of using $(document).ready(), use the $(document).load() to make sure all the images are loaded before you initiate your carousel.
Update: Use $(window).on('load', function() { .. }); instead.
Hope this helps.

Add/inject image to DOM via PhantomJS

I'm trying to dynamically add an image to the DOM of a loaded page, but it's not showing up when rendering the page.
In page.evaluate in modify the DOM like this (excerpt):
page.open(url, function(status) {
...
window.setTimeout(function () {
...
page.evaluate(function() {
...
var myimg = document.createElement("img");
myimg.setAttribute('src', 'http://www.foobar.com/fooimage.png');
myimg.setAttribute('height', '41px');
myimg.setAttribute('width', '80px');
outerdiv.appendChild(myimg); // outerdiv is visible in the rendered output
document.body.appendChild(outerdiv);
...
}
page.render
Debugging page.content shows that it's successfully added, but page.render does not show it (only the outerdiv it's appended to). Instead of using an external URL src I also tried a base64 encoded string with no luck. I also omitted the path and stored the file inside PhantomJS' include path. None of this 3 seems to work.
I also have a window.timeout of 2000 so I don't think it's an issue of rendering the page before the PNG is loaded.
What would be the proper way to add the src? External URL, local file? Why isn't even adding a base64 encoded image working? Are any security limitations blocking what I'm trying to do? I'm running PhantomJS 1.9.0 btw.
I cannot reproduce your problem with this complete script.
var page = require('webpage').create();
var url = "http://phantomjs.org/img/phantomjs-logo.png";
page.open("http://example.com", function (status) {
if (status !== 'success') {
console.log('Unable to access network');
phantom.exit();
} else {
page.render("without.png");
page.evaluate(function(url){
var myimg = document.createElement("img");
myimg.setAttribute('src', url);
document.body.appendChild(myimg);
}, url);
setTimeout(function(){
page.render("with.png");
phantom.exit();
}, 5000);
}
});
I tried it with PhantomJS 1.9.0 and 1.9.8 with exactly the same result.
I came across the same problem. When I am using page.evaluate() to add images, they are not getting loaded, but content is getting changed.
If I do the same with page.content, images are getting loaded and working as expected.
var page = require('webpage').create();
page.onLoadFinished = function() {
console.log('');
console.log('load finished');
var render = page.render('evaluateTesting.png');
}
page.open('about:blank', function(status) {
console.log('');
console.log('open callback');
//page reloading after this line
page.content = '<!DOCTYPE html><head></head><body><img src="image path"></img></body>';
/*
* this is not causing page reload
* but content is changing
page.evaluate(function() {
console.log('');
console.log('evaluating');
var body = document.getElementsByTagName('body');
body = body[0];
body.innerHTML = '<img src="file:///home/ravitejay/projects/testappDup/sample.png"></img><br>';
})*/
console.log('content : '+page.content);
})

'load' event not firing when iframe is loaded in Chrome

I am trying to display a 'mask' on my client while a file is dynamically generated server side. Seems like the recommend work around for this (since its not ajax) is to use an iframe and listen from the onload or done event to determine when the file has actually shipped to the client from the server.
here is my angular code:
var url = // url to my api
var e = angular.element("<iframe style='display:none' src=" + url + "></iframe>");
e.load(function() {
$scope.$apply(function() {
$scope.exporting = false; // this will remove the mask/spinner
});
});
angular.element('body').append(e);
This works great in Firefox but no luck in Chrome. I have also tried to use the onload function:
e.onload = function() { //unmask here }
But I did not have any luck there either.
Ideas?
Unfortunately it is not possible to use an iframe's onload event in Chrome if the content is an attachment. This answer may provide you with an idea of how you can work around it.
I hate this, but I couldn't find any other way than checking whether it is still loading or not except by checking at intervals.
var timer = setInterval(function () {
iframe = document.getElementById('iframedownload');
var iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
// Check if loading is complete
if (iframeDoc.readyState == 'complete' || iframeDoc.readyState == 'interactive') {
loadingOff();
clearInterval(timer);
return;
}
}, 4000);
You can do it in another way:
In the main document:
function iframeLoaded() {
$scope.$apply(function() {
$scope.exporting = false; // this will remove the mask/spinner
});
}
var url = // url to my api
var e = angular.element("<iframe style='display:none' src=" + url + "></iframe>");
angular.element('body').append(e);
In the iframe document (this is, inside the html of the page referenced by url)
window.onload = function() {
parent.iframeLoaded();
}
This will work if the main page, and the page inside the iframe are in the same domain.
Actually, you can access the parent through:
window.parent
parent
//and, if the parent is the top-level document, and not inside another frame
top
window.top
It's safer to use window.parent since the variables parent and top could be overwritten (usually not intended).
you have to consider 2 points:
1- first of all, if your url has different domain name, it is not possible to do this except when you have access to the other domain to add the Access-Control-Allow-Origin: * header, to fix this go to this link.
2- but if it has the same domain or you have added Access-Control-Allow-Origin: * to the headers of your domain, you can do what you want like this:
var url = // url to my api
var e = angular.element("<iframe style='display:none' src=" + url + "></iframe>");
angular.element(document.body).append(e);
e[0].contentWindow.onload = function() {
$scope.$apply(function() {
$scope.exporting = false; // this will remove the mask/spinner
});
};
I have done this in all kinds of browsers.
I had problems with the iframe taking too long to load. The iframe registered as loaded while the request wasn't handled. I came up with the following solution:
JS
Function:
function iframeReloaded(iframe, callback) {
let state = iframe.contentDocument.readyState;
let checkLoad = setInterval(() => {
if (state !== iframe.contentDocument.readyState) {
if (iframe.contentDocument.readyState === 'complete') {
clearInterval(checkLoad);
callback();
}
state = iframe.contentDocument.readyState;
}
}, 200)
}
Usage:
iframeReloaded(iframe[0], function () {
console.log('Reloaded');
})
JQuery
Function:
$.fn.iframeReloaded = function (callback) {
if (!this.is('iframe')) {
throw new Error('The element is not an iFrame, please provide the correct element');
}
let iframe = this[0];
let state = iframe.contentDocument.readyState;
let checkLoad = setInterval(() => {
if (state !== iframe.contentDocument.readyState) {
if (iframe.contentDocument.readyState === 'complete') {
clearInterval(checkLoad);
callback();
}
state = iframe.contentDocument.readyState;
}
}, 200)
}
Usage:
iframe.iframeReloaded(function () {
console.log('Reloaded');
})
I've just noticed that Chrome is not always firing the load event for the main page so this could have an effect on iframes too as they are basically treated the same way.
Use Dev Tools or the Performance api to check if the load event is being fired at all.
I just checked http://ee.co.uk/ and if you open the console and enter window.performance.timing you'll find the entries for domComplete, loadEventStart and loadEventEnd are 0 - at least at this current time:)
Looks like there is a problem with Chrome here - I've checked it on 2 PCs using the latest version 31.0.1650.63.
Update: checked ee again and load event fired but not on subsequent reloads so this is intermittent and may possibly be related to loading errors on their site. But the load event should fire whatever.
This problem has occurred on 5 or 6 sites for me now in the last day since I noticed my own site monitoring occasionally failed. Only just pinpointed the cause to this. I need some beauty sleep then I'll investigate further when I'm more awake.

Override "Error Loading Page" for network failure in js file

I have JQuery Mobile-1.0.js file.
// Load a page into the DOM.
$.mobile.loadPage = function (url, options) {
// This function uses deferred notifications to let callers
// know when the page is done loading, or if an error has occurred.
var deferred = $.Deferred(),
// The default loadPage options with overrides specified by
// the caller.
settings = $.extend({}, $.mobile.loadPage.defaults, options),
// The DOM element for the page after it has been loaded.
page = null,
// If the reloadPage option is true, and the page is already
// in the DOM, dupCachedPage will be set to the page element
// so that it can be removed after the new version of the
// page is loaded off the network.
dupCachedPage = null,
// determine the current base url
findBaseWithDefault = function () {
var closestBase = ($.mobile.activePage && getClosestBaseUrl($.mobile.activePage));
return closestBase || documentBase.hrefNoHash;
},
// The absolute version of the URL passed into the function. This
// version of the URL may contain dialog/subpage params in it.
absUrl = path.makeUrlAbsolute(url, findBaseWithDefault());
// If the caller provided data, and we're using "get" request,
// append the data to the URL.
if (settings.data && settings.type === "get") {
absUrl = path.addSearchParams(absUrl, settings.data);
settings.data = undefined;
}
// If the caller is using a "post" request, reloadPage must be true
if (settings.data && settings.type === "post") {
settings.reloadPage = true;
}
// The absolute version of the URL minus any dialog/subpage params.
// In otherwords the real URL of the page to be loaded.
var fileUrl = path.getFilePath(absUrl),
// The version of the Url actually stored in the data-url attribute of
// the page. For embedded pages, it is just the id of the page. For pages
// within the same domain as the document base, it is the site relative
// path. For cross-domain pages (Phone Gap only) the entire absolute Url
// used to load the page.
dataUrl = path.convertUrlToDataUrl(absUrl);
// Make sure we have a pageContainer to work with.
settings.pageContainer = settings.pageContainer || $.mobile.pageContainer;
// Check to see if the page already exists in the DOM.
page = settings.pageContainer.children(":jqmData(url='" + dataUrl + "')");
// If we failed to find the page, check to see if the url is a
// reference to an embedded page. If so, it may have been dynamically
// injected by a developer, in which case it would be lacking a data-url
// attribute and in need of enhancement.
if (page.length === 0 && dataUrl && !path.isPath(dataUrl)) {
page = settings.pageContainer.children("#" + dataUrl)
.attr("data-" + $.mobile.ns + "url", dataUrl);
}
// If we failed to find a page in the DOM, check the URL to see if it
// refers to the first page in the application. If it isn't a reference
// to the first page and refers to non-existent embedded page, error out.
if (page.length === 0) {
if ($.mobile.firstPage && path.isFirstPageUrl(fileUrl)) {
// Check to make sure our cached-first-page is actually
// in the DOM. Some user deployed apps are pruning the first
// page from the DOM for various reasons, we check for this
// case here because we don't want a first-page with an id
// falling through to the non-existent embedded page error
// case. If the first-page is not in the DOM, then we let
// things fall through to the ajax loading code below so
// that it gets reloaded.
if ($.mobile.firstPage.parent().length) {
page = $($.mobile.firstPage);
}
} else if (path.isEmbeddedPage(fileUrl)) {
deferred.reject(absUrl, options);
return deferred.promise();
}
}
// Reset base to the default document base.
if (base) {
base.reset();
}
// If the page we are interested in is already in the DOM,
// and the caller did not indicate that we should force a
// reload of the file, we are done. Otherwise, track the
// existing page as a duplicated.
if (page.length) {
if (!settings.reloadPage) {
enhancePage(page, settings.role);
deferred.resolve(absUrl, options, page);
return deferred.promise();
}
dupCachedPage = page;
}
var mpc = settings.pageContainer,
pblEvent = new $.Event("pagebeforeload"),
triggerData = { url: url, absUrl: absUrl, dataUrl: dataUrl, deferred: deferred, options: settings };
// Let listeners know we're about to load a page.
mpc.trigger(pblEvent, triggerData);
// If the default behavior is prevented, stop here!
if (pblEvent.isDefaultPrevented()) {
return deferred.promise();
}
if (settings.showLoadMsg) {
// This configurable timeout allows cached pages a brief delay to load without showing a message
var loadMsgDelay = setTimeout(function () {
$.mobile.showPageLoadingMsg();
}, settings.loadMsgDelay),
// Shared logic for clearing timeout and removing message.
hideMsg = function () {
// Stop message show timer
clearTimeout(loadMsgDelay);
// Hide loading message
$.mobile.hidePageLoadingMsg();
};
}
if (!($.mobile.allowCrossDomainPages || path.isSameDomain(documentUrl, absUrl))) {
deferred.reject(absUrl, options);
} else {
// Load the new page.
$.ajax({
url: fileUrl,
type: settings.type,
data: settings.data,
dataType: "html",
success: function (html, textStatus, xhr) {
//pre-parse html to check for a data-url,
//use it as the new fileUrl, base path, etc
var all = $("<div></div>"),
//page title regexp
newPageTitle = html.match(/<title[^>]*>([^<]*)/) && RegExp.$1,
// TODO handle dialogs again
pageElemRegex = new RegExp("(<[^>]+\\bdata-" + $.mobile.ns + "role=[\"']?page[\"']?[^>]*>)"),
dataUrlRegex = new RegExp("\\bdata-" + $.mobile.ns + "url=[\"']?([^\"'>]*)[\"']?");
// data-url must be provided for the base tag so resource requests can be directed to the
// correct url. loading into a temprorary element makes these requests immediately
if (pageElemRegex.test(html)
&& RegExp.$1
&& dataUrlRegex.test(RegExp.$1)
&& RegExp.$1) {
url = fileUrl = path.getFilePath(RegExp.$1);
}
if (base) {
base.set(fileUrl);
}
//workaround to allow scripts to execute when included in page divs
all.get(0).innerHTML = html;
page = all.find(":jqmData(role='page'), :jqmData(role='dialog')").first();
//if page elem couldn't be found, create one and insert the body element's contents
if (!page.length) {
page = $("<div data-" + $.mobile.ns + "role='page'>" + html.split(/<\/?body[^>]*>/gmi)[1] + "</div>");
}
if (newPageTitle && !page.jqmData("title")) {
if (~newPageTitle.indexOf("&")) {
newPageTitle = $("<div>" + newPageTitle + "</div>").text();
}
page.jqmData("title", newPageTitle);
}
//rewrite src and href attrs to use a base url
if (!$.support.dynamicBaseTag) {
var newPath = path.get(fileUrl);
page.find("[src], link[href], a[rel='external'], :jqmData(ajax='false'), a[target]").each(function () {
var thisAttr = $(this).is('[href]') ? 'href' :
$(this).is('[src]') ? 'src' : 'action',
thisUrl = $(this).attr(thisAttr);
// XXX_jblas: We need to fix this so that it removes the document
// base URL, and then prepends with the new page URL.
//if full path exists and is same, chop it - helps IE out
thisUrl = thisUrl.replace(location.protocol + '//' + location.host + location.pathname, '');
if (!/^(\w+:|#|\/)/.test(thisUrl)) {
$(this).attr(thisAttr, newPath + thisUrl);
}
});
}
//append to page and enhance
// TODO taging a page with external to make sure that embedded pages aren't removed
// by the various page handling code is bad. Having page handling code in many
// places is bad. Solutions post 1.0
page
.attr("data-" + $.mobile.ns + "url", path.convertUrlToDataUrl(fileUrl))
.attr("data-" + $.mobile.ns + "external-page", true)
.appendTo(settings.pageContainer);
// wait for page creation to leverage options defined on widget
page.one('pagecreate', $.mobile._bindPageRemove);
enhancePage(page, settings.role);
// Enhancing the page may result in new dialogs/sub pages being inserted
// into the DOM. If the original absUrl refers to a sub-page, that is the
// real page we are interested in.
if (absUrl.indexOf("&" + $.mobile.subPageUrlKey) > -1) {
page = settings.pageContainer.children(":jqmData(url='" + dataUrl + "')");
}
//bind pageHide to removePage after it's hidden, if the page options specify to do so
// Remove loading message.
if (settings.showLoadMsg) {
hideMsg();
}
// Add the page reference and xhr to our triggerData.
triggerData.xhr = xhr;
triggerData.textStatus = textStatus;
triggerData.page = page;
// Let listeners know the page loaded successfully.
settings.pageContainer.trigger("pageload", triggerData);
deferred.resolve(absUrl, options, page, dupCachedPage);
},
error: function (xhr, textStatus, errorThrown) {
//set base back to current path
if (base) {
base.set(path.get());
}
// Add error info to our triggerData.
triggerData.xhr = xhr;
triggerData.textStatus = textStatus;
triggerData.errorThrown = errorThrown;
var plfEvent = new $.Event("pageloadfailed");
// Let listeners know the page load failed.
settings.pageContainer.trigger(plfEvent, triggerData);
// If the default behavior is prevented, stop here!
// Note that it is the responsibility of the listener/handler
// that called preventDefault(), to resolve/reject the
// deferred object within the triggerData.
if (plfEvent.isDefaultPrevented()) {
return;
}
// Remove loading message.
if (settings.showLoadMsg) {
// Remove loading message.
hideMsg();
//show error message
$("<div class='ui-loader ui-overlay-shadow ui-body-e ui-corner-all'><h1>" + $.mobile.pageLoadErrorMessage + "</h1></div>")
.css({ "display": "block", "opacity": 0.96, "top": $window.scrollTop() + 100 })
.appendTo(settings.pageContainer)
.delay(800)
.fadeOut(400, function () {
$(this).remove();
});
}
deferred.reject(absUrl, options);
}
});
}
return deferred.promise();
};
This is the code for showing an error message "Error Loading Page" for error in page. Here i want to show alert message for net connection failure as "Please check your net connection" instead of the below image.
Note: I dont want to change the pageloaderrormessage. want to stop to get the page error messages, instead of that i will enable my network error condition as in Show Network Error in android. If the user pressed "Ok" in alert dialog i'll navigate them into Reload.html.
Please tell me where i can check that condition and where i have to change the error message?
As both #shkschneider and #codemonkey have suggested you need to set this option on mobileinit
Example:
$(document).bind("mobileinit", function(){
$.mobile.pageLoadErrorMessage = "Please check your net connection";
});
Linking the jQM 1.0.1 docs:
http://jquerymobile.com/demos/1.0.1/docs/api/globalconfig.html
Here is a example:
http://jquerymobile.com/demos/1.0.1/docs/config/pageLoadErrorMessage.html ( click the "or Try this broken link" button )
Now if you have the ability to upgrade jQM to 1.1.1 you might try something like this:
//use theme swatch "b", a custom message, and no spinner
$.mobile.showPageLoadingMsg("b", "Please check your net connection", true);
// hide after delay
setTimeout( $.mobile.hidePageLoadingMsg, 1500 );
Docs:
http://jquerymobile.com/demos/1.1.1/docs/api/methods.html
UPDATE:
Another thought is to use a plugin to achieve something like you want, Does something like this work?
http://dev.jtsage.com/jQM-SimpleDialog/demos/bool.html
Simply use:
$(document).bind("mobileinit", function(){
$.mobile.pageLoadErrorMessage("Please check your netconnection");
});
http://jquerymobile.com/test/docs/api/globalconfig.html
Set the pageLoadErrorMessage as described here http://jquerymobile.com/demos/1.1.1/docs/api/globalconfig.html
EDIT
If you want to handle the behaviour in a custom way, set loadingMessage to false. This prevents the loading message from being displayed. You can bind to the pageloadfailed (described here http://jquerymobile.com/demos/1.1.1/docs/api/events.html) and add add your custom handling logic in the event handler.

jQuery .ready in a dynamically inserted iframe

We are using jQuery thickbox to dynamically display an iframe when someone clicks on a picture. In this iframe, we are using galleria a javascript library to display multiple pictures.
The problem seems to be that $(document).ready in the iframe seems to be fired too soon and the iframe content isn't even loaded yet, so galleria code is not applied properly on the DOM elements. $(document).ready seems to use the iframe parent ready state to decide if the iframe is ready.
If we extract the function called by document ready in a separate function and call it after a timeout of 100 ms. It works, but we can't take the chance in production with a slow computer.
$(document).ready(function() { setTimeout(ApplyGalleria, 100); });
My question: which jQuery event should we bind to to be able to execute our code when the dynamic iframe is ready and not just it's a parent?
I answered a similar question (see Javascript callback when IFRAME is finished loading?).
You can obtain control over the iframe load event with the following code:
function callIframe(url, callback) {
$(document.body).append('<IFRAME id="myId" ...>');
$('iframe#myId').attr('src', url);
$('iframe#myId').load(function() {
callback(this);
});
}
In dealing with iframes I found good enough to use load event instead of document ready event.
Using jQuery 1.3.2 the following worked for me:
$('iframe').ready(function() {
$('body', $('iframe').contents()).html('Hello World!');
});
REVISION:!
Actually the above code sometimes looks like it works in Firefox, never looks like it works in Opera.
Instead I implemented a polling solution for my purposes. Simplified down it looks like this:
$(function() {
function manipIframe() {
el = $('body', $('iframe').contents());
if (el.length != 1) {
setTimeout(manipIframe, 100);
return;
}
el.html('Hello World!');
}
manipIframe();
});
This doesn't require code in the called iframe pages. All code resides and executes from the parent frame/window.
In IFrames I usually solve this problem by putting a small script to the very end of the block:
<body>
The content of your IFrame
<script type="text/javascript">
//<![CDATA[
fireOnReadyEvent();
parent.IFrameLoaded();
//]]>
</script>
</body>
This work most of the time for me. Sometimes the simplest and most naive solution is the most appropriate.
Following DrJokepu's and David Murdoch idea I implemented a more complete version.
It requires jQuery on both the parent and iframe and the iframe to be in your control.
iframe code:
var iframe = window.frameElement;
if (iframe){
iframe.contentDocument = document;//normalization: some browsers don't set the contentDocument, only the contentWindow
var parent = window.parent;
$(parent.document).ready(function(){//wait for parent to make sure it has jQuery ready
var parent$ = parent.jQuery;
parent$(iframe).trigger("iframeloading");
$(function(){
parent$(iframe).trigger("iframeready");
});
$(window).load(function(){//kind of unnecessary, but here for completion
parent$(iframe).trigger("iframeloaded");
});
$(window).unload(function(e){//not possible to prevent default
parent$(iframe).trigger("iframeunloaded");
});
$(window).on("beforeunload",function(){
parent$(iframe).trigger("iframebeforeunload");
});
});
}
parent test code:
$(function(){
$("iframe").on("iframeloading iframeready iframeloaded iframebeforeunload iframeunloaded", function(e){
console.log(e.type);
});
});
Found the solution to the problem.
When you click on a thickbox link that open a iframe, it insert an iframe with an id of TB_iframeContent.
Instead of relying on the $(document).ready event in the iframe code, I just have to bind to the load event of the iframe in the parent document:
$('#TB_iframeContent', top.document).load(ApplyGalleria);
This code is in the iframe but binds to an event of a control in the parent document. It works in FireFox and IE.
This function from this answer is the best way to handle this as $.ready explicitly fails for iframes. Here's the decision not to support this.
The load event also doesn't fire if the iframe has already loaded. Very frustrating that this remains a problem in 2020!
function onIframeReady($i, successFn, errorFn) {
try {
const iCon = $i.first()[0].contentWindow,
bl = "about:blank",
compl = "complete";
const callCallback = () => {
try {
const $con = $i.contents();
if($con.length === 0) { // https://git.io/vV8yU
throw new Error("iframe inaccessible");
}
successFn($con);
} catch(e) { // accessing contents failed
errorFn();
}
};
const observeOnload = () => {
$i.on("load.jqueryMark", () => {
try {
const src = $i.attr("src").trim(),
href = iCon.location.href;
if(href !== bl || src === bl || src === "") {
$i.off("load.jqueryMark");
callCallback();
}
} catch(e) {
errorFn();
}
});
};
if(iCon.document.readyState === compl) {
const src = $i.attr("src").trim(),
href = iCon.location.href;
if(href === bl && src !== bl && src !== "") {
observeOnload();
} else {
callCallback();
}
} else {
observeOnload();
}
} catch(e) {
errorFn();
}
}
Basically what others have already posted but IMHO a bit cleaner:
$('<iframe/>', {
src: 'https://example.com/',
load: function() {
alert("loaded")
}
}).appendTo('body');
Try this,
<iframe id="testframe" src="about:blank" onload="if (testframe.location.href != 'about:blank') testframe_loaded()"></iframe>
All you need to do then is create the JavaScript function testframe_loaded().
I'm loading the PDF with jQuery ajax into browser cache. Then I create embedded element with data already in browser cache. I guess it will work with iframe too.
var url = "http://example.com/my.pdf";
// show spinner
$.mobile.showPageLoadingMsg('b', note, false);
$.ajax({
url: url,
cache: true,
mimeType: 'application/pdf',
success: function () {
// display cached data
$(scroller).append('<embed type="application/pdf" src="' + url + '" />');
// hide spinner
$.mobile.hidePageLoadingMsg();
}
});
You have to set your http headers correctly as well.
HttpContext.Response.Expires = 1;
HttpContext.Response.Cache.SetNoServerCaching();
HttpContext.Response.Cache.SetAllowResponseInBrowserHistory(false);
HttpContext.Response.CacheControl = "Private";
This was the exact issue I ran into with our client. I created a little jquery plugin that seems to work for iframe readiness. It uses polling to check the iframe document readyState combined with the inner document url combined with the iframe source to make sure the iframe is in fact "ready".
The issue with "onload" is that you need access to the actual iframe being added to the DOM, if you don't then you need to try to catch the iframe loading which if it is cached then you may not. What I needed was a script that could be called anytime, and determine whether or not the iframe was "ready" or not.
Here's the question:
Holy grail for determining whether or not local iframe has loaded
and here's the jsfiddle I eventually came up with.
https://jsfiddle.net/q0smjkh5/10/
In the jsfiddle above, I am waiting for onload to append an iframe to the dom, then checking iframe's inner document's ready state - which should be cross domain because it's pointed to wikipedia - but Chrome seems to report "complete". The plug-in's iready method then gets called when the iframe is in fact ready. The callback tries to check the inner document's ready state again - this time reporting a cross domain request (which is correct) - anyway it seems to work for what I need and hope it helps others.
<script>
(function($, document, undefined) {
$.fn["iready"] = function(callback) {
var ifr = this.filter("iframe"),
arg = arguments,
src = this,
clc = null, // collection
lng = 50, // length of time to wait between intervals
ivl = -1, // interval id
chk = function(ifr) {
try {
var cnt = ifr.contents(),
doc = cnt[0],
src = ifr.attr("src"),
url = doc.URL;
switch (doc.readyState) {
case "complete":
if (!src || src === "about:blank") {
// we don't care about empty iframes
ifr.data("ready", "true");
} else if (!url || url === "about:blank") {
// empty document still needs loaded
ifr.data("ready", undefined);
} else {
// not an empty iframe and not an empty src
// should be loaded
ifr.data("ready", true);
}
break;
case "interactive":
ifr.data("ready", "true");
break;
case "loading":
default:
// still loading
break;
}
} catch (ignore) {
// as far as we're concerned the iframe is ready
// since we won't be able to access it cross domain
ifr.data("ready", "true");
}
return ifr.data("ready") === "true";
};
if (ifr.length) {
ifr.each(function() {
if (!$(this).data("ready")) {
// add to collection
clc = (clc) ? clc.add($(this)) : $(this);
}
});
if (clc) {
ivl = setInterval(function() {
var rd = true;
clc.each(function() {
if (!$(this).data("ready")) {
if (!chk($(this))) {
rd = false;
}
}
});
if (rd) {
clearInterval(ivl);
clc = null;
callback.apply(src, arg);
}
}, lng);
} else {
clc = null;
callback.apply(src, arg);
}
} else {
clc = null;
callback.apply(this, arguments);
}
return this;
};
}(jQuery, document));
</script>

Categories

Resources