Cross-Platform Development With NW.js

More applications are taking advantage of web technologies. For example, Brackets, Peppermint, and Pinegrow are programmer editors made from HTML, JavaScript, and CSS. This allows for using familiar tools, but also makes applications more cross-platform in nature. In this tutorial, I will show you how to use NW.js for making a simple programmer’s editor that you can use in Windows, Mac OS X, and Linux.

Introduction and Downloading NW.js

NW.js, originally known as Node Webkit, is the packaging of Node.js and WebKit HTML render in a package for running local applications. The NW.js version is using io.js, a more current version of the V8 JavaScript engine with more ES6 support. Since io.js is 100% compatible with the latest Node.js, all libraries and programs using Node.js are usable with io.js as well.

To get started, download all three OS versions from NW.js or just the versions you want to run the project. I will be developing on my MacBook Air, but you can use any system you want. The project is Fun Editor: an easy to use, single document code editor. It is built with the Linux motto: A single task done well!

Getting the Parts

To get started, node or io.js has to be installed on your system. Once you install node or io.js, the npm command will be on your system. This command is for installing different JavaScript libraries.

The first library is Bower. Type the following in a command line prompt:

On some systems, you might need to use sudo before the npm command to run it in super user mode.

The bower command is a package manager for many web technologies. It gives an easy way to add web related items to your project.

To do custom actions to the DOM, this editor will make use of Zepto.js JavaScript library instead of jQuery. Since Fun Editor will use the library for DOM work only, Zepto fits the bill at a fraction of the size.

Create a new directory for the project. In the directory with the command line, type the following:

There will now be a new directory called bower_components. In this directory, bower installed the less and zepto libraries. That was much easier than finding the web site and downloading.

The Ace JavaScript library is the base for this editor. It is a flexible and easy to use editor in JavaScript designed for embedding into web sites. To install, type the following in the terminal in our project directory:

There is now a new directory ace. All of the sources for the library are in this directory. The library needs to be compiled to the compact format to speed up loading. In the command line, type:

These commands put you into the ace directory, install all needed libraries for the ace editor, and create the minimized library files in the build sub-directory.

This project will use the node-watch library to know if the file in the editor has changed. This library activates a callback function whenever the specified file changes. To install this library in to our project, the project directory needs to be a node package directory. In the command line in the project directory, type the following:

The npm program will ask several questions about the project. Once you answer the questions, a package.json file is in the project directory. The npm command will store the names of all libraries installed in this file. That way, giving the project file to someone else enables them to create the same working environment.

Now to install the node-watch library, type the following:

Once finished, there is a node_modules directory with the library inside. The –save flag told npm to save the library into the project and not globally.

The last piece of software to install is Emmet. You cannot have a code editor without Emmet. Get the Emmet code from Emmet on GitHub and save it to the file emmet.js in the js directory.

Now that all the pieces are in place in the project folder, everything is ready to put it together!

Putting It All Together

The first item needed for a NW.js project is its project file. Unfortunately, the node project file uses the same file name. Since the node project file is not needed during development, it can be moved aside until needed again. In the command line in the project directory, type the following:

These commands moved the original package.json file to node.package.json and created an empty file called package.json. In the package.jsonfile, type the following:

This file tells NW.js how to launch the program. The different fields are:

description

This should be a short description of the program and what it does.

main

This is the main HTML file to open. This file should contain all the HTML for the main page of the program.

name

This is the name of the program.

version

This should be a version number for the program.

window

This is a json array of values to describe the User Interface of the program. This array has the following items:

height

This is a number of pixels for the height of the program window when started.

width

This is the number of pixels for the width of the program window when started.

show

This is a Boolean telling NW.js whether or not to show the main windows upon loading. I have it set to true. If you set it to false, make sure there is some way to activate it later.

title

This is the default title for the program. It will be used when loaded, until some code in the program changes it.

toolbar

