I've created this code to print data from an iFrame
function (data) {
var frame = $("<iframe>", {
name: "iFrame",
class: "printFrame"
});
frame.appendTo("body");
var head = $("<head></head>");
_.each($("head link[rel=stylesheet]"), function (link) {
var csslink = $("<link/>", { rel: "stylesheet", href: $(link).prop("href") })
head.append(csslink);
;});
frame.contents().find("head")
.replaceWith(head);
frame.contents().find("body")
.append(this.html());
window.frames["iFrame"].focus();
window.frames["iFrame"].print();
}
This creates an iFrame, adds a head to where it sets all the css links that are needed for this website. Then it creates the body.
Trouble is, the styling won't get applied to the print, unless I break at line frame.contents().find("head").replaceWith(head), which means that something in that part is running asynchronously.
Question is, can I somehow get the code to wait for a short while before running that line, or is there perhaps another way to do this? Unfortunately I'm not all that familiar with iFrames, so I have no clue what it's trying to do there.
This turned out to be a real hassle. I've always been reluctant to using iframes, but since there are many resources saying that using an iframe for printing more stuff than what's on the screen, we figured we'd give it a try.
This was instead solved by putting the data inside a hidden div which then was shown before a window.print(). At the same time, all other elements on the page were given a "hidden-print" class which is a class we're already using to hide elements for prints.
This might not be as elegant for the user (The div will show briefly before the user exits the print dialogue), but it's a way more simpler code to use and manage.
I think you could / should move the last focus() and print() calls to a onload handler for the iframe, to get it to happen after styles are loaded and applied.
I've just run into the same issue and did the following:
setTimeout(() => {
window.frames["iFrame"].focus();
window.frames["iFrame"].print();
}, 500)
This appears to have sorted it for me. I hate using timeout and the length is guess work at best but as it's not system critical it's something I can run with for now.
Related
I have a application that loads CSS styles dynamic based on user preference. I use requirejs to load these like:
require(['css!dir/styles'], function(){ .... });
this works great but I don't want to show the screen until all the styles have fully initialized.
I've added a CSS class to the body of the page called hide-page and then remove that class when the callback occurs. Like:
setTimeout(function() { $(document.body).removeClass('hide-page'); }, 100);
but even with the settimeout, the page still loads jumbled until everything has initialized. I was thinking about doing a setInterval and checking if a particular style has been applied to a node like:
setInterval(function(){
if($(document.body).style('background'') === "#FFFFFF"){
$(document.body).removeClass('hide-page');
}
}, 10);
but thats kinda hackey. Is there a better solution anyone has to accomplish this?
You don't say how you're hiding the page content, but that could be the problem if you're using display:none.
Try visibility:hidden instead. This will allow the browser to allocate the space needed to construct the page, so you shouldn't see the jumbled FOUC.
I have a question about javascript/html.
First, I have this:
var post = document.body.getElementsByClassName("post");
var x=post[i].getElementsByClassName("MyDiv")[0].innerHTML;
I get from the debugger that x is not defined, it doesn't exists.
This javascript function runs onload of the body. I am sure that I gave the right classnames in my javascript, so it should find my div.
So, I read somewhere that sometimes javascript does not find an element because it is not yet there, it is not yet created in the browser ( whatever that means).
Is it possible that my function can't find the div with that classname because of this reason?
Is there a solution?
So, I read somewhere that sometimes javascript does not find an element because it is not yet there, it is not yet created in the browser ( whatever that means).
Browsers create the DOM progressively as they get the markup. When a script element is encountered, all processing of the markup stops (except where defer and async have an effect) while the script is run. If the script attempts to access an element that hasn't been created yet (probably because its markup hasn't been processed yet) then it won't be found.
This javascript function runs onload of the body.
If that means you are using something like:
<body onload="someFn()"...>
or perhaps
<script>
window.onload = function() {
someFn();
...
}
</script>
then when the function is called, all DOM nodes are available. Some, like images, may not be fully loaded, but their elements have been created.
If it means you have the script in the body and aren't using the load event, you should move the script to the bottom of the page (e.g. just before the closing body tag) and see if that fixes the issue.
Okay, instead of calling functions with
body onload, use jQuery's ready() function, or, if you don't want to use jQuery, you can use pure javascript, but this is up to you:
// jQuery
$(document).ready(function() {
var post = document.getElementsByClassName("post"),
x = post[i].getElementsByClassName("MyDiv")[0].innerHTML;
});
// JavaScript
window.onload = function initialization() {
var post = document.getElementsByClassName("post"),
x = post[i].getElementsByClassName("MyDiv")[0].innerHTML;
}
A few side notes, I don't know what the use of innerHTML
is, and also if you're doing a for loop with i then definitely
post that code, that's kind of important.
After some discussion, my answer seems to have worked for you, but you can also place your script at the end of your body tag as #RobG has suggested.
I am trying to load Skyscanner API dynamically but it doesn't seem to work. I tried every possible way I could think of and all it happens the content disappears.
I tried console.log which gives no results; I tried elements from chrome's developers tools and while all the content's css remains the same, still the content disappears (I thought it could be adding display:none on the html/body sort of). I tried all Google's asynch tricks, yet again blank page. I tried all js plugins for async loading with still the same results.
Skyscanner's API documentation is poor and while they offer a callback it doesn't work the way google's API's callback do.
Example: http://jsfiddle.net/7TWYC/
Example with loading API in head section: http://jsfiddle.net/s2HkR/
So how can I load the api on button click or async? Without the file being in the HEAD section. If there is a way to prevent the document.write to make the page blank or any other way. I wouldn't mind using plain js, jQuery or PHP.
EDIT:
I've set a bounty to 250 ontop of the 50 I had previously.
Orlando Leite answered a really close idea on how to make this asynch api load although some features doesn't work such as selecting dates and I am not able to set styling.
I am looking for an answer of which I will be able to use all the features so that it works as it would work if it was loading on load.
Here is the updated fiddle by Orlando: http://jsfiddle.net/cxysA/12/
-
EDIT 2 ON Gijs ANSWER:
Gijs mentioned two links onto overwriting document.write. That sounds an awesome idea but I think it is not possible to accomplish what I am trying.
I used John's Resig way to prevent document.write of which can be found here: http://ejohn.org/blog/xhtml-documentwrite-and-adsense/
When I used this method, I load the API successfuly but the snippets.js file is not loading at all.
Fiddle: http://jsfiddle.net/9HX7N/
I belive what you want is it:
function loadSkyscanner()
{
function loaded()
{
t.skyscanner.load('snippets', '1', {'nocss' : true});
var snippet = new t.skyscanner.snippets.SearchPanelControl();
snippet.setCurrency('GBP');
snippet.setDeparture('uk');
snippet.draw(document.getElementById('snippet_searchpanel'));
}
var t = document.getElementById('sky_loader').contentWindow;
var head = t.document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.type = 'text/javascript';
script.onreadystatechange= function() {
if(this.readyState == 'complete') loaded();
}
script.onload= loaded;
script.src= 'http://api.skyscanner.net/api.ashx?key=PUT_HERE_YOUR_SKYSCANNER_API_KEY';
head.appendChild(script);
}
$("button").click(function(e)
{
loadSkyscanner();
});
It's load skyscanner in iframe#sky_loader, after call loaded function to create the SearchPanelControl. But in the end, snippet draws in the main document. It's really a bizarre workaround, but it works.
The only restriction is, you need a iframe. But you can hide it using display:none.
A working example
EDIT
Sorry guy, I didn't see it. Now we can see how awful is skyscanner API. It puts two divs to make the autocomplete, but not relative to the element you call to draw, but the document.
When a script is loaded in a iframe, document is the iframe document.
There is a solution, but I don't recommend, is really a workaround:
function loadSkyscanner()
{
var t;
this.skyscanner;
var iframe = $("<iframe id=\"sky_loader\" src=\"http://fiddle.jshell.net/orlleite/2TqDu/6/show/\"></iframe>");
function realWorkaround()
{
var tbody = t.document.getElementsByTagName("body")[0];
var body = document.getElementsByTagName("body")[0];
while( tbody.children.length != 0 )
{
var temp = tbody.children[0];
tbody.removeChild( temp );
body.appendChild( temp );
}
}
function snippetLoaded()
{
skyscanner = t.skyscanner;
var snippet = new skyscanner.snippets.SearchPanelControl();
snippet.setCurrency('GBP');
snippet.setDeparture('uk');
snippet.draw(document.getElementById('snippet_searchpanel'));
setTimeout( realWorkaround, 2000 );
}
var loaded = function()
{
console.log( "loaded" );
t = document.getElementById('sky_loader').contentWindow;
t.onLoadSnippets( snippetLoaded );
}
$("body").append(iframe);
iframe.load(loaded);
}
$("button").click(function(e)
{
loadSkyscanner();
});
Load a iframe with another html who loads and callback when the snippet is loaded. After loaded create the snippet where you want and after set a timeout because we can't know when the SearchPanelControl is loaded. This realWorkaround move the autocomplete divs to the main document.
You can see a work example here
The iframe loaded is this
EDIT
Fixed the bug you found and updated the link.
the for loop has gone and added a while, works better now.
while( tbody.children.length != 0 )
{
var temp = tbody.children[0];
tbody.removeChild( temp );
body.appendChild( temp );
}
For problematic cases like this, you can just overwrite document.write. Hacky as hell, but it works and you get to decide where all the content goes. See eg. this blogpost by John Resig. This ignores IE, but with a bit of work the trick works in IE as well, see eg. this blogpost.
So, I'd suggest overwriting document.write with your own function, batch up the output where necessary, and put it where you like (eg. in a div at the bottom of your <body>'). That should prevent the script from nuking your page's content.
Edit: OK, so I had/took some time to look into this script. For future reference, use something like http://jsbeautifier.org/ to investigate third-party scripts. Much easier to read that way. Fortunately, there is barely any obfuscation/minification at all, and so you have a supplement for their API documentation (which I was unable to find, by the way -- I only found 'code wizards', which I had no interest in).
Here's an almost-working example: http://jsfiddle.net/a8q2s/1/
Here's the steps I took:
override document.write. This needs to happen before you load the initial script. Your replacement function should append their string of code into the DOM. Don't call the old document.write, that'll just get you errors and won't do what you want anyway. In this case you're lucky because all the content is in a single document.write call (check the source of the initial script). If this weren't the case, you'd have to batch everything up until the HTML they'd given you was valid and/or you were sure there was nothing else coming.
load the initial script on the button click with jQuery's $.getScript or equivalent. Pass a callback function (I used a named function reference for clarity, but you can inline it if you prefer).
Tell Skyscanner to load the module.
Edit #2: Hah, they have an API (skyscanner.loadAndWait) for getting a callback once their script has loaded. Using that works:
http://jsfiddle.net/a8q2s/3/
(note: this still seems to use a timeout loop internally)
In the skyrunner.js file they are using document.write to make the page blank on load call back... So here are some consequences in your scenario..
This is making page blank when you click on button.
So, it removes everything from page even 'jQuery.js' that is why call back is not working.. i.e main function is cannot be invoked as this is written using jQuery.
And you have missed a target 'div' tag with id = map(according to the code). Actually this is the target where map loads.
Another thing i have observed is maps is not actually a div in current context, that is maps api to load.
Here you must go with the Old school approach, That is.. You should include your skyrunner.js file at the top of the head content.
So try downloading that file and include in head tag.
Thanks
I am coding a big website but I have cut down my problem into the following tiny html file:
http://dl.dropbox.com/u/3224566/test.html
The problem is that if I (re)load with JQuery a content that features a facebook code, the latter won't appear, even if I reload the script (leading to a duplication of that all.js script, which is another issue).
How can I fix this?
Regards,
Quentin
Use the FB.XFBML.parse() docs after you load the new content
function loadPage() {
$('#test').load('test.html #test', function() {
FB.XFBML.parse( );
}).fadeOut('slow').fadeIn('slow');
}
Note, that loading a fragment with id test in a div with id test will create multiple (two) elements with the same id (nested in each other) in the page, which should never happen as it is invalid.
To avoid this use the more verbose $.get method
$.get('test.html',
function(data) {
var temp = $('<div>').html(data).find('#test');
$('#test').html(temp.html());
}
);
Upon loading, my web site runs this code if the users is using an alternate theme:
ReplaceJSCSSFile("css/skin1.css", "css/skin2.css", "css");
AJAX_LoadResponseIntoElement("skinContainer", "skin" + themeSelect + ".txt", function() {
AJAX_LoadResponseIntoElement("contentdiv", "index.txt", initPage);
});
What AJAX_LoadResponseIntoElement does ought to be obvious, but here's a short explanation of ReplaceJSCSSFile: It basically searches for link elements within the web page with their src property equal to "css/skin1.css", at which point it creates a new link element (src="css/skin2.css") and uses .parentNode.replaceChild to replace the old with the new.
The problem with this is that initPage() sets the positions and of certain divs that move around on the screen in relation to the positions of static elements. The position of those static elements is fetched from CSS, but the CSS is not being applied before the code is running, and initPage() is putting things in the wrong place because of it.
I have tried using a callback:
ReplaceJSCSSFile("css/skin1.css", "css/skin2.css", "css", function() {
AJAX_LoadResponseIntoElement("skinContainer", "skin" + themeSelect + ".txt", function() {
AJAX_LoadResponseIntoElement("contentdiv", "index.txt", initPage);
});
});
Unfortunately, this didn't work. Presumably, it tells the browser to replace the CSS file, and as soon as it tells the browser to do that, it moves on to the callback function, using AJAX to fetch the page contents. I'm thinking the page contents come back prior to the CSS file, which is why the new CSS is not being applied by the time initPage() is being executed.
Is it possible to fix this without using alternate stylesheets?
The only thing that I can come up with other than alternate stylesheets is fetching the CSS contents with AJAX (which would actually wait for the server's response before executing the callback function) to replace the "css/skin1.css" link element.
You can fudge it and use setTimeout in your callback so that the body of the callback isn't run until a bit later. This gives the browser some time to recover.