Creating a note taking app using the Electron API and Node.js.

I’ve been fascinated by creating an electron app for a while now. I tried previously and failed. ( Well not really ) I didn’t complete it. This time however I have created an markdown editor. MD files are what GitHub uses for their Readme’s. Follow along has I describe how I created this app to experiment with the Electron API.

Electron allows you to build cross platform desktop apps with JavaScript, HTML and CSS. You can learn more about electron here. Electron runs on node and in order to develop you application you will need to have node installed on your computer. You can learn more about node here and how to install it. I would recommend using the node version manager to get started.
To get started I downloaded a sample application from GitHub to begin building my app. To get my finished application check here.

# Clone this repository
git clone https://github.com/wyntonfranklin/electron-tutorial.git
# Go into the repository
cd electron-tutorial
# Install dependencies
npm install
# Run the app
npm start

Once that was done my app was ready to begin developing. I used Visual Studio to code my application.

The Setup

The package.json file has some important information about your app.

{
  "name": "electron-quick-start",
  "version": "1.0.0",
  "description": "A minimal Electron application",
  "main": "main.js",
  "scripts": {
    "start": "electron ."
  },

The main and scripts keys are important. The main key defines your entry script for your application. This is where you will setup your application. The scripts sections allows you to run shorter commands. To start the application all you have to do is run –

npm start

This command description is shown in the package JSON file as actually being –

electron .

which is the actual command that starts up electron.

The Main.js file

The main.js file has the startup configurations for your app. You can review this file from the repo. Some things to note.

  // Create the browser window.
  mainWindow = new BrowserWindow({width: 800, height: 600})

  // and load the index.html of the app.
  mainWindow.loadFile('index.html')

  // Open the DevTools.
  mainWindow.webContents.openDevTools()

The mainWindow has a width and height set. Then we load the html file which is also in the base directory. We open developer tools so we can easily debug our application using the

mainWindow.webContents.openDevTools()

command.

The View

The view is the index.html file. It shows our main view and includes our css and js scripts. At the end of the view we add our script files that have the business logic for our application. Note how jQuery was added to the application context.

    <script>window.$ = window.jQuery = require('./vendor/jquery/jquery.min.js');</script>
    <script src="vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
    <!-- Renderer Script -->
    <script> require('./js/renderer.js');</script>

The HTML template was taken from a bootstrap template site your can find it here. The template has a black side bar which we use for the recent files menu and then the main content view were we edit our markdown files. To create a markdown editor I used the Simple MDE markdown editor plugin for JavaScript. You can learn more about it here.

The Renderer.js

This file has the bulk of our business logic. We instanciate classes to use, get references to DOM elements and set listeners in this file. We required it in the view shown above. Alot is going own in this file but I’ll review some interesting parts.

Create a new file

In order to create a new .md file I used a async function. I checked to ensure the filename was accurate and the I used the fs module from node to write the file.

const fs = require('fs');  // require fs

async function writeToFile(text, filename){
    if(filename!== undefined && validateFile(filename)){
        fs.writeFile(filename, text, function(err) {
            if(err) {
                //return console.log(err);
            }
            return true;
        }); 
    }
}

Get the File Name

To get the filename from the path I used this little helper function to show the file name.

function getFileName(fullPath){
    if(fullPath !== undefined){
        return fullPath.replace(/^.*[\\\/]/, '');
    }
}

Open File Dialogs

To open files in electron the API has a dialog feature that allows you to do this. Below is the function to open a “save dialog”. Well I’m using it has a save dialog. It returns the name of the file that you selected. To use the dialog we had to use a special require statement. Also you will notice the default path setting. So you can choose the name of the file before the dialog shows up.

// renderer.js file

const {dialog } = require('electron').remote;

// requires default path string
function openSaveDialog(df){
    var filename= dialog.showSaveDialog(
        { defaultPath: df, properties: ['selectFile'] });
    setActiveFile(filename);
    return filename;
}

To select a file to open and edit

function openSelectFileDialog(){
   var files = dialog.showOpenDialog(
       { properties: ['openFile'] });
    var filename = files[0]; // returns array. 
    return filename;
}

Rename File

To rename a file I used the path module in node to get some extra functions. The path.dirname gets the directory of the current file and I use join to add the new filename and created a file in that directory.

// renderer.js file

const path = require('path'); // require

function renameFile(filename){
    var currentPath = getActiveFile();
    var basePath = path.dirname(currentPath);
    var newPath = path.join(basePath, filename);
    createFile(newPath);
}

That’s pretty much the business logic. Nothing fancy here. 🙂

Menus

To create the menus for the electron app we have a file called menu that holds most of the functionality. In order to render the menu the code is shown below.

// main.js file  

  template = require('./menu.js')(mainWindow);
  const menu = Menu.buildFromTemplate(template)
  Menu.setApplicationMenu(menu)

We create a template, require the menu.js file and build the template and set it to our application.

A sample menu is show below. The menu items take certain attributes. You can learn more about these attributes here. The role attribute gives you default features. However you can create your own menu item by adding a label and a click function to define the functionality. That’s what I did. I added menu items for creating a file and opening a file. Note the mainWindow.webContents.send function. You can add attributes to this function and access them using the arguments attribute in the ipcRenderer function. You can learn more about this function here. The MainWindow attribute in menu.js is actually the Browser Window that I passed to this file when I required it. You can see that above.

// menu.js file
   
const template = [
        {
         label: 'File',
         submenu: [
            {
                label:"New File",
                click () { mainWindow.webContents.send('new-file'); }
            },
            {
               label: 'Open File',
               click () { mainWindow.webContents.send('open-file'); }
            },
            {
               type: 'separator'
            },
            {
               role: 'close'
            },
         ]
      },

The type named separator creates a line menu item.

In order to get the click event in the renderer.js file I used the function shown below. The function registers an function on the event description “new-file”. You can learn more about the ipcRenderer here.

// renderer.js file 

electron.ipcRenderer.on('new-file', (event, arg) => {
   var filename = openSaveDialog("");
   saveFile(filename);
});

The custom menus I created in the application are –

  • New File – Create a new File
  • Open File – Open a file
  • Clear Recents – clear the recent files database
  • Toggle Recents – Hide/Show the recent files sidebar
  • Learn More – Link to git repo

The Recent Files Database

In order to track my recent files I edited I added a flat file database to my project dependencies. In fact let me show you my current dependencies.

// package.json file
  
"dependencies": {
    "lowdb": "^1.0.0",
    "notifyjs": "^3.0.0",
    "simplemde": "^1.11.2"
  }

The flat file database is Lowdb. You can learn more about that here. Lowdb is a JSON flat file database for node or JavaScript. The setup is pretty simple

// renderer.js file

const low = require('lowdb')
const FileSync = require('lowdb/adapters/FileSync')

const adapter = new FileSync('db.json')
const db = low(adapter)


db.defaults({ files: [], count: 0 })
  .write() // default values

What I’ve basically done above is create a table named files. To retrieve the files I create the getRecentFiles function to handle the logic. This function is also used to display the recent files list.

// renderer.js file

function getRecentFiles(){
    var files = db.get('files').value("files"); // get files
    var htmlView = createRecentFilesView(files);
    recentFiles.empty();
    recentFiles.append(htmlView);
}

To remove a file is just as simple –

// renderer.js file

function removeAllRecentFiles(){
    db.get('files')
  .remove().write();
}

Packaging

To package my application I had to install another dependency. A dev dependency. I installed the electron builder using the following command –

npm install electron-builder --save-dev

Once it is installed I updated by package.json to add some required fields. My JSON file was missing some author information so I added that.

 "author": {
    "name": "Wynton Franklin",
    "email": "wffdevservices@gmail.com",
    "url": "https://wftutorials.wordpress.com"
  },

Then the configuration for the builder is shown below. I built this on Linux machine. Apparently electron builder only builds packages based on your current machine. So this configuration worked find more me. Once I opened the AppImage my app started. For windows the configuration might be different.

  "build": {
    "appId": "com.igestdevelopment.markdowneditor",
    "linux": {
      "target": [
        "AppImage",
        "deb"
      ]
    },
    "win": {
      "target": "squirrel"
    }
  },

Two sources of information I used to get more information on this I added below.

Guide to Build – https://medium.com/how-to-electron/a-complete-guide-to-packaging-your-electron-app-1bdc717d739f

Electron-Builder Docs – https://www.electron.build/configuration/configuration

Conclusion

Creating a desktop application using HTML, CSS and JavaScript was fun and not really hard to do. For me the hardest part was to get out of the pages mindset. Once you view your app as a single page app and not a web application its quite easier to understand how you should code your application.

One thought on “Creating a note taking app using the Electron API and Node.js.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s