Last week I did a webinar on creating desktop applications with DevExtreme and Electron. In case you missed it, it is available on our YouTube Channel
This was also one of the first webinars where a swift introduction on DevExtreme with Visual Studio Code, Git, Node/NPM (Node Package Manager) and the Command-Line was performed. In case you have heard the term NPM, but don’t know exactly what it is; NPM is for Node what Nuget is for dotNet.
For all of you wanting to redo the demo project on your own – a simple ToDo app – I have written down the steps that where performed in the webinar.
Prerequisites
Before opening a command prompt, make sure you have the latest versions installed of the following:
With these tools setup, you can fire up a command prompt and create a folder which will hold the project and navigate into it.
Make sure you use some short folder structure like C:\Src\App1, since the MAX_PATH value in Windows is reached fairly quickly when using NPM and node modules!
C:\cd \Src C:\Src>md App1 C:\Src>cd App1
No project template?
Because Visual Studio Code doesn’t support Project templates out of the box, I decided to use a GitHub repo as project template by cloning it.
The repo can be found at: https://github.com/donwibier/DXDevExtremeKnockoutTemplate, and we can clone it to our App1 folder with the following command at the command prompt:
C:\Src\App1>git clone https://github.com/donwibier/DXDevExtremeKnockoutTemplate .(don’t forget the dot at the end)
Once the cloning is finished, we can start VSCode like:
C:\Src\App1>code .(mind the dot)
The package.json already includes the required NPM packages but if you’re new to NPM, it might be worth taking a look at it. You could e.g. change the application name and version number.
If you’re used to the full Visual Studio experience and have used the project template which comes with DevExtreme, you might recognize the folder structure as well as several files provided with this clone.
One thing which is slightly different, is the lack of JavaScript files in the /js folder and the references in the index.html. This is because we will use NPM to get all dependencies for this project.
Because we made a fresh clone, we need to get all those dependencies through NPM. This is done by the following command:
C:\Src\App1>npm install
After some time – depending on your internet speed – NPM finishes. In our case it comes with a couple of warnings which we can safely ignore. This project will not use Angular, Globalize or Webpack.
Now, we can start the project in our browser by issuing the command:
C:\Src\App1>npm start
The package.json will be checked for the scripts / start setting which will execute the lite-server and show a browser with our SPA (Single Page Application).
Lite-server is a small web-server – much like IIS Express for ASP.NET development – to test SPA web-based apps. It is written in Node by John Papa and Christopher Martin. It is specified as dev-dependency in the package.json, so it was downloaded and configured during our npm install command. It has a couple of convenient features like browser-sync. This feature monitors the source folder for changed files and automatically refreshes the browser used for testing.
We will not be using this since we’re going to create a desktop app. Let’s hit Ctrl+C in our terminal window to exit the lite-server.
Getting Electron in the project
So, let’s turn this into a Desktop app by adding the Electron package to the project. This is done by entering the following command:
C:\Src\App1>npm install electron-prebuilt –save
This will download the Electron package and stores the dependency in our package.json file so it will look like this:
The npm install only caused step 1 to be added to the package.json file because of the –save option. Since we’re not using the lite-server anymore, we could either remove the lite-server dev-dependency manually from the package.json file or we could issue the command:
C:\Src\App1>npm remove lite-server –save-dev
If we want the electron runtime to boot up when we issue the npm start command, we need to change the scripts / start option manually to electron main.js as mentioned in step 3.
Initializing the Electron app
So now everything is almost setup to run our JavaScript SPA as a desktop application. If you look closely to the start configuration and next to your project folder, you might notice that one file is missing – main.js.
The main.js file is a JavaScript file containing the bootstrap code for initializing the main process of the desktop application. It initializes the bare chromium browser window and determines what to do when certain thing concerning that window are happening – like minimizing / maximizing and closing the window.
The content of this file is pretty much the same as found on the Quick start guide available at the http://electron.atom.io/.
const electron = require('electron'); // Module to control application life. const app = electron.app; // Module to create native browser window. const BrowserWindow = electron.BrowserWindow; global.settings = { databaseFolder : app.getPath("documents") } // Keep a global reference of the window object, if you don't, the window will // be closed automatically when the JavaScript object is garbage collected. let mainWnd; function createWindow() { // Create the browser window. mainWnd = new BrowserWindow({ width: 1200, height: 700 }); // and load the index.html of the app. mainWnd.loadURL(`file://${__dirname}/index.html`); // Open the DevTools. //mainWnd.webContents.openDevTools(); // Emitted when the window is closed. mainWnd.on('closed', () => { // Dereference the window object, usually you would store windows // in an array if your app supports multi windows, this is the time // when you should delete the corresponding element. mainWnd = null; }); } // This method will be called when Electron has finished // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. app.on('ready', createWindow); // Quit when all windows are closed. app.on('window-all-closed', () => { // On macOS it is common for applications and their menu bar // to stay active until the user quits explicitly with Cmd + Q if (process.platform !== 'darwin') { app.quit(); } }); app.on('activate', () => { // On macOS it's common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. if (mainWnd === null) { createWindow(); } });
The code inside the main.js file is actually being executed in the Node context of an Electron application. One of the nice things in Node is the use or the require(…) construct to include npm packages.
A nice side-effect is that we can also use the require method inside our render process(es) to include npm packages. Render processes are basically the rendering of our SPA app – html/CSS/JavaScript assets inside the Chromium window.
The reason that we can use require(..) in our render process is because Electron adds a couple methods to the JavaScript Window object, which also allows us to exchange data between the main and render processes and to facilitate the Electron API to access local resources and O.S. specific features.
JQuery comment
Those extra methods mentioned before are causing JQuery to fail while initializing inside an Electron window for various reasons. This can be fixed easily by changing the index.html script sections at the bottom of the page. Let’s also make use of the require construct:
Original code in index.html:
<body><div class="dx-viewport"></div><script src="./node_modules/jquery/dist/jquery.js"></script><script src="./node_modules/knockout/build/output/knockout-latest.js"></script><script src="./node_modules/devextreme/dist/js/dx.all.js"></script><script src="./js/db.js"></script><script src="./index.js"></script><script src="./layouts/Desktop/DesktopLayout.js"></script><!-- ViewModels --><script type="text/javascript" src="./views/home.js"></script></body>
Electron changes in index.html:
<body><div class="dx-viewport"></div><script> //because of electron, we need to include jQuery slightly differentwindow.$ = window.jQuery = require("jquery"); window.ko = require("knockout"); var dxapp = dxapp || {}; require("./node_modules/devextreme/dist/js/dx.all.js"); require("./layouts/Desktop/DesktopLayout.js"); require("./js/db.js"); require("./index.js"); require("./views/tasks.js");</script></body>
With these modifications in place, we're ready to boot up our desktop app by entering the command:
C:\Src\App1\npm start
Final comments on the webinar project
In the webinar, I showed how to use a local SQLite database to store the ToDo items. For this I replaced the /js/db.js file with the one hosted on github. I also added the npm package sqlite-sync to the project with:
npm install sqlite-sync –save
This script also requires the app.json file to be present in the root folder of our project and it shows you how to work with configuration settings in an Electron app.
The db.js script shows you a way on how to create a DevExtreme Datasource with a custom store which supports paged results, sorting and filtering. This functionality is used in the /views/tasks.js file to create a proper viewmodel for the view /views/tasks.html file. Here I added a dxDataGrid widget.
The entire project created during the webinar is also available on github and can be tested with the following commands in your terminal window:
C:\Src>md app2 C:\Src>cd app2 C:\Src\app2>git clone https://github.com/donwibier/DXDevExtremeElectron . C:\Src\app2>npm install && npm start
In case you want to know more on Electron, please check their documentation at http://electron.atom.io/docs/ . This also contains information about packaging you application for the specific platforms, best practices and things to consider when building a cross-platform app.
Don’t forget to test it on a Linux machine as well as a Mac and do let me know what you have created with DevExtreme and Electron!