The thing I want to build is that by clicking a button I want to trigger the print of a PDF file, but without opening it.
+-----------+
| Print PDF |
+-----------+
^ Click *---------> printPdf(pdfUrl)
The way how I first tried it is to use an iframe:
var $iframe = null;
// This is supposed to fix the onload bug on IE, but it's not fired
window.printIframeOnLoad = function() {
if (!$iframe.attr("src")) { return; }
var PDF = $iframe.get(0);
PDF.focus();
try {
// This doesn't work on IE anyways
PDF.contentWindow.print();
// I think on IE we can do something like this:
// PDF.document.execCommand("print", false, null);
} catch (e) {
// If we can't print it, we just open it in the current window
window.location = url;
}
};
function printPdf(url) {
if ($iframe) {
$iframe.remove();
}
$iframe = $('<iframe>', {
class: "hide",
id: "idPdf",
// Supposed to be a fix for IE
onload: "window.printIframeOnLoad()",
src: url
});
$("body").prepend($iframe);
}
This works on Safari (desktop & iOS) and Chrome (can we generalize it maybe to webkit?).
On Firefox, PDF.contentWindow.print() ends with a permission denied error (even the pdf is loaded from the same domain).
On IE (11), the onload handler is just not working.
Now, my question is: is there another better way to print the pdf without visually opening it to the user?
The cross browser thing is critical here. We should support as many browsers as possible.
What's the best way to achieve this? Is my start a good one? How to complete it?
We are now in 2016 and I feel like this is still a pain to implement across the browsers.
UPDATE: This link details an elegant solution that involves editing the page properties for the first page and adding an action on Page Open. Works across all browsers (as browsers will execute the JavaScript placed in the actions section). Requires Adobe Acrobat Pro.
It seems 2016 brings no new advancements to the printing problem. Had a similar issue and to make the printing cross-browser I solved it using PDF.JS but had to make a one-liner addition to the source (they ask you to build upon it in anyways).
The idea:
Download the pre-built stable release from https://mozilla.github.io/pdf.js/getting_started/#download and add the "build" and "web" folders to the project.
The viewer.html file is what renders out PDFs with a rich interface and contains print functionality. I added a link in that file to my own JavaScript that simply triggers window.print() after a delay.
The link added to viewer:
<script src="viewer.js"></script>
<!-- this autoPrint.js was added below viewer.js -->
<script src="autoPrint.js"></script>
</head>
The autoPrint.js javascript:
(function () {
function printWhenReady() {
if (PDFViewerApplication.initialized) {
window.print();
}
else {
window.setTimeout(printWhenReady, 3000);
}
};
printWhenReady();
})();
I could then put calls to viewer.html?file= in the src of an iframe and hide it. Had to use visibility, not display styles because of Firefox:
<iframe src="web/viewer.html?file=abcde.pdf" style="visibility: hidden">
The result: the print dialog showed after a short delay with the PDF being hidden from the user.
Tested in Chrome, IE, Firefox.
After spending the past couple of hours trying to figure this one out and lots of searching here is what I have determined...
The HTML5 Web API spec for Printing indicates that one of the printing steps must fire beforeprint, a simple event (an event that is non-cancelable), to the window object of the Document being printed (as well as any nested browsing contexts, this relates to iframes) to allow for changes to the Document prior to printing. This step is internal to the browser and not something you'll be able to adjust. During this process, the browser's print dialog sometimes shows a preview of the file (Chrome does this)...so if your goal is to never display the file to the viewer you might be stuck.
The closest to achieving this I came was by creating an index.html file which has a button containing data-* attributes which provided context. Change the path/filename.ext in the data-print-resource-uri attribute to a local file of your own.
<!DOCTYPE html>
<html>
<head>
<title>Express</title>
<link rel="stylesheet" href="/stylesheets/style.css">
</head>
<body>
<h1>Express</h1>
<p>Welcome to Express</p>
<button name="printFile" id="printFile" data-print-resource-uri="/binary/paycheckStub.pdf" data-print-resource-type="application/pdf">Print File</button>
<iframe name="printf" id="printf" frameborder="0"></iframe>
<script src="/javascripts/print.js"></script>
</body>
</html>
Then in the print.js file, I tried a few things, but never quite got it working (leaving different things I had played with in the comments).
// Reference vars
var printButton = document.getElementById('printFile');
var printFrame = document.getElementById('printf');
// onClick handler
printButton.onclick = function(evt) {
console.log('evt: ', evt);
printBlob('printf', printButton.getAttribute('data-print-resource-uri'), printButton.getAttribute('data-print-resource-type'));
}
// Fetch the file from the server
function getFile( fileUri, fileType, callback ) {
var xhr = new XMLHttpRequest();
xhr.open('GET', fileUri);
xhr.responseType = 'blob';
xhr.onload = function(e) {
// Success
if( 200 === this.status ) {
// Store as a Blob
var blob = new Blob([this.response], {type: fileType});
// Hang a URL to it
blob = URL.createObjectURL(blob);
callback(blob);
} else {
console.log('Error Status: ', this.status);
}
};
xhr.send();
}
function printBlob(printFrame, fileUri, fileType) {
// Debugging
console.log('inside of printBlob');
console.log('file URI: ', fileUri);
console.log('file TYPE: ', fileType);
// Get the file
getFile( fileUri, fileType, function(data) {
loadAndPrint(printFrame, data, fileType);
});
}
function loadAndPrint(printFrame, file, type) {
// Debugging
console.log('printFrame: ', printFrame);
console.log('file: ', file);
window.frames[printFrame].src = file;
window.frames[printFrame].print();
/*
// Setup the print window content
var windowContent = '<!DOCTYPE html>';
windowContent += '<html>'
windowContent += '<head><title>Print canvas</title></head>';
windowContent += '<body>'
windowContent += '<embed src="' + file + '" type="' + type + '">';
windowContent += '</body>';
windowContent += '</html>';
// Setup the print window
var printWin = window.open('','','width=340,height=260');
printWin.document.open();
printWin.document.write(windowContent);
printWin.document.close();
printWin.focus();
printWin.print();
printWin.close();
*/
}
I think that if you can get it working properly using the Blob might work the best in the cross-browser method you wanted.
I found a few references about this topic which might be helpful:
How to send a pdf file directly to the printer using JavaScript?
https://www.w3.org/TR/html5/webappapis.html#printing
https://developer.mozilla.org/en-US/docs/Web/Guide/Printing#Print_an_external_page_without_opening_it
Printing a web page using just url and without opening new window?
I will post here the modified functions of the OP functional on IE 11
printPdf: function (url) {
$('#mainLoading').show();
let iframe = $('#idPdf');
if (iframe) {
iframe.remove();
}
iframe = $('<iframe>', {
style: "display:none",
id: "idPdf"
});
$("body").prepend(iframe);
$('#idPdf').on("load", function(){
utilities.printIframeOnLoad()
})
utilities.getAsyncBuffer(url, function(response){
let path = utilities.getPdfLocalPath(response);
$('#idPdf').attr('src', path);
})
},
printIframeOnLoad: function () {
let iframe = $('#idPdf');
if (!iframe.attr("src")) { return; }
var pdf = iframe.get(0);
pdf.focus();
$('#mainLoading').hide();
pdf.contentWindow.print();
},
getPdfLocalPath: function (data) {
var filename = "Application_" + utilities.uuidv4() + ".pdf";
var blob = new Blob([data], { type: 'application/pdf' });
if (window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveBlob(blob, filename);
return filename;
}
else {
let url = window.URL || window.webkitURL;
let href = url.createObjectURL(blob);
return href;
}
},
getAsyncBuffer: function (uriPath, callback) {
var req = new XMLHttpRequest();
req.open("GET", uriPath, true);
req.responseType = "blob";
req.onreadystatechange = function () {
if (req.readyState === 4 && req.status === 200) {
callback(req.response);
}
};
req.send();
}
Related
Is it possible to listen to the loading of images (or stylesheets) via the Mozilla Add-On SDK?
Having the user load a new URL can be found via the Page-Mod module, AJAX calls via overriding XMLHttpRequest.prototype.*().
Yet both only listen to loading of entirely new pages, not to the attached images of a page. Also, the image source might be changed for example in Javascript.
(It might be possible to use a http-on-modify-request, as pointed to here, but how can you access the nsIHttpChannel's URL and parameters?)
You can listen to every network request via
var {Cc, Ci} = require("chrome");
var httpRequestObserver = {
observe: function(subject, topic, data) {
if (topic == "http-on-modify-request") {
var httpChannel = subject.QueryInterface(Ci.nsIHttpChannel);
var myURL = httpChannel.URI.spec;
console.log("url: " + myURL);
}
},
register: function() {
var observerService = Cc["#mozilla.org/observer-service;1"]
.getService(Ci.nsIObserverService);
observerService.addObserver(this, "http-on-modify-request", false);
},
unregister: function() {
var observerService = Cc["#mozilla.org/observer-service;1"]
.getService(Ci.nsIObserverService);
observerService.removeObserver(this, "http-on-modify-request");
}
};
httpRequestObserver.register();
exports.onUnload = function(reason) {
httpRequestObserver.unregister();
};
See also Firefox Addon observer http-on-modify-request not working properly.
URI access
The nsIHttpChannel extends nsIChannel, which has a URI Attribute of type nsIURI, which has a spec attribute that contains the whole URL (including schema, parameters, ref, etc).
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);
})
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.
I'm developing a chrome extension and i've met a very strange bug - my code works well on Mac OS, but doesn't work on Windows and Linux versions of Chrome. Versions are the same.
function captureAllScreen() {
chrome.windows.getCurrent(function(w) {
chrome.tabs.captureVisibleTab(w.id, {"format":"png"}, function(response) {
var image = response;
var url;
chrome.tabs.getSelected(w.id, function(response) {
url = response.url;
});
var viewTabUrl = [chrome.extension.getURL('app.html'),
'?id=', id++].join('');
chrome.tabs.create({url: viewTabUrl}, function(tab) {
var targetId = tab.id;
var addSnapshotImageToTab = function(tabId, changedProps, tab) {
if (tabId != targetId || changedProps.status != "complete") {
return;
};
chrome.tabs.onUpdated.removeListener(addSnapshotImageToTab);
var views = chrome.extension.getViews();
for (var i = 0; i < views.length; i++) {
var view = views[i];
if (view.location.href == viewTabUrl) {
view.twm_Draw.sendScreen(image, url); //Application-specific method
break;
}
}
window.close();
};
chrome.tabs.onUpdated.addListener(addSnapshotImageToTab);
});
});
});
};
Update:
What i want to do with this code - is to take a screenshot and tab url and send it to my extension's page. When user clicks on my extension's icon - it opens a popup with two buttons, one of it fires this function.
In Mac Os everything works - this code takes a screenshot, tab url, opens new tab with my application and sends the data there. On Linux & Windows versions of chrome it doesn't send the data, after clicking the icon in the popup you just get a blank tab opened.
I think this part might be causing problems:
var url;
chrome.tabs.getSelected(w.id, function(response) {
url = response.url;
});
//using url
The rest of the code should be wrapped into callback function, otherwise order of execution is not guaranteed.
I'm guess it's only supported on Mac, whatever it does:
view.twm_Draw.sendScreen(image, url); //Application-specific method
I don't know about Unix but on Windows you can only get a screenshot using a NPAPI plugin like the Google extension for screen capture.
function CallPrint() {
var prtContent = document.getElementById('<%= pnlDelete.ClientID %>');
var winPrint = window.open('', '', 'left=0,top=0,width=800,height=600,toolbar=0,scrollbars=0,status=0');
winPrint.document.write("<h3>Summary</h3><br />" + prtContent.innerHTML);
winPrint.document.close();
winPrint.focus();
winPrint.print();
winPrint.close();
}
I have a need where I have to print contents of a div. I am using above code to do so. It is working fine in IE but does nothing in Firefox. Am I missing something here that needs to be done in Firefox?
Instead of opening a new window without any URL, I opened this page in the window and accessed the contents of the pnlSummary from the opened window via window.opener object –
function CallPrint() {
var winPrint = window.open('Print.aspx', '', 'left=0,top=0,width=800,height=600,toolbar=0,scrollbars=0,status=0');
}
On Print.aspx page I used this function –
function Print() {
var prtContent = "<h3>Summary</h3>" + window.opener.document.getElementById('ctl00_cphContent_pnlSummary').innerHTML;
document.getElementById("printDiv").innerHTML = prtContent;
window.print();
window.opener.focus();
window.close(); }
and called it on body onload.
<body onload="Print();">
<form id="form1" runat="server">
<div id="printDiv">
</div>
</form>
</body>
This is working fine in both IE and Firefox.
Use setTimeout() function for loading the page. The example is given bellow link.
http://oraclehappy2help.blogspot.in/2012/09/child-window-printing-problem-solution.html
Uhm... your code seems to work fine for me, on Firefox 3.5 (Windows).
It's possible that are something wrong on your pnlDelete.ClientID?
Your javascript code is rendered well on the page?
Anyway I suggest you to use jQuery + a print plugin like this.
Check to ensure your panel has something. My guess is prtContent is undefined
Try this:
function CallPrint() {
var prtContent = document.getElementById('<%= pnlDelete.ClientID %>');
if (prtContent) {
var winPrint = window.open('', '', 'left=0,top=0,width=800,height=600,toolbar=0,scrollbars=0,status=0');
winPrint.document.write("<h3>Summary</h3><br />" + prtContent.innerHTML);
winPrint.document.close();
winPrint.focus();
winPrint.print();
winPrint.close();
}
else {
alert('No summary available for printing');
}
}
you can use JS Printer Setup https://addons.mozilla.org/en-us/firefox/addon/js-print-setup/"
which is Fire fox Depended addon most usefulladdon In web-app Kisok in Firefox to select printer
attached some example for attached printer and local printer it may help you to build without print dialog.
function EB_Print(printType) {
try{
var printerType = printType; // type of the Print Code : network
// Default Printer Configuring
var Default_printer = "Canon MG2500 series";
/** local Printer configuring via Network
** Config teh Local server use \\\\ to get \\
**/
var Organizer_Printer = "\\\\network\\Canon LBP2900";
jsPrintSetup.setPrinter(Default_printer);
jsPrintSetup.setSilentPrint(true);// withoud dialog
/** alert(jsPrintSetup.getPrintersList()); // Debugger for the attached Printers list
alert(jsPrintSetup.getPrinter()); // get the set printer Option
**/
// id network is selected It will print the page in network
if(printerType == 'network'){
jsPrintSetup.setPrinter(Organizer_Printer);
}
jsPrintSetup.print(); // Print the page
}catch (e) {
// TODO: handle exception
}
}
you could try a jquery plugin...
http://plugins.jquery.com/project/PrintArea