Print Labels With the Windows Live Contacts Control

I was installing a new label printer at home a little while ago and noticed mention of a developer SDK in the readme docs. (Yes, some people actually read the readme) I know from experience that getting text to land right side up on a target as small as an address label can be a pain in the neck using good old Win32 GDI.  So the prospect of an SDK that dealt with the nitty gritty of handling multiple label formats and orientations and made spitting out a label easy piqued my curiosity.

On my first glance at the Dymo Label Printer SDK readme (PDF), I was pleasantly surprised to see Delphi among the long list of languages supported by the SDK's COM-based print engine.  Not just supported, but sample code for all those languages is included in the SDK.

As I was about to pop this sidetrack off the stack and get back to whatever it was I was supposed to be doing, this word jumped out at me:   JavaScript.  A printer driver for JavaScript?  Hmm...  The Dymo SDK contains sample code to print labels in JavaScript or VBScript using COM objects in IE, and using a browser extension in Firefox.

Printing labels from a browser web app... interesting idea, but where would a web app fetch and store postal addresses of the people you know?  Hmm...

Ok, that was a baited question. If you answered "The Windows Live Contacts Control , of course! ", have a cookie.

So I installed the Dymo Label Printer SDK and sure enough, there was the IE JavaScript sample code.  The code instantiates the Dymo print engine as an ActiveX object, and then makes calls into it to fetch the list of available Dymo printers on the machine, select a printer, select a label format, and send an address off to be printed.  Easy as pie. 

Now, what to do about that address? 

The idea is to receive the contact infos that the user selects in the contacts control, display them in mock labels on the web page, and prompt the user to select a printer and label format to render the address bits in meatspace. 

Selecting the printer and label format after selecting the contacts allows the user a moment's pause to reflect on the printing journey they're about to undertake.  They may discover in the label preview that some of their contact infos are out of date or missing address info, and go fix that in the contacts control before printing.  Or they may realize that Valentine's Day was yesterday and no amount of postage will get their marvelous card to its intended recipient by yesterday, so maybe a wimpy little email apology will do.  (Hint: It won't.  Send the card!  Blame the post office!)

I plopped a Windows Live Contacts Control onto a new HTML page, dressed it up with the traditional border and float-right placement, and hooked up its onData event to a new function called receiveData(p_contacts) to format the contact objects into postal addresses:

 function receiveData(p_contacts) {    document.getElementById("ContactsDisplay").innerHTML = "Done! " + p_contacts.length + " records received. <br>";    var s = "";    var c;    var lines;    labels = [];    for (var i = 0; i < p_contacts.length; i++) {        c = p_contacts[i];        lines = null;        if (c.personalStreet && c.personalCity && c.personalPostalCode) {            lines = new Array();            lines[0] = c.name;            lines[1] = c.personalStreet;            lines[2] = c.personalCity + " " + c.personalState + " " + c.personalPostalCode;            if (c.personalCountry) {                lines[2] += " " + c.personalCountry;            }        } else if (c.businessStreet && c.businessCity && c.businessPostalCode) {            lines = new Array();            if (c.name) {                lines.push(c.name);            }            if (c.businessName) {                lines.push(c.businessName);            }            lines.push(c.businessStreet);            lines.push(c.businessCity + " " + c.businessState + " " + c.businessPostalCode);            if (c.businessCountry) {                lines[lines.length-1] += " " + c.businessCountry;            }        }         if (lines) {            s += "<div class='mocklabel'>";            for (var k = 0; k < lines.length; k++) {                s += lines[k] + "<br>";            }            labels.push(lines);        } else {            s += "<div class='errorlabel'>Incomplete address info: <br>"           for (var j in c) {               s += j + ": " + c[j] + "<br>";           }        }        s += "</div>";    }    showPage("reviewandprint");    document.getElementById("displayLabels").innerHTML = s;    document.getElementById("PrintBtn").disabled = false;}

