Converting HTML to PDF using EvoPdf

We have been using ABCpdf to generate PDFs on the fly of the page currently being browsed. It has given us years of service, but now with the new version of our site I have had to find an alternative solution as my page body gets styled out of sight and then moved into position using jquery to change the left:-2000px to left:0px;. ABCpdf ignores javascript so I am getting a lovely half generated page in the PDF output.

The PDF generater that speaks javascriptThe only product I have found out there on the interwebs that understands javascript is EvoPdf. I have downloaded the zip file of 52mb done the install – which by the way could not be easier: add reference to dll, copy another, and then call the following method:

using EvoPdf.HtmlToPdf;

private void ConvertURLToPDF()
    {
        string urlToConvert = Request["url"];

        PdfConverter pdfConverter = new PdfConverter();
        pdfConverter.LicenseKey = "xxxxxxx";
        pdfConverter.PdfDocumentOptions.PdfPageSize = PdfPageSize.A4;
        pdfConverter.PdfDocumentOptions.PdfCompressionLevel = PdfCompressionLevel.Normal;
        pdfConverter.PdfDocumentOptions.PdfPageOrientation = PdfPageOrientation.Portrait;
        pdfConverter.PdfDocumentOptions.LiveUrlsEnabled = true;
        pdfConverter.JavaScriptEnabled = true; //the important bit – waits for javascript
        pdfConverter.ConversionDelay = 10;
        pdfConverter.InterruptSlowJavaScript = false;
        pdfConverter.PdfDocumentOptions.JpegCompressionEnabled = true;
        pdfConverter.PdfDocumentOptions.FitWidth = true;

        byte[] pdfBytes = pdfConverter.GetPdfBytesFromUrl(urlToConvert);
        System.Web.HttpResponse response = System.Web.HttpContext.Current.Response;
        response.Clear();
        response.AddHeader("Content-Type", "application/pdf");
        response.AddHeader("Content-Disposition", String.Format("attachment; filename=Output.pdf; size={0}", pdfBytes.Length.ToString()));
        response.BinaryWrite(pdfBytes);
        response.End();
}

The license is a little hefty, but you can trail the full version to see if it suits your needs before dusting off the credit card – the only thing is the outputed PDF will have a watermark over it until you get a license.

Detect browser window focus

The scenario: You have a link that creates a hidden iframe which generates a file download (a pdf of the current page in my example). You have cleverly given the user some visual feedback in the form of an animated ‘waiting’ gif and you would like the clever animation to go away once the user has saved the file.

Using a javascript setTimeout is just not going to work as you don’t know how long this will take, so the logical thing is to detect when the browser window gets focus again.

