Call to window.print firing before documents style is applied - javascript

I have a form with a print function. Inside the function I open a window, build the document, add the head with style sheets, add the form HTML and make the call to print the last part of the body. The problem is the initial print preview/print doesn't reflect the style. If I cancel the print and attempt to print manually, the style shows up.
I've tried quite a few ways of doing this with no luck. It seems like a timing issue. Any ideas?
Browser is Chrome. Below is the JS function. (This is injected with a faces context).
function printForm(windowTitle, path){
var printWindow = window.open();
var printDocument = printWindow.document;
var headHtml = "<link href='" + path + "/css/style.css' rel='stylesheet' type='text/css'/>";
printDocument.getElementsByTagName('head')[0].innerHTML = headHtml;
var printDiv = printDocument.createElement("DIV");
var formDiv = document.getElementById("formDiv");
printDiv.innerHTML = formDiv.innerHTML // Styling here is the issue
printDocument.body.appendChild(printDiv);
var scriptTag = printDocument.createElement("script");
var script = printDocument.createTextNode("print();");
scriptTag.appendChild(script);
printDocument.body.appendChild(scriptTag);
}

I think you need a setTimeout to delay printing for a second or two so the style can be applied. You'll need to tinker to find the right length.

Instead of trying to guessing with a timer, it might be more precise to use the load event on the <link ...> to know exactly when it has completed loading.
Below is a function I use which illustrates this technique. It uses an off-screen <iframe> to print a portion of a page. It's jQuery based, but the principles are the same in vanilla JS. Note the setting of the <link>'s onload function with an anonymous function which invokes print() on the <iframe>'s window object.
/*
* expects:
* <tag id="whatever">...goop...</tag>
* <input type="button" class="printit" data-target="whatever" value="Print">
*/
$(function() {
$( '.printit' ).click(function() {
var goop = $( '#' + this.dataset.target ).prop('outerHTML');
var ifr = $('<iframe>');
var bas = $('<base href="' + document.baseURI + '">');
var lnk = $('<link rel="stylesheet" href="/css/print.css">');
lnk.on('load', function() { /* CSS is ready! */
ifr[0].contentWindow.print();
});
ifr.css( { position: 'absolute', top: '-10000px' } )
.appendTo('body')
.contents().find('body').append(goop);
ifr.contents().find('head').append(bas).append(lnk);
return false;
});
});

Related

Print iFrame only when CSS is loaded completely

I’m printing selected parts of pages by putting them in an empty iframe. Everthing is fine except the CSS. Sometimes it’s there, sometimes not, so I guess the print function is loaded in moste cases before the css is complete. Unforntunately I’m obviously not able to solver the problem. The basic code:
var pStyles = new String ("<link href='/css/print.css' rel='stylesheet' type='text/css' id='cssprint' />");
var pWrapIn = new String ("<main><article><section>");
var pWrapOut = new String ("</section></article></main>");
function printFrame(fId) {
window.frames["printhelper"].document.body.innerHTML = pStyles + pWrapIn + document.getElementById(fId).innerHTML + pWrapOut;
console.log(window.frames["printhelper"].document.body.innerHTML);
window.frames["printhelper"].window.focus();
window.frames["printhelper"].window.print();
}
This works, but with the described CSS issue. To make sure the CSS is loaded, I ended up with the modified function, which is not working at all:
function printFrame(fId) {
window.frames["printhelper"].document.body.innerHTML = pStyles + pWrapIn + document.getElementById(fId).innerHTML + pWrapOut;
console.log(window.frames["printhelper"].document.body.innerHTML);
$(#cssprint).on('load', function() {
console.log ('CSS loaded');
window.frames["printhelper"].window.focus();
window.frames["printhelper"].window.print();
}, 0);
}
#cssprint seems to be loaded never, so console.log stays empty and no print is done. But what am I doing wrong? (The function printFrame is called by a simple onClick in the HTML markup.)
I would probably do something like this:
Add onload event handler to CSS link that calls a function that posts a message to the parent window (parent.postMessage(...)) or directly does so, then you do not need an extra script tag.
The outer window listens to the message of the iframe window and starts printing when receiving it.
You can also just skip the message posting if you do not need to synchronize anything with the parent window an just print directly in the onload event:
<link onload="window.print()" ...>
See MDN for more info on posting and receiving messages.
The complete working function (thanks to H. B.):
var pStyles = new String ("<link href='/css/print.css' rel='stylesheet' type='text/css' onload='window.focus(); window.print()' />");
var pWrapIn = new String ("<main><article><section>");
var pWrapOut = new String ("</section></article></main>");
function printFrame(fId) {
window.frames["printhelper"].document.body.innerHTML = pStyles + pWrapIn + document.getElementById(fId).innerHTML + pWrapOut;
}

Dynamic height of editor block