Contact objects can contain personal and/or business address info, or no address info at all.  receiveData() handles these three cases, checking first to see if the contact object has sufficient minimal personal address info, then for sufficient business address info.  The first one found gets converted into a US Postal Service style 3 or 4 line address string.  If the contact info is insufficient in both categories, we'll set things up so that the mock labels will show an error message instead of a label on screen.

 function print() {    if (!DymoAddIn || !DymoLabel) {         alert("Can't connect to Dymo printer software. Are you sure you have a Dymo printer installed?");    }    if (labels.length == 0) {        alert("Nothing to print!");    }    if (DymoAddIn.Open2(labelFile)) {        var i;        var roll = chooseRollEl.disabled ? 0 : chooseRollEl.value;        DymoAddIn.StartPrintJob();        for (i = 0; i < labels.length; i++) {            var s = labels[i][0];            var j;            for (j = 1; j < labels[i].length; j++) {                s += "\n" + labels[i][j];            }            DymoLabel.SetAddress(1, s);            DymoAddIn.Print2(1, true, roll);        }        DymoAddIn.EndPrintJob();    } else {        alert('Error: Label file Not Found!');    }} 

As you might guess, the print() function is where the rubber meets the road.  Or the address meets the label.  DymoAddIn.Open2() opens the named label file on the client file system, or pops up a file open common dialog if the given file can't be found.  The local file name paths of recently used label files are obtained by calling DymoAddIn.GetMRULabelFiles() in the HTML page's init code.  The list of lines associated with each label is strung together using "\n" to mark line breaks.  "\n" doesn't mix well with HTML in the label preview, so I add it in only as we send the data off to the printer.  DymoLabel.SetAddress() submits the address to the label format, and DymoAddIn.Print2() commits the label contents and advances the print job to the next label.  Wrap it all up in a DymoAddIn.StartPrintJob() and DymoAddIn.EndPrintJob() and you have a web-based label printer app to print your Windows Live Contacts on your local Dymo label printer!

Side note:  Strictly speaking, the fact that this Dymo ActiveX interface provides browser JavaScript visibility into the local file system is a slight information disclosure issue.  An evil.com hacker could learn a little bit about how your hard disk is organized by looking at the path information of these label files.  He/she may glean personally identifying information about you such as your login name from those local file paths or could use that information to take random pokes and prods at your browser to try to replace or damage critical files on your system.  Without your local file paths, the hacker would just be stabbing in the dark, but with the local file paths, he/she could target your machine more precisely.  This assumes the hacker has found a hole in your machine's firewall protection to begin with, and that you're too lazy to change the default directory that you install software into.  You never install into the default directories, right?

There's plenty of room for improvement in this little web app:  fetching label templates from the web instead of from the local file system, supporting custom label formats, editing & correcting addresses in place on the mock labels on the html page and pushing those back into the contacts control, etc.  Dymo also supports printing US postage on special postage labels; the ultimate extension of this web app would be to print labels and postage at the same time.  Alas, there doesn't appear to be an API to talk to the Dymo postage app (yet?) so that idea will have to wait.

Dymo provides a Firefox plugin to provide the same API entry points for Firefox users as the Dymo ActiveX APIs for IE.  I 've successfully installed the plugin and run the Dymo sample script from their SDK to print a test label, but I can't figure out for the life of me how to get the Firefox plugin to work with a real live HTML page from a real server.  I understand that some configuration must be made on the local browser to tell it to allow the plugin to work, but nothing I've tried has worked.  Firefox gurus out there, please help!  Send me a clue! I really want this to work on multiple browsers.

Obviously, to use of this label printing app to print labels, you need a Dymo printer.  This is not an endorsement of Dymo over any other label printer brand - it's just the printer that was sitting on my desk when this idea popped into my head.  If you're a label printer vendor with a JavaScript-reachable printer API and want some help building a sample app like this for your printer, send me an email.  I'd be happy to help you out.

Windows Live Contacts Label Printing web app: <www.dannythorpe.com/live/v0.2/printlabels.html>