Putting a 'Save as PDF' link in your protected members area or local intranet site

Our regular Save As PDF Links are meant to quickly let your customers turn your webpages into PDF by adding a simple link to your pages. Though that is a very quick solution for most situations, it won't work in all situations.

For example when you have one of the following situations:

  • Your web pages are on a local intranet and not reachable from the regular internet
  • Your web pages are in a secure members area and you want logged-in users to be able to save their pages as PDF
  • You want to show an "in progress" message when the PDF is generated

For all these situations you can use our API with some simple JavaScript that we will explain below. You can see it in action when you click the button in the "Usage Example" section on the right.

How does it work?

Our HTML to PDF API takes HTML as input and returns a PDF. With the JavaScript below you take the HTML of the webpage that you're on and send it to our API. Our API will then return a PDF and the JavaScript will display it to the user.

Here's the basic code in JavaScript that you should link to the click of your button. You can do this by adding onclick="PDFmyURL()" to your save as PDF button or link, for example like this:

<a href="#" onclick="PDFmyURL()" class="hide_me">Save this page as PDF</a>
<script>
function PDFmyURL() {
	// Replace this with your PDFmyURL license - you get one when you sign up at https://pdfmyurl.com/plans
	var license = 'yourlicensekey';
	
	// First we take the HTML of the page
	var html = '', node = document.firstChild;
	while (node) {
		switch (node.nodeType) {
			case Node.ELEMENT_NODE:
				html += node.outerHTML;
				break;
			case Node.TEXT_NODE:
				html += node.nodeValue;
				break;
			case Node.CDATA_SECTION_NODE:
				html += '<![CDATA[' + node.nodeValue + ']]>';
				break;
			case Node.COMMENT_NODE:
				html += '<!--' + node.nodeValue + '-->';
				break;
			case Node.DOCUMENT_TYPE_NODE:
				// (X)HTML documents are identified by public identifiers
				html += "<!DOCTYPE " + node.name + (node.publicId ? ' PUBLIC "' + node.publicId + '"' : '') + (!node.publicId && node.systemId ? ' SYSTEM' : '') + (node.systemId ? ' "' + node.systemId + '"' : '') + '>\n';
				break;
		}
		node = node.nextSibling;
	}	    	

	// Now we prepare the data that we pass to the API
	// We pass the html, our license and custom CSS to hide the PDF button
	// Note that you don't need to pass any other parameters if your defaults in our members area are already good
	var data = { html: html, license:license, css:'.hide_me{display:none;}'};
	var serialized = Object.keys(data).map(function(k) { 
		return encodeURIComponent(k) + '=' + encodeURIComponent(data[k])
		}).join('&')
				
	// You can insert an "in progress" message here
	
	// We now prepare the API call
	xhttp = new XMLHttpRequest();
	xhttp.onreadystatechange = function() {
		var a;
		if (xhttp.readyState === 4 && xhttp.status === 200) {
			// The PDF is now generated
			// You can remove the "in progress" message here
		
			// Now we show the PDF to the user
			var filename = "example.pdf";
			if (window.navigator && window.navigator.msSaveOrOpenBlob) { 
				window.navigator.msSaveOrOpenBlob(xhttp.response, filename);
			} else { 
				a = document.createElement('a');
				a.href = window.URL.createObjectURL(xhttp.response);
				a.download = filename;
				a.style.display = 'none';
				document.body.appendChild(a);
				a.click();
			}
		}
	};
	
	// This is the actual call to our API
	xhttp.open("POST", "https://pdfmyurl.com/api", true);
	xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
	xhttp.responseType = 'blob';
	xhttp.send(serialized);
}
</script>

What if we're using a Content Security Policy?

If your pages use a Content-Security-Policy then XMLHttpRequests will usually only be allowed to the same domain as yours. This means you'll get an error when you use the above example code.

If you want to be able to call our API as in the example, then you'd need to add it as a connect-src directive. You can do this with a statement like this:

Content-Security-Policy: connect-src 'self' https://pdfmyurl.com;

If your company's policies do not allow this, then you could instead create a server-side script that calls our API. You would then POST to that instead of to https://pdfmyurl.com/api. This makes the XMLHttpRequest call the same domain and would not break the Content Security Policy.

