Writing cross-platform apps with Node-WebKit

Node-WebKitLast week, the daycare where my son stays told me they were desperately looking for an application to save them a lot of precious time. Every month, they have someone from the school spend 3 hours dealing with invoices. The process is crazy manual, no automation at all:

  1. An Excel (xls) spreadsheet contains all the tuitions due by parents and is updated every month.
  2. A new PDF is created manually for each parent with the tuition that varies every month for some parents.
  3. An email is sent to each parents (across 3 different schools, so that's a lot of parents) with the PDF invoice as an attachment.
  4. Sometimes, a message is added in the PDF invoice to inform about days-off (holidays) during the month.

I told them I could develop the app for them for fun, so I first started thinking, oh cool, I will be developing this in Swift, a cool project. I started looking around for libraries to parse Excel files and found one that could work (libxls). After an hour of pain trying to make it compile in my project, I realized that for such an app, do I really need super tight integration with the OS or the performance that native would provide? Then, I realized the app would be used across different platforms maybe, and I will probably need to fix bugs remotely, push updates, so I needed something super flexible and I decided to have a look at Node.js, and I quickly realized the power of not only the Node stack but more than anything, the ecosystem.

Here is the recipe I ended up with:

  1. XLSJS for the parsing of the Excel documents.
// we parse our file coming from a drag and drop event
var workbook = XLS.read(e.target.result, {type: 'binary'});
// let's grab the sheets names
var sheets = self.workbook.SheetNames;
// grab the first sheet
var sheet1 = self.workbook.Sheets[self.sheets[0]]
// grab the range of cells to work with
var start = self.sheet1['!range'].s.r + 1;
var end = self.sheet1['!range'].e.r;
  1. PDFKit for the PDF generation

Having created AlivePDF back in the days, I was curious to see what the ecosystem looked like with Node and PDFKit works beautifully, simple APIs and gets the job done. Below is the core function that generates the PDF and returns its path for the email attachment:

// core PDF generation function
// takes a name and amount and writes a PDF to the disk
// that path is reused in the email function to send the attachment
function savePDFDocument(name, amount){
    var doc = new PDFDocument()
    var date = returnDate();
    var filePath = "invoices/"+name+" - "+date+".pdf";
    try {
        fs.openSync(filePath, 'r')
        fs.unlinkSync(filePath);
    } catch (e) {
    } finally {
        var stream = fs.createWriteStream(filePath, {flags : 'w'});
        doc.pipe(stream);

        doc.image('images/logo.jpg', 0, 15)

        doc.font('fonts/PalatinoBold.ttf')
            .fontSize(25)
            .text('Invoice for '+name , 100, 100)
            .text('Amount: $'+amount , 100, 200)

        doc.addPage()
           .fontSize(25)
           .text('Here is some vector graphics...', 100, 100)

        doc.save()
           .moveTo(100, 150)
           .lineTo(100, 250)
           .lineTo(200, 250)
           .fill("#FF3300")

        doc.scale(0.6)
           .translate(470, -380)
           .path('M 250,75 L 323,301 131,161 369,161 177,301 z')
           .fill('red', 'even-odd')
           .restore()

        doc.addPage()
           .fillColor("blue")
           .text('Here is a link!', 100, 100)
           //.underline(100, 100, 160, 27, color: "#0000FF")
           .link(100, 100, 160, 27, 'http://google.com/')

        doc.end()
    }
    return [name, filePath];
}

Yeah, returning an Array here, hmm, I miss tuples.

  1. NodeMailer and the SMTP Pool extension for sending large batch of emails.

Here again, I remember writing an SMTP library in AS3 for that on top of flash.net.Socket. NodeMailer here again works seamlessly, here is the core function that takes the path of the PDF and the name of the person to send it to and does the job:

// core email sending function
function sendEmail (to, from, names){
    var mailOptions = {
    from: from, // sender address
    to: to, // list of receivers
    subject: 'Invoice July 2014', // Subject line
    html: 'Hi '+names[0]+", please find attached your invoice for the July 2014 tuition.", // plaintext body
   // html: '<b>Hello world ✔</b>', // html body
    attachments: [
        {   // utf-8 string as an attachment
            filename: 'invoice.pdf',
            path: names[1]
        }]
    };
    // send mail with defined transport object
    transporter.sendMail(mailOptions, function(error, info, name){
        if(error){
           // document.write(error);
        }else{
            document.getElementById('output').className = "alert alert-success" 
            document.getElementById('output').innerHTML = ('Email(s) successfully sent.');
        }
    });
}

Now that I had my application working, the question was, how do I package this Node app to my Daycare without any friction, basically no dependencies for them, just like a normal standalone app. This is where Node-WebKit comes into play.

Node-WebKit is basically the WebKit engine that allows you to call Node modules from the DOM. So Node gives you the VM (V8), Node-WebKit plus it with a DOM. Super powerful, all you need to do is create a JSON descriptor file for the window size and app entry point:

{
"name": "hello",
"main": "index.html",
"window": {
"toolbar": false,
"width": 800,
"height": 600
}
}

Nuwk!Then you can use Node from your traditional HTML code, import your modules and use them normally. When it comes to packaging, you will place a copy of your node modules next to your .html and .js files. You can distribute your app like this, as a folder with the Node-WebKit executable, or even better you can create a fully standalone .app with its own icon, exactly like a normal app.

For this, I used Nuwk! (Mac only). Still a very early project, but super useful.

Comments (3)

  1. Só cool

    Wednesday, July 30, 2014 at 2:04 pm #
  2. Francois wrote:

    I’m curious about your nodewekit choice, did you consider using CEF ? brackets.io instead ?

    Thursday, July 31, 2014 at 4:01 pm #
  3. Patrick wrote:

    This is really fantastic! I can see how all your previous experience with different languages helped you put this together.
    Love it! Thanks for sharing.

    Saturday, August 2, 2014 at 7:27 am #