---
title: "Learn to Build Oracle JET Native Apps with Electron and Node.js"
slug: "oracle-jet-native-apps-with-electron-and-node-js"
date: 2016-09-26T03:20:09Z
author: "Justin Biard"
tags:
  - "node.js"
  - "oracle jet"
  - "electron"
description: "Oracle JET can be combined with Electron to build native desktop applications using HTML, CSS and JavaScript. Follow along with this post to learn how."
draft: false
archive: true
---

Oracle JET (JavaScript Extension Toolkit) - hereafter JET - is a collection of open source JavaScript technologies for building client facing web applications. Oracle had this to say about it:

> \[Oracle JET\] is targeted at intermediate to advanced JavaScript developers working on client-side applications. It's a collection of open source JavaScript libraries along with a set of Oracle contributed JavaScript libraries that make it as simple and efficient as possible to build applications...

JET ships with [examples](http://www.oracle.com/webfolder/technetwork/jet/jetCookbook.html) for user interface (UI) components, data binding, Single Page Application (SPA) architecture, charts / graphs, and more.

In this post I will look at building desktop software by deploying JET applications as native cross-platform applications using [Electron](http://electron.atom.io). Although I talk a bit about JET, this is not a JET tutorial. You can learn more about JET [online](http://oraclejet.org) and in the JET [Massive Open Online Courses (MOOC)](https://apexapps.oracle.com/pls/apex/f?p=44785:145:8819137691196::NO:RP,52:P145_EVENT_ID,P145_PREV_PAGE:5292,143).

_**NB.** This is a technique I thought was cool that shows some promise and in which I built a rapid prototype. I have not deployed anything to production or done comprehensive testing. That said, products like [Slack](https://medium.com/ben-and-dion/how-slack-built-a-well-loved-product-going-against-peter-thiel-and-native-app-fashion-2abbbe5a022f#.o1fsp34rv) have used similar techniques in their software so I feel like there is already evidence to suggest it could work well._

### Introduction to the Sample

Below is a screenshot of where we are headed in this post. This is a prototype for a desktop application based on the JET `navdrawer` template and it is running inside of [Electron](http://electron.atom.io). This desktop application is also fully responsive to screen size changes and it can be compiled for distribution and off-line execution (Internet not required.)

![Oracle JET Example 1](https://icodealot.com/img/647657a6/jet_sample_02.gif)

So what?

1. Clearly you won't use this technique to solve every problem, however, you can re-use your HTML, JavaScript and CSS development skills (and JET) to quickly build or prototype desktop software. Write your software once and deploy just about everywhere. JET works in desktop and mobile web browsers and it can be packaged for deployment in Electron (or Cordova) for native integration.

2. Developing applications for the web browser that front end some Node.js functionality usually requires both a server and a client. With Electron you can develop integrated Node.js features directly into your desktop deployed application. (Perhaps this is a gross over simplification but generally `npm install <some package>` and you can call it in your Electron application.)

3. Finally, deploying native applications will grant you more access to the host operating system than you will typically get through applications deployed via web browser. Electron brings HTML and JavaScript applications into the realm of desktop software much like [Cordova](http://cordova.apache.org) has done for cross platform native mobile applications.


Of course, there are many other factors and pros / cons to consider. I am highlighting a few things that I am excited about...just some awesome sauce for your next desktop software project or data visualization prototype.

### Install All The Things

Like building software in any other integrated development environment (IDE), software development kit (SDK), Etc., we need to install the software to build our applications.

1. [Git](https://git-scm.com/downloads) \- to automatically download code from Git repositories
2. [Node.js](https://nodejs.org/en/download/) \- the primary engine of this example
3. Optionally, some source code editor (Atom, Sublime, TextMate, UltraEdit, NetBeans, etc...)

I'm not going to provide detailed explanations for installing these packages. They are pretty self explanatory once you have the installers downloaded.

**Note:** If you have older versions of Node.js and Node Package Manager ( `npm`) installed, I recommend that you install the latest production version of Node.js and update `npm` by running `npm -g install npm`.

### Getting Started

If something goes wrong here jump over to Oracle's [getting started page](http://www.oracle.com/webfolder/technetwork/jet/globalGetStarted.html). At the time of this writing you need to run a couple of `npm` commands to get started. Except for the Electron integration, the process used here is the same as noted in Oracle's guide.

1. `npm -g install yo bower grunt-cli`
2. `npm -g install generator-oraclejet`

Once you have run the `npm` commands above you should be able to use [Yeoman](http://yeoman.io) to deploy the `navdrawer` Oracle JET sample application. This is the basis for our desktop application. Open your terminal or DOS command prompt and change directories to where you want to start. Next we will run commands to create our project folder.

I called my application **Jetopia2** in the following commands. You can replace that with whatever you want to call your project.

1. `yo oraclejet Jetopia2 --template=navdrawer`
2. `cd Jetopia2`
3. `grunt build`
4. `grunt serve`

If you run into any errors you'll need to investigate them as needed. For example, I ran this process on a fresh Windows VM and received a `ENOGIT` error from Yeoman because I forgot to close and re-open my DOS terminal after installing Git, to get an updated `PATH` variable.

If all goes well you should see a new web browser window or tab open with the default `navdrawer` application template displayed.

![Nav Drawer Template](https://icodealot.com/img/647657a6/navdrawer_default.jpg)

This template deploys with four different tabs that navigate to HTML/JS application files. These are [Knockout](http://knockoutjs.com) templates with automatic data binding that Oracle has defined to help us get started quickly.

### Setup Electron

Now that we have the basic `navdrawer` JET template running in the web browser we can install Electron, add our main JavaScript program and run our native application. First open a terminal to your JET project folder and run:

1. `npm install electron-prebuilt --save-dev`
2. `npm install electron-packager --save-dev`

Next create a new JavaScript file in your `src` folder. I called mine `jetopia.js`. This is the Node.js program that Electron will run when we start our native desktop application. Add the following code:

```javascript
const electron = require('electron');
const{app, BrowserWindow} = electron;

app.on('ready', () => {
    let win = new BrowserWindow({
        width: 1000,
        height: 600
    });
    win.loadURL(`file://${__dirname}/index.html`);
});

```

This code tells Electron to open a new window and to load the `index.html` file. Since our script is located in the `src` directory it will be automatically deployed to the `web` folder in the build step. If you organized your project differently you may need to play around with the path in the `loadURL()` call.

My `jetopia.js` and `index.html` files are located in the same directory so I am using `${__dirname}` as a shortcut for the relative path.

Next open the `package.json` file and add the following lines. The file should already exist from the original call to `yo oraclejet...` which created and initialized your project folder.

```javascript
...
  "main": "./web/jetopia.js",
  "scripts": {
    "start": "grunt build && electron ./web/jetopia.js"
  },
...

```

This will allow us to use `npm start` to build our JET application, launch Electron and later on will tell the Electron packager which script is the `main` script. We need to specify the `.web/` directory here (instead of `src`) because `grunt build` will assemble our code into the `web` directory.

### Run Oracle JET Native

Now that the `navdrawer` application template is generated and Electron is installed, go ahead and run `npm start`. You should see an Electron native application window launch with your JET `index.html` file loaded.

![Electron Step 1](https://icodealot.com/img/647657a6/electron_step1.png)

We got the application window but something isn't quite right yet. None of our `navdrawer` templates or menus made it into the application. It turns out that JET uses [RequireJS](http://requirejs.org) to import JavaScript dependencies.

A pointer to RequireJS has been aliased in JET as `require` and also `requirejs`. This is the main reason why my application is called `Jetopia2`. When I first tried this proof of concept, I spent a few hours untangling the naming conflicts before finding the simplest solution [identified here](https://github.com/electron/electron/issues/303). (see below for the fix / code)

Because our JET application is now running inside Node.js with Electron, `require()` is already defined by the standard modules of Node.js, thus there is a name conflict. Node's `require()` expects a string path argument while RequireJS can handle an Array of module names. Let's fix this now.

### Patching The JET Code

In the `src` directory find the `index.html` file and open it in your source code editor. Add the following code just after your opening `<head>` tag.

```html
...
    <script>
      window.nodeRequire = require;
      delete window.require;
      delete window.exports;
      delete window.module;
    </script>
...

```

Add the following lines of code just above your closing `body` tag of the `index.html` file to fix a missing reference.

```html
...
    <script>
      require.nodeRequire = window.nodeRequire;
    </script>
...

```

Either this is a bug in JET's RequireJS bundle since `require.nodeRequire` is not defined anywhere, or I just don't know what I'm doing yet. (See `fs = require.nodeRequire('fs');` in `text.js` around line 249)

Save your changes. If your Electron application is still running stop it and then re-run `npm start` to build and run the application again. If all goes well here then you should now see we are getting closer to running the JET `navdrawer` inside Electron.

![Electron Step 2](https://icodealot.com/img/647657a6/electron_step2.png)

We have the main application frame running, the require name conflicts are cleaned up but there is no content yet in our main application area. Node.js is looking for the HTML Knockout templates but it cannot find them. To fix that we need to make a change to `src/js/main.js`. Open that file and insert the following code to the top of the `init()` function.

```javascript
...
  if (window.nodeRequire !== undefined) {
    var viewPath = 'text!' + __dirname + '/js/views/';
    oj.ModuleBinding.defaults.viewPath = viewPath;
  }
...

```

This code is used to determine if we are running inside of Node.js and if so, then sets a full path to the location of the Knockout templates. There may have been a more efficient way to deal with this but this is my first attempt so I'm going with this for now.

Save your changes and then re-run `npm start` to build and run the application again. This time everything should be fully functional inside of Electron.

![Electron Step 3](https://icodealot.com/img/647657a6/electron_step3.png)

While you are at it, go ahead and run `grunt serve` as well. Everything should still work in your regular web browser also. Cool!

### About the Cookbook Samples

You should definitely spend some time figuring out how to get code out of the JET cookbook and into your application. [Thanks to this helpful post](http://likeahouseafire.com/2016/01/20/pouring-jet-cookbook-into-quickstart/), I was able to convert the examples in this application fairly quickly.

In summary, you need to replace `require[` from the cookbook JS with `define[`. Generally speaking you also need to remove the jQuery call at the end of the cookbook sample JS and replace it with a `return` statement to return an instance of the ViewModel you are working with.

So the following...

```javascript
...
    var chartModel = new ChartModel();

    $(document).ready(
          function(){
      ko.applyBindings(chartModel, document.getElementById('chart-container'));
    });
...

```

Becomes...

```javascript
...
    var chartModel = new ChartModel();
    return chartModel;
...

```

JET automatically binds your Knockout templates to the fields in the returned view model so this jQuery code is not required for standalone JET applications, it is only used by the cookbook. (Some interpretation required.)

### Build the Dashboard

To build the Dashboard page of the JET application shown near the top of this post open the Cookbook to the [Visualizations > Chart > Common > Associated Views: Selection](http://www.oracle.com/webfolder/technetwork/jet/jetCookbook.html?component=chart&demo=selecting) demo. We will use this code for our Dashboard.

- Copy the template HTML for the sample to your clipboard.

![Cookbook Step 1](https://icodealot.com/img/647657a6/electron_step4.gif)

- Open the Knockout template for the Dashboard and replace the default contents with this cookbook HTML. The file is located under `src/js/views/dashboard.html`.

- Next go back to the cookbook and copy the JavaScript from the `JS` tab to your clipboard. (Right next to the HTML tab)

- Open the view model for the Dashboard page and replace the existing code with the JavaScript from the cookbook. The file is located under `src/js/viewModels/dashboard.js`

- Update the `dashboard.js` file with the two changes noted in the previous section. ( `require` -\> `define` and `jQuery` -\> `return viewModel;`).


With all of these changes implemented, save your files and re-run `npm start`. You should now see the Dashboard of your application running in Electron is populated with the cookbook sample charts and data. If you make your native application window larger and smaller the interface will automatically adjust. (Responsive!)

![Dashboard Complete](https://icodealot.com/img/647657a6/electron_step5.jpg)

### Communicate with Node.js

The final trick is really to figure out how to get JET talking to Node.js so you can do more interesting things with your applications. You could just ignore Node.js all together and make this a client/server application but it is still worth understanding how to get to Node.js and as a result to the `npm` ecosystem.

Electron has two processes that we need to communicate between, a **main process** and a **renderer process**.

We are going to add a button to the About page from the renderer process that will kick off a Node.js function in the main process. The Node.js function will use HTTPS to download some JSON data from Oracle REST Data Services (ORDS) and use a callback function to be displayed on the About page.

This example is cooked up just for show and to help you connect the renderer with the main process. You probably have more practical or interesting ideas about what to do inside of Node.js.

- Open the Knockout template for the About page and replace it's content with the following HTML. (See if you can locate the template using what you learned from updating the Dashboard related files.)

```html
<div class="oj-hybrid-padding">
  <h3>Talk to Node.js</h3>
  <div>
      <div style="padding-left:16px; display:inline-block">
      <button id= "button1"
         data-bind="click: talkToNode,
           ojComponent: {component: 'ojButton', label: 'Fetch JSON data from ORDS using Node.js'}">
      </button>
    </div>
    <div id="ords-data"></div>
  </div>
</div>
```

- Open the view model JavaScript file for the About page and insert the following three lines of JavaScript at the top of the file.

```javascript
const electron = nodeRequire('electron');
const {remote} = electron;
const jetopia = remote.require(__dirname + '/jetopia.js') ;

```

This code is what links the renderer with a reference to any functions that get exported from the `jetopia.js` file and therefore gives us a link to the main process.

- Add a new function inside of the `AboutViewModel()` that will call our `getDataFromORDS()` function from the main process. We will pass a callback function to the main process to insert the result into a `div` using jQuery.

```javascript
    function AboutViewModel() {
      var self = this;
      ...
      self.talkToNode = function () {
        $('#ords-data').html('Fetching data...');
        jetopia.getDataFromORDS((data) => {
          $('#ords-data').html('<pre>' + data + '</pre>');
        });
      }
...

```

- Open the `src/jetopia.js` file and insert the following lines of JavaScript code. This is the Node.js function that will retrieve the JSON results from ORDS and pass it back to the callback function on our JET page.

```javascript
...
const https = require('https');

...

exports.getDataFromORDS = (cb) => {
    var data;
    https.get('https://apex.oracle.com/pls/apex/icodealot/crm/customers/', (res) => {
        res.on('data', (d) => {
            data = data + d;
        });
        res.on('end', () => {
            cb(data);
        });
    });
};

```

Finally, save your changes as needed and re-run `npm start`. Once your Electron application starts up navigate to the **About** tab and click on the new button. You should see JSON results from Node.js loaded into the main content area.

![Talk to Node](https://icodealot.com/img/647657a6/talk_to_node.gif)

And now our JET application is talking with Node.js directly on the main process of Electron. I hope you agree that this is both powerful and awesome!

### Wrapping Up

The theoretical final step in building your native JET applications for distribution (ignoring testing and further development) would be packaging them for deployment. To do that you will need to call the Electron Packager. This was installed earlier in this post if you followed along so we can add these related commands to our `package.json` file.

```javascript
...
  "scripts": {
    "start": "grunt build && electron ./web/jetopia.js",
    "build-mac": "electron-packager --platform=darwin --arch=x64 --version=1.0.0 . Jetopia --ignore=node_modules/electron-*,node_modules/asar",
    "build-win": "electron-packager --platform=win32 --arch=x64 --version=1.0.0 . Jetopia --ignore=node_modules/electron-*,node_modules/asar"
  },
...
```

This is just an example. You will want to investigate and customize the build options. With those two new options added to `scripts` you can now call `npm run build-mac` or `npm run build-win` to generate an executable for distribution. You will need to run the **build-mac** or **build-win** from each of those platforms respectively.

Alternatively you can read the [documentation](https://github.com/electron-userland/electron-packager) for the packager to find out how to build Windows EXEs using Mac. (I use a Windows VM for this on my Mac.)

If you have any questions or need to see the full source for my example project you can grab the code from GitHub at: [https://github.com/icodealot/odtug/tree/master/OracleJET/Electron](https://github.com/icodealot/odtug/tree/master/OracleJET/Electron)

I hope you found this post useful and that it helps you along the way.

Cheers!

### Related Links

Here are some links related to the contents of this post for your reference. These are some of the resources I used along the way.

- [http://www.oracle.com/webfolder/technetwork/jet/jetCookbook.html](http://www.oracle.com/webfolder/technetwork/jet/jetCookbook.html)
- [http://www.oracle.com/webfolder/technetwork/jet/globalGetStarted.html](http://www.oracle.com/webfolder/technetwork/jet/globalGetStarted.html)
- [http://docs.oracle.com/middleware/jet210/jet/developer/toc.htm](http://docs.oracle.com/middleware/jet210/jet/developer/toc.htm)
- [https://www.youtube.com/watch?v=jKzBJAowmGg](https://www.youtube.com/watch?v=jKzBJAowmGg)
- [https://github.com/electron-userland/electron-prebuilt](https://github.com/electron-userland/electron-prebuilt)
- [https://github.com/electron-userland/electron-packager](https://github.com/electron-userland/electron-packager)
- [http://electron.atom.io/docs/](http://electron.atom.io/docs/)
- [https://nodejs.org/en/](https://nodejs.org/en/)
- [https://git-scm.com/downloads](https://git-scm.com/downloads)
- [http://knockoutjs.com](http://knockoutjs.com)