This Boolean tells NW.js whether or not to have a toolbar. Since FunEditor is a minimalistic editor, this is set to false. But, if you ever need to use the Dev Tools to debug your editor, you can set this to true and an icon for the Dev Tools will be there.

icon

This is a relative path of the icon to use for the program. The project directory should be the base for the reference to this file.

Since a main.html file is in the configuration, it is the next file made. In the project directory, create the main.html file and put this code in it:

That is the main program. It is a simple HTML page that is mostly a list of JavaScript files to load, one Less file for styling everything, a div with an id of editor for the editor, and a div with an id of info and some spans for doing the status line. I took the styling for the status line from my tutorial on Getting Spiffy With Powerline. It doesn’t use Powerline—it just looks like it. At the bottom are two inputs that do not get displayed. These are for the NW.js open file dialog and save file dialog.

Good coding practices would say to load the JavaScript at the end of the page. Since the window for the page is not shown until the program tells it to show, it does not matter.

To make the HTML look right, styling is the next item to create. Create a new directory called less in the project directory. Then create the file default.less and add this to it:

This styling information is to make the editor div position absolute and take up all the browser window area except for 25 px at the bottom. That area is for the status line.

At the top of the file, you will notice several Less variable definitions. Since the same colors get used in more than one place (i.e. the span for line number and its “arrow” span afterwards), I made them into a Less variable. That way, I only have to change one item to change all of them. Stylus or SASS could be used as well, but Less allows for dynamically changing them in the code. Great for theme changing down the road.

Now for the main program file. In the js directory, create a file called FunEditor.js and place the following code:

At the top of the file, you will see the declaration of the FunEditor object and some variables. After the variables, there are three require statements. These statements load the node-watch library for watching a file change, the nw.gui library which allows for interaction with the graphical user interface, the fs library which allows access to the file system, and the os library for working with the operating system. The gui, fs, and os libraries are part of the NW.js program, while the node-watch is a npm downloaded library.

With the gui library, the FunEditor.clipboard variable contains the clipboard object for accessing the system clipboard. Likewise, the FunEditor.win variable contains the window object for the main windows created by NW.js for this program.

After the libraries and global variables are loaded, several helper functions are created. All functions that start with FunEditor are functions for running the editor and are not specific to NW.js. The other functions are required by NW.js.

I described each function here:

FunEditor.handleDocumentChange

This function loads the editor with the correct syntax highlighting based on the document loaded. It also sets the window title and the document name in the status line. When the window title is set, it checks the operating system being used with FunEditor.os.platform(). If it is a Windows operating system, the search for the last directory separator has to be different since Windows uses \, while OS X and Linux use /.

This function also sets up file watching using FunEditor.watch. When the file given to the function is changed, the callback function reloads the file in to the editor.

FunEditor.setCursorLast

This function will set the cursor to the last saved location. The location is saved every time the cursor is moved and recorded in the status line. Whenever the file is reloaded after a changed, this function is used to put the cursor back to the last known location.

FunEditor.newFile

This function is for creating a new, blank file.

FunEditor.readFileIntoEditor

This function uses the FunEditor.fs library reference variable to read in the file from the file system and place it into the editor. It then clears the selection and puts the cursor at the last known location.

FunEditor.writeEditorToFile

This function takes the current state of the editor and saves it to the file system using the FunEditor.fs variable.

FunEditor.copyFunction

This function uses the NW.js library to take the currently selected text and put it in to the system clipboard.

FunEditor.cutFunction

This function is the same as FunEditor.copyFunction, except that it deletes the selected text from the editor.

FunEditor.pasteFunction

This function takes the clipboard contents using the NW.js library for the clipboard and pastes it to the current cursor location in the editor.

FunEditor.openFile

This function opens the system open file dialog by triggering a click on the hidden input element. This is particular to NW.js as well.

FunEditor.saveFile

This function saves the editor contents to the file if it was loaded from a file and that file has write access. Otherwise, it opens the system save as dialog for the user to select a file. This also works by triggering a click on the hidden saveFile element.