function onFocus() { $(‘#icon_pdf’).removeClass(‘icon_pdf_waiting’); };
    if (/*@cc_on!@*/false) { document.onfocusin = onFocus;/*IE*/ } else { window.onfocus = onFocus; }

    $(‘#icon_pdf’).click(function() {
        $(this).addClass(‘icon_pdf_waiting’);
        $(‘iframe#pdfFrame’).remove();
        $(document.body).append(‘<IFRAME height="1" width="1" id="pdfFrame"></IFRAME>’);
        $(‘iframe#pdfFrame’).attr(‘src’, ‘/pdf/save.aspx?url=’ + location.href);
    });

The icon_pdf_waiting class replaces the existing icon with a waiting icon, so removing the class once you finished puts the existing icon back.

I only needed the onfocus event, but you could also make use of the onblur if need be.

A truly versatile three column template

There are hundreds of 3 column templates out there, so what’s one more right? Well the difference here is that this one template can handle 1,2 or 3 columns, purely dependent on the existence of the columns on the html. So, if you have a CMS system and you generate a left or right column, then it will be rendered correctly, but if not then the content will stretch accordingly.

First we need some structure

<div id="skin" class="align rel">
        <div id="page" class="abs">
            <div id="main" class="column">
                <h1>Heading</h1>
                <div id="startcontent" class="content">
                    <p>Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. </p>
                    <p>There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form.</p>
                </div>
            </div>
            <div class="rel" id="footer"><p>footer</p></div>
            <div id=‘left’ class=‘column abs leftcolumn’><h2>Left</h2></div>
            <div id=‘right’ class=‘column abs rightcolumn’><h2>Right</h2></div>
        </div>  
    </div>
    <!– items that fall ouside of the page structure–>
    <div id="bannerimage" class="abs"><a class="align">banner</a></div>

Some CSS (I like to include a reset.css before this to clear out any cross browser issues)

.align          {width:1004px;margin:0 auto;}
    .rel            {position:relative;}
    .abs            {position:absolute;}
    .displayblock   {display:block;}
    #page           {width:1004px;top:340px;background-color:#fff;}
    #left           {top:0;left:0;width:220px;background-color:#eee;}
    #right          {top:0px;left:740px;width:264px;background-color:#eee;}
    #main           {margin:0 10px;background-color:#bbb;}
    .incleft #main  {margin-left:220px;}
    .incright #main {margin-right:265px;}
    .content        {margin:27px 30px 100px 30px;}
    #bannerimage    {top:0px;left:-1px;z-index:-1;width:100%;height:600px;margin:0 auto;background-color:#ccc;}
    #bannerimage a  {height:195px;margin-top:85px;display:block;}
    #footer         {width:1004px;height:200px;background-color:#ddd;}

And then the javascript (remember to add the jQuery library and an optional equal heights plugin)

//—Column display manager
        if ($(‘#page .leftcolumn’).length > 0) { $(‘#page’).addClass(‘incleft’); $(‘#left’).addClass(‘displayblock’); }
        if ($(‘#page .rightcolumn’).length > 0) { $(‘#page’).addClass(‘incright’); $(‘#right’).addClass(‘displayblock’); }

        //—Column equal heights | dependancy: jquery.equalheights.js
        if ($(‘.column’).length > 1) { $(‘.column’).equalHeights(); }

The great thing about this template is that it justs works – remove the #left div and the content takes up the first two columns, remove the #right div and the content takes up the last two columns. Take out # left and #right and, I’m sure you’ve guessed it, the content takes up all three columns.

Multi Column Lists with jQuery and CSS

I found myself needing to solve this problem and some googling revealed that I was not the only one. The challenge I had was how to take a long list of items and make them wrap, creating columns, newspaper style next to each other. The closest I got was from the guys at alistapart, but still not quite what I was after.

So, with coffee mug charged and appropriate tunes on the ipod I set out to solve some problems. The resulting jQuery function is actually quite succinct – maybe I have left something out, but it is exactly what I need: The list is dynamic, so I never know quite how long it is going to be and I do not want children elements to be spread over multiple columns.

The list goes something like this

<ul id="test">
                <li>Parent
                    <ul>
                        <li>child</li>
                        <li>child</li>
                        <li>child</li>
                    </ul>
                </li>
                <li>Parent
                    <ul>
                        <li>child</li>
                        <li>child</li>
                        <li>child</li>
                        <li>child</li>
                        <li>child</li>
                    </ul>
                </li>

and the javascript something like this:

jQuery.fn.multiColumnList = function(settings) {
    settings = jQuery.extend({
        width: 300,
        maxheight: 400,
        forceWidth: false
    }, settings);
    var $originallist = $(this);
    var cols = Math.round($originallist.width() / settings.width);
    var colwidth = Math.floor($($originallist).width() / cols);
    var size = $(‘>li’, this).size();
    var newlist = "";
    var currentColumnHeight = $(this).find(">li").eq(0).height();
    var currentColumnContents = ‘<li>’ + $(this).find(">li").eq(0).html() + ‘</li>’;

    for (i = 0; i < size; i++) {

        if ((i + 1) < size) {
            if (currentColumnHeight < settings.maxheight) {
                currentColumnContents = currentColumnContents + ‘<li>’ + $(this).find(">li").eq(i + 1).html() + ‘</li>’;
                currentColumnHeight = currentColumnHeight + $(this).find(">li").eq(i + 1).height();
            }
            else {
                newlist = newlist + ‘<ul class="megacol">’ + currentColumnContents + ‘</ul>’;
                currentColumnContents = ‘<li>’ + $(this).find(">li").eq(i + 1).html() + ‘</li>’; ;
                currentColumnHeight = $(this).find(">li").eq(i).height();
            }
        }
    }
    newlist = newlist + ‘<ul class="megacol">’ + currentColumnContents + ‘</ul><br style="clear:both">’;

    $(newlist).insertBefore($originallist);
    if (settings.forceWidth) {
        $(‘ul.megacol’).css(‘width’, settings.width);
    }
    $originallist.remove();
    return this;
}

Lastly, a sprinkling of CSS to make sure your new columns render next to each other

.megacol    {float:left;}

To make this all come together, make sure you have included the jQuery library and the function before calling it:

$(document).ready(function() {
            $("#test").guidemega();
        });

Getting destination page to reload after back button is clicked

I found that in certain cases that the back button was causing much mayhem on my site as the code is not reloaded and therefore caused some unwanted behaviour.

So here’s my cross-browser fix that I have devised after much googling. For a change IE was not the challenge, but rather Firefox as the onload event does not fire when the back button is used.

First, oddly, you need to clear the unload event

<body onunload="">

Then add a hidden field to the page to use as a flag:

<input type="hidden" id="refreshed" value="no" />

Lastly, the magic javascript. If you are using jQuery, don’t try and modernise the code and stick it in the .ready event – it won’t work, I tried.

onload = function() {
    var e = document.getElementById("refreshed");
    if (e.value == "no") e.value = "yes";
    else { e.value = "no"; location.reload(); }
}

Good luck, let me know if this helped.

Thanks go to those who contributed.

Pure joy

Laser refractive surgery (Lasik)

Do people still use these things?

Do people still use these things?

Yesterday, after months of deliberation, investigation and hesitation I willingly allowed Dr John Hill, of the Dr Hill Eye Laser Centre, to perform laser refractive surgery (Lasik) on my less than perfect eyes which where -1.75 and astigmatic. (I say willingly because I had to sign a form just before the procedure that stated that I have asked all the right questions and understand what I was getting myself into. ) In less than 10 minutes both eyes had been fixed and I found myself back in the reception getting care instructions for my new eyes. I write this post less that 24 hours after the procedure and already my vision is on a par with what I had while wearing lenses.

Just for the record: I am an absolute girl scout and am not the bravest when it comes to needles and men in masks. I must admit I nearly fainted half way through as the thought of what was actually been done to me became a little over whelming. But honestly, the dentist is far worse and I experienced absolutely no pain. My vision was very smokey at first but but the evening was greatly improved and was even better this morning.

The procedure is not cheap (R15000), but having done it I would, without hesitation, recommend it to anyone.

AWB secretary-general looses the plot during interview

Seriously? I thought we were over this.

Unhinged: Surviving Joburg

A brilliant depiction of the vibe in JHB. I grew up there, and its all true. A place that draws you in with the promise of success. A place with as many opportunities as there are dangers.

Can’t wait to see it, read more here.

Fool climbs speaker stack at Kirstenbosch Goldfish concert