I use InnovaEditor to create edit block.
I try to find way in order to set dynamic height of edit block.
Ie block height should correspond block content.
HTML:
<iframe id="idContenteditor_field_1" name="idContenteditor_field_1" style="width:100%;height:100%;border:none;">
<html>
<head></head>
<body>12345</body>
</html>
</iframe>
What I did:
1) set keyup event in iframe body
2) wrap to content to get real height
3) set calculated height to the iframe
Javascript:
var $iframe = $("iframe#idContenteditor_field_1");
var $iframeBody = $iframe.contents().find("body");
$iframeBody.keyup(function(e) {
if ($(this).find('.content').length === 0) {
// add wrap
var bodyContent = $(this).html();
$(this).html('<div class="content">' + bodyContent + '</div>');
}
var $contentBlock = $(this).find('.content');
var bodyHeight = $contentBlock.outerHeight();
$('#idContenteditor_field_1').height(bodyHeight); // set real height
});
It works fine.
The issue:
I have 10 edit blocks on the page and they are same except id.
But I have problems when I try to apply this code to all iframes.
// return all iframes
var $iframes = $('iframe[id^="idContenteditor_field_"]');
// return only single body of first iframe.
var $iframesBody = $iframes .contents().find("body");
So I can't set keyup event for all iframes.
Could you help me?
Maybe there is easier way to set dynamic height?
var $iframesBody = $iframes .contents().find("body");
^^^ it returns only single body of first iframe, because rest iframes have not yet been loaded fully.
So I just execute this script after load of all iframes.
And it works.
I haven't tested this code, but something like below should work.
You just need to iterate through your objects and set the event listener for each one in turn.
var $iframes = $('iframe[id^="idContenteditor_field_"]');
$iframes.each(function(index, item) {
var $iframeBody = $(item).contents().find("body");
$iframeBody.keyup(function(e) {
if ($(this).find('.content').length === 0) {
// add wrap
var bodyContent = $(this).html();
$(this).html('<div class="content">' + bodyContent + '</div>');
}
var $contentBlock = $(this).find('.content');
var bodyHeight = $contentBlock.outerHeight();
$(item).height(bodyHeight); // set real height
});
});

jscript to run at window.onload AND item.onchange