FunEditor.initMenus

This function creates the graphical menu elements for an in-context menu and the system menu. This makes use of the FunEditor.gui library.

The main menu for Mac OS X has added functionality. In that section, the FunEditor.os library tells the software platform. If it is Mac OS X, then perform the extra functions needed for the main menu.

Onchosenfiletoopen

This NW.js defined function gets the file selected when the Open file dialog gets closed. This function will then load that file into the editor using the FunEditor.readFileIntoEditor function.

Onchosenfiletosave

This NW.js defined function gets the file selected when the Save As file dialog gets closed. This function will then save the editor contents to the given file using the FunEditor.writeEditorToFile function.

Onload

This NW.js function is called whenever everything defined in the main.html file gets loaded into NW.js. It's equivalent to the document.onload = function(){}; or the jQuery $(document).ready(funcition(){});.

This function gets the editor ready with the desired preferences. The theme is solarized dark, the keyboard to vim layout, active line highlighting, etc. The editor keyboard shortcuts for copy, paste, cut, and save are set with their Windows and Mac defaults. The ace editor takes care of cross-platform issues.

Once the editor is initialized, listeners for the changeStatus, copy, paste, and save editor events are set up. These allow the editor to enforce the use of the system clipboard inside of the ace editor and update the line and column information in the status line. The changeStatus listener updates the vim mode and current cursor location.

I also dug into the save function that Vim mode has (:w). This took some work, but I finally figured it out as well. Ace editor borrows code from Code Mirror to create the Vim keyboard layout. It works, but is not a robust solution.

Onresize

This NW.js defined function gets called whenever the window is resized. For the FunEditor, nothing needs to be done.

Running On Different Platforms

That's all the programming for the editor. Now, to run it on each platform. If you haven’t already downloaded the NW.js for each platform, do it now and follow the instructions for each platform. Take all the files in the project directory and compress them in to a zip archive. When done, change the name of the archive to FunEditor.nw.

On every platform, you can always go to the command line and run the nw command in the project directory. Unfortunately, the basic install does not put the executable file in the system path. Therefore, depending on the shell you are using, you can create an nw alias to the executable file. While developing, I use this in the project directory to test:

Once I have the FunEditor.nw created, I use:

Once verified working, I start the packaging for my other systems.

Mac

Once NW.js gets installed on the Mac, you will have the nwjs.app file in your Applications directory. In the directory in which you compressed the application, you can run the program with the following command line:

To make a clickable package, change the FunEditor.nw file to app.nw. Copy the /Applications/nwjs.app to /Applications/FunEditor.app. In Finder, right click on the application and select Show Contents. Place the app.nw file in the Resources directory and change the icon to one that you like. I include one in the download. Remember to keep the nw.icns file name.

To get the proper name in the menu, you will have to change the info.plist file. Open it up and change the contents to:

I took the info.plist file for NW.js, removed everything that is specific for it launching files with the nw extension, changed the version to 1.0, and changed the name to FunEditor.

FunEditor on Mac OS X
FunEditor on Mac OS X

Once that is done, you have a clickable application for FunEditor.

Windows

The easiest way to run the editor on Windows is to use a batch file. Create a file FunEditor.bat somewhere in your system path. Type the following into the file:

FunEditor on Windows 7
FunEditor on Windows 7

When you double click the batch file, the FunEditor will open.

Linux

Once you have the NW.js program downloaded, make sure the directory with it is on your path. Create a script file that will call the NW.js program with your program. Create a file in your path called FunEditor with this:

Save and set to executable permissions with:

If you run that command now, it should launch the editor for Linux!

FunEditor on Arch Linux
FunEditor on Arch Linux

On my Arch Linux, FunEditor is fun to use!

Conclusion

With this project, you learned how to take advantage of NW.js to create an editor runnable on Windows, Linux, and Mac OS X. This project has much room for improvements. Have fun making this little editor into your dream editor and use it on every operating system!

Tags:

Comments

Related Articles