Writing Desktop Applications with Node WebKit

Writing desktop applications can be done using a variety of languages such as C; C++; C#; Python; Java; etc, but since Node WebKit has been created, they can now also be written very easily using JavaScript, HTML and CSS.

A few other projects to bring JavaScript to the desktop exist such as AppJS, but Node WebKit is in my opinion easier to use.

As the name suggests, it combines WebKit and Node in order to write desktop applications using HTML, CSS and JavaScript for each of the major platforms, Windows; Mac OS and Linux.

This allows for the rapid development of desktop applications using the web technologies that most developers are familiar with already.

Since you can develop your application using normal web technologies, you could also quite easily use something like Phone Gap to turn your project into a mobile application.

You can write anything from encoders and text editors, to music players and video games; essentially almost anything you could write using any other language can be written in JavaScript using Node WebKit, and in those cases where JavaScript won't quite do what you need you can always just write an extension in C++.

Of course, when you need performance nothing will beat languages like C or C++, but for applications where the performance isn't such a big concern Node WebKit can provide a fast way to write applications for each of the major platforms.

The packaged application can also optionally include a copy of Node WebKit so the end user doesn't have to install anything, which can make simple programs quite large compared to their C or C++ counterparts but allows the user to run the program out of the box with no setup required.

To get started all you need to do is download the project from the Node WebKit GitHub page and follow the instructions there.

They provide instructions for a quick start program and instructions on how to package your application up for distribution so the end user never has to install any other software or use the command line to get things running.

Once the project has been downloaded you should have a folder with some dll files in it with an nw.exe file among others (Windows version).

Now we can go through creating a simple notepad application.

There are a couple of files which are required for an application to work.

The first is a package.json file, which contains the meta data of the project, including things like the text in the title bar, min/max/default width/height of the window, whether you want to display the default window frame, whether you want to hide the address bar etc.

{
  "name": "Simple Notepad",
  "main": "index.html",
  "window": {
    "title": "Simple Notepad Demo",
    "icon": "notepad.png",
    "toolbar": false,
    "frame": false,
    "width": 800,
    "height": 600,
    "min_width": 640,
    "min_height": 480
  }
}

Here we have set icon equal to notepad.png which should just be an icon we want to use for our application in the same directory as package.json.

If the icon we specify doesn't exist then Node WebKit will just use it's default icon instead.

Next is an index.html file. This doesn't have to be called index.html, it just has to be named the same as the value of the main key in the package.json file. This will basically be the entry point of the application.

Those are the only two required files for a simple application.

In index.html we can begin by writing the HTML for our application.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Simple Notepad</title>
    <link rel="stylesheet" href="./style.css">
  </head>
  <body>
    <div id="title-bar">
      <button id="open-file">Open</button>
      <input type="file" id="open-file-dialog">

      <button id="save-file">Save</button>

      <button id="close-window">X</button>
    </div>

    <textarea id="plain-text-editor"></textarea>

    <script src="./app.js"></script>
  </body>
</html>

Here we set up a div to be our application's title bar because in package.json we set frame equal to false, which will hide the default OS close button and other buttons normally contained within the frame.

This title bar then contains a button that will be used to close the window (since we hid the default OS close button) and an open and save button, to open files and save files respectively.

The open button will actually call a click event on the hidden file input field.

The textarea will be used to edit the text of the currently opened file.

And finally we include a link tag in the head to link to a stylesheet and a script tag at the bottom of body to link to our application's JavaScript.

Now we need to create the style.css file in the same directory as index.html and package.json, and it should look something like this:

html,
body {
  margin: 0;
  font: 14px sans-serif;
  height: 100%;
}

textarea {
  /* Disable user resizing */
  resize: none;
  width: calc(100% - 20px);
  border: none;
  margin: 0;
  padding: 10px;
  background: #F3F3F3;
  font-family: consolas;
  position: absolute;
  top: 30px;
  bottom: 0;
}

#title-bar {
  background: #5C5C5C;
  padding: 2px;
  overflow: hidden;
  /* Disable user text selection */
  -webkit-user-select: none;
  /* Enable the window to be dragged with this div */
  -webkit-app-region: drag;
}

#title-bar * {
  /*
    Disable drag on all children because it stops the
    user being able to click on things
  */
  -webkit-app-region: no-drag;
}

#open-file-dialog {
  display: none;
}

#close-window {
  margin: 0;
  padding: 3px 7px;
  background: none;
  border: none;
  cursor: pointer;
  float: right;
  color: #fff;
  font-weight: bold;
  border: 1px solid #fff;
}

Finally, to give some functionality to our application we need to create app.js in the same directory as all of the other files.

Because we are running on Node we can just use require to pull in JS files and modules even though our application is running in a browser. For example I could require another file in the same directory by typing var myModule = require('./myModule');

// First we require our dependencies, which in our case is
// just the fs module in node
var fs = require('fs');

// Store the elements that we need to work with in variables
var openFile = document.getElementById('open-file'),
    openDialog = document.getElementById('open-file-dialog'),
    saveFile = document.getElementById('save-file'),
    closeWindow = document.getElementById('close-window'),
    editor = document.getElementById('plain-text-editor');

// Keep track of the current file path
var currentFilepath = null;

// Bind the close window button
closeWindow.addEventListener('click', function() {
  window.close();
}, false);

// Bind the open file button to send a click event to the file input
openFile.addEventListener('click', function() {
  var evt = new Event('click');

  openDialog.dispatchEvent(evt);
}, false);

// Bind the file dialog to read the file and add the contents to the
// text area on change
openDialog.addEventListener('change', function() {
  var filepath = this.value;

  // Read the file using the fs module in Node
  fs.readFile(filepath, 'utf8', function(err, data) {
    if (err) {
      throw err;
    }

    // Replace the content of the textarea
    editor.value = data;

    // Update the currentFilepath variable
    currentFilepath = filepath;
  });
}, false);

// Bind the save button to save the current file
saveFile.addEventListener('click', function() {
  if (!currentFilepath) {
    alert('Please open a file');

    return;
  }

  // Rebuild the string to make sure it saves correctly
  var data = editor.value.split('').join('');

  fs.writeFile(currentFilepath, data, function(err) {
    if (err) {
      throw err;
    }

    alert('File saved');
  });
}, false);

Now that all of our files have been written we can package it up ready for distribution.

The easiest way to do this on Windows is to just zip up all of the files in your project then change the zip's file name to package.nw.

This .nw file can then be put inside the Node WebKit folder and when the nw.exe is used it will automatically load your application from the package.nw file.

This works, but you can also merge your package.nw file with the Node WebKit executable if you want to make it a bit more difficult for users to just open up the package.nw file and start changing your code.

To do it on Windows just open up the command prompt and type:

copy /b nw.exe+package.nw app.exe

Then you just need to zip up your new app.exe file with the required dll, dat and pak files and you are ready to distribute your app.

You will also need to find out which licenses to distribute with your application before actually distributing it, but since this depends on your application you will need to figure this out by yourself.

You could then package all of this up within an installer to automatically extract files and folders and create shortcuts to the .exe file to run the application.

Instructions on methods of packaging up your application for all platforms can be found on the packaging wiki page.

This example was just a simple textarea that loaded and saved the text of files, but the applications you can create with Node WebKit are the same as those you would be able to create with lower level language like C or C++.

Instructions and examples for most things you would need to know about Node WebKit can be found on the wiki page.