I'm using jscript to load a string in to several elements of an array, then display the useful element in html.
There's a pulldown menu that causes the string to change. So, I need the function to be re-run when that happens.
I have been able to successfully display the element using window.onload
and, I have been able to successfully display the element AFTER the pulldown menu has been changed.
But, I can't seem to display the element with window.onload and subsequently after item.onchage
If anyone has any suggestions, I'd be grateful.
Here is the code that works for window.onload
window.onload = function() {
var selectedOptionId = jQuery('select').val();
var optionPartNumber = jQuery('input[name="OptID_' + selectedOptionId + '"]').val();
//loads the string in to an array and returns the 2nd element
var optionPartNumber = optionPartNumber.split(".")[1];
document.getElementById("MASNUM").innerHTML=optionPartNumber;
}
Here is the code that works for pulldown.onchange
jQuery(function(){
jQuery('.dropdownimage-format select').on('change', function(){
var selectedOptionId = jQuery('select').val();
var optionPartNumber = jQuery('input[name="OptID_' + selectedOptionId + '"]').val();
var optionPartNumber = optionPartNumber.split(".")[1];
document.getElementById("MASNUM").innerHTML=optionPartNumber;
});
In both cases, the element is displayed in html by
<a id="MASNUM">

Javascript image src on click of image

I'm trying to create a simple image swapper. So I have 20 images on the page, and 1 large one. I want the user to click one of the smaller images from the 20 and it be output into the place of the big image source.
I've only just started but even so my code doesn't seem to validate as the script fails before it even gets to the console.log. Also not sure if I'm using thisID correctly.
<img id='avatar-output' onclick='selectAvatar(thisID)' src='images/avatars/$number.png' />
<script>
function selectAvatar(thisID){
var imageSource = document.getElementById ("avatar-output").src;
console.log("Avatar source is " imageSource);
}
</script>
HTML:
<img id='avatar-output' src='images/avatars/$number.png' />
<img class='avatar-small' src='images/avatars/1.png' />
<img class='avatar-small' src='images/avatars/2.png' />
<img class='avatar-small' src='images/avatars/3.png' />
JS:
var els = document.getElementsByClassName('avatar-small'),
target = document.getElementById("avatar-output"),
handler = function() { target.src = this.src; };
for (var i=0; i<els.length; ++i) els[i].onclick = handler;
Demo
Or, if all small images are together, better use event delegation:
var target = document.getElementById("avatar-output");
document.getElementById("avatar-small-wrapper").onclick = function(e) {
if(e.target.tagName.toLowerCase() === 'img') target.src = e.target.src;
};
Demo
Notes
Better separation of content (html) and behavior (inline JS)
<script> element needs type="text/javascript" attribute
Avoid running JavaScript in global scope to avoid creating global variables, polluting window. Run in in a closure: (function(){ /* code here */ })()
You have a typo in it, change
var imageSource = document.getElementById ("avatar-output").src;
to
var imageSource = document.getElementById("avatar-output").src;
since there is a space where it shouldn't be.
Edit: oh and another one in the
console.log("Avatar source is " imageSource);
line. Add a + between your string and your variable like this:
console.log("Avatar source is " + imageSource);

Body onload in Javascript

I had written one JS in asp.net. I had called that from body onload, but the JS doesn't get called where I have put my debugger. What could be possible reasons for this? I'm developing website in dotnetnuke.
The JS I have written is syntactically and logically correct.
<script type="text/javascript">
var displayTime, speed, wait, banner1, banner2, link1, link2, bannerIndex, bannerLocations, bannerURLs;
function initVar() {
debugger;
displayTime = 10; // The amount of time each banner will be displayed in seconds.
speed = 5; // The speed at which the banners is moved (1 - 10, anything above 5 is not recommended).
wait = true;
banner1 = document.getElementById("banner1");
banner2 = document.getElementById("banner2");
//link1 = document.getElementById("link1");
//link2 = document.getElementById("link2");
//banner1 = document.getElementById("banner1");
//banner2 = document.getElementById("banner2");
banner1.style.left = 0;
banner2.style.left = 500;
bannerIndex = 1;
/* Important: In order for this script to work properly, please make sure that the banner graphic and the
URL associated with it have the same index in both, the bannerLocations and bannerURLs arrays.
Duplicate URLs are permitted. */
// Enter the location of the banner graphics in the array below.
//bannerLocations = new Array("internet-lg.gif","jupiterweb.gif","jupitermedia.gif");
bannerLocations = new Array("image00.jpg", "image01.jpg", "image02.jpg", "admin_ban.bmp");
// Enter the URL's to which the banners will link to in the array below.
bannerURLs = new Array("http://www.internet.com","http://www.jupiterweb.com","http://www.jupitermedia.com");
}
function moveBanner() {
//debugger;
if(!wait){
banner1.style.left = parseInt(banner1.style.left) - (speed * 5);
banner2.style.left = parseInt(banner2.style.left) - (speed * 5);
if(parseInt(banner1.style.left) <= -500){
banner1.style.left = 500;
bannerIndex = (bannerIndex < (bannerLocations.length - 1)) ? ++bannerIndex :0;
banner1.src = bannerLocations[bannerIndex];
//link1.href = bannerURLs[bannerIndex];
wait = true;
}
if(parseInt(banner2.style.left) <= -500){
banner2.style.left = 500;
bannerIndex = (bannerIndex < (bannerLocations.length - 1)) ? ++bannerIndex :0;
banner2.src = bannerLocations[bannerIndex];
//link2.href = bannerURLs[bannerIndex];
wait = true;
}
setTimeout("moveBanner()",100);
} else {
wait = false;
setTimeout("moveBanner()", displayTime * 1000);
}
}
</script>
REGISTRATION IN JS
<body onload="initVar(); moveBanner();">
</body>
I ran your code. Both methods executed without me having to make any modifications to the posted code. Is there possibly some other code that is overwriting the onload method?
The DotNetNuke best practice for binding to the "onload" property in JavaScript is to hook into JQuery's ready() method:
jQuery(document).ready( function() {
// put your code here
initVar();
moveBanner();
});
DotNetNuke 4.9.x and later ship with the jQuery JavaScript library included.
Have you edited DNN's Default.aspx? Otherwise, there isn't any way for you to have access to the body tag to add the onload attribute like you show.
How are you injecting this script? Are you using a Text/HTML module, are you using the Page Header Text setting for the page, are you adding it directly to the skin, have you written a custom module, or something else?
Instead of using the onload attribute on the body tag, I would suggest wiring up to that event in the script itself. If you're using any code to inject the script, you can ask DNN to register jQuery or a ScriptManager (for ASP.NET AJAX) so that you can use those libraries to wire the event up easily. If you can't guarantee that those are on the page, use the following:
function addLoadEvent(func) {
var oldonload = window.onload;
if (typeof window.onload != 'function') {
window.onload = func;
} else {
window.onload = function() {
if (oldonload) {
oldonload();
}
func();
}
}
}
addLoadEvent(function () {
initVar();
moveBanner();
});
I don't know much about asp.net but if you can put javascript code in your page, then you can try this alternative:
window.onload = function()
{
// any code here
}
This is the same as what you put in body tag.
The CSS left property takes a length, not an integer. You must have units for non-zero lengths. (Even when setting it using JavaScript!).

Categories

Resources