A more elaborate scenario if your resources are even more protected

In some situations we will find that just sending the plain HTML is not enough. This can be when the resources on your page are ALSO not accessible from the internet and you didn't inline them.
In that case you can use some of our JavaScript that inlines the resources for you.

Below we explain our own JavaScript, where we:

  1. Make sure we take ALL HTML rather than just the HTML in the first example
  2. Take the stylesheets on the page and make sure we link to the absolute URL rather than the relative URL
  3. Take the images on the page and pass them as data instead of as URLs
  4. Use BootStrap and JQuery to show an "in progress" message

Here's the example with some commenting. Please drop us an email if you need further help implementing this.

<script>
// In this example we show everything so you can copy/paste if you have jQuery & BootStrap

//jQuery is loaded now, now wait for the DOM to be loaded 
$(document).ready(function() { 
	
	$('#pdfbutton').click(function(e) {
		e.preventDefault();
		// The following code is now linked to the PDF button

		// now we replace the stylesheets with their absolute URL version
		var elements = document.querySelectorAll('link[rel=stylesheet]');
		for(var i=0;i<elements.length;i++){
			var head = document.head;
	    	var link = document.createElement("link");
	    	link.type = "text/css";
	    	link.rel = "stylesheet";
	    	link.href = elements[i].href;
	    	head.appendChild(link);
	    	
			elements[i].parentNode.removeChild(elements[i]);
		}

		// let's convert all images to data so they don't need to be downloadable  	
		function setBase64Image(img) {
			var canvas = document.createElement("canvas");
			canvas.width = img.width;
			canvas.height = img.height;
			var ctx = canvas.getContext("2d");
			ctx.drawImage(img, 0, 0);
			var dataURL = canvas.toDataURL("image/png");
			img.src = dataURL;
				
		}

		var images = document.getElementsByTagName("img");
		if (images.length > 0) {
			for (var j=0; j<images.length; j++ ) {
				setBase64Image(images[j]);
			}
		}

		// now we'll take ALL HTML including the doctype
		var html = '', node = document.firstChild;
		while (node) {
			switch (node.nodeType) {
				case Node.ELEMENT_NODE:
					html += node.outerHTML;
					break;
				case Node.TEXT_NODE:
					html += node.nodeValue;
					break;
				case Node.CDATA_SECTION_NODE:
					html += '<![CDATA[' + node.nodeValue + ']]>';
					break;
				case Node.COMMENT_NODE:
					html += '<!--' + node.nodeValue + '-->';
					break;
				case Node.DOCUMENT_TYPE_NODE:
					// (X)HTML documents are identified by public identifiers
					html += "<!DOCTYPE " + node.name + (node.publicId ? ' PUBLIC "' + node.publicId + '"' : '') + (!node.publicId && node.systemId ? ' SYSTEM' : '') + (node.systemId ? ' "' + node.systemId + '"' : '') + '>\n';
					break;
			}
			node = node.nextSibling;
		}	    	

		var data = { html: html, license: 'yourlicensekey' };
		// You can pass all the parameters you like if you want, but you don't need to if your defaults in our members area are already good

		// we will show an in progress message with a BootStrap modal
		$('#inProgress').modal('show');
			
		xhttp = new XMLHttpRequest();
		xhttp.onreadystatechange = function() {
			var a;
			if (xhttp.readyState === 4 && xhttp.status === 200) {
				// the PDF is ready so we remove the in progress message
				$('#inProgress').modal('hide');

				// and show the PDF
				var filename = "example.pdf";
				if (window.navigator && window.navigator.msSaveOrOpenBlob) { 
					window.navigator.msSaveOrOpenBlob(xhttp.response, filename);
				} else { 
					a = document.createElement('a');
					a.href = window.URL.createObjectURL(xhttp.response);
					a.download = filename;
					a.style.display = 'none';
					document.body.appendChild(a);
					a.click();
				}
			}
		};
		
		xhttp.open("POST", "https://pdfmyurl.com/api", true);
		xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
		xhttp.responseType = 'blob';
		xhttp.send($.param( data ));
	});
}); 
</script>