Category: Electron

https://cdn3d.iconscout.com/3d/premium/thumb/atom-3d-icon-download-in-png-blend-fbx-gltf-file-formats–molecule-electron-structure-science-chemistry-chemical-laboratory-pack-technology-icons-6842501.png

  •  System Dialogs

    It is very important for any app to be a user-friendly one. As a result you should not create dialog boxes using alert() calls. Electron provides a pretty good interface to accomplish the task of creating dialog boxes. Let us have a look at it.

    Electron provides a dialog module that we can use for displaying native system dialogs for opening and saving files, alerting, etc.

    Let us directly jump into an example and create an app to display simple textfiles.

    Create a new main.js file and enter the following code in it −

    const {app, BrowserWindow} = require('electron') 
    const url = require('url') 
    const path = require('path') 
    const {ipcMain} = require('electron')  
    
    let win  
    
    function createWindow() { 
       win = new BrowserWindow({width: 800, height: 600}) 
       win.loadURL(url.format ({ 
    
      pathname: path.join(__dirname, 'index.html'), 
      protocol: 'file:', 
      slashes: true 
    })) } ipcMain.on('openFile', (event, path) => { const {dialog} = require('electron') const fs = require('fs') dialog.showOpenDialog(function (fileNames) {
      
      // fileNames is an array that contains all the selected 
      if(fileNames === undefined) { 
         console.log("No file selected"); 
      
      } else { 
         readFile(fileNames[0]); 
      } 
    }); function readFile(filepath) {
      fs.readFile(filepath, 'utf-8', (err, data) => { 
         
         if(err){ 
            alert("An error ocurred reading the file :" + err.message) 
            return 
         } 
         
         // handle the file content 
         event.sender.send('fileData', data) 
      }) 
    } }) app.on('ready', createWindow)

    This code will pop open the open dialog box whenever our main process recieves a ‘openFile’ message from a renderer process. This message will redirect the file content back to the renderer process. Now, we will have to print the content.

    Now, create a new index.html file with the following content −

    <!DOCTYPE html> 
    <html> 
       <head> 
    
      &lt;meta charset = "UTF-8"&gt; 
      &lt;title&gt;File read using system dialogs&lt;/title&gt; 
    </head> <body>
      &lt;script type = "text/javascript"&gt; 
         const {ipcRenderer} = require('electron') 
         ipcRenderer.send('openFile', () =&gt; { 
            console.log("Event sent."); 
         }) 
         
         ipcRenderer.on('fileData', (event, data) =&gt; { 
            document.write(data) 
         }) 
      &lt;/script&gt; 
    </body> </html>

    Now whenever we run our app, a native open dialog box will pop up as shown in the following screenshot −

    Open Dialog

    Once we select a file to display, its contents will be displayed on the app window −

    File Read Using Dialog

    This was just one of the four dialogs that Electron provides. They all have similar usage though. Once you learn how to do it using showOpenDialog, then you can use any of the other dialogs.

    The dialogs having the same functionality are −

    • showSaveDialog([browserWindow, ]options[, callback])
    • showMessageDialog([browserWindow, ]options[, callback])
    • showErrorDialog(title, content)
  • Inter Process Communication

    Electron provides us with 2 IPC (Inter Process Communication) modules called ipcMain and ipcRenderer.

    The ipcMain module is used to communicate asynchronously from the main process to renderer processes. When used in the main process, the module handles asynchronous and synchronous messages sent from a renderer process (web page). The messages sent from a renderer will be emitted to this module.

    The ipcRenderer module is used to communicate asynchronously from a renderer process to the main process. It provides a few methods so you can send synchronous and asynchronous messages from the renderer process (web page) to the main process. You can also receive replies from the main process.

    We will create a main process and a renderer process that will send each other messages using the above modules.

    Create a new file called main_process.js with the following contents −

    const {app, BrowserWindow} = require('electron')
    const url = require('url')
    const path = require('path')
    const {ipcMain} = require('electron')
    
    let win
    
    function createWindow() {
       win = new BrowserWindow({width: 800, height: 600})
       win.loadURL(url.format ({
    
      pathname: path.join(__dirname, 'index.html'),
      protocol: 'file:',
      slashes: true
    })) } // Event handler for asynchronous incoming messages ipcMain.on('asynchronous-message', (event, arg) => { console.log(arg) // Event emitter for sending asynchronous messages event.sender.send('asynchronous-reply', 'async pong') }) // Event handler for synchronous incoming messages ipcMain.on('synchronous-message', (event, arg) => { console.log(arg) // Synchronous event emmision event.returnValue = 'sync pong' }) app.on('ready', createWindow)

    Now create a new index.html file and add the following code in it.

    <!DOCTYPE html>
    <html>
       <head>
    
      &lt;meta charset = "UTF-8"&gt;
      &lt;title&gt;Hello World!&lt;/title&gt;
    </head> <body>
      &lt;script&gt;
         const {ipcRenderer} = require('electron')
         // Synchronous message emmiter and handler
         console.log(ipcRenderer.sendSync('synchronous-message', 'sync ping')) 
         // Async message handler
         ipcRenderer.on('asynchronous-reply', (event, arg) =&gt; {
            console.log(arg)
         })
         // Async message sender
         ipcRenderer.send('asynchronous-message', 'async ping')
      &lt;/script&gt;
    </body> </html>

    Run the app using the following command −

    $ electron ./main_process.js
    

    The above command will generate the following output −

    // On your app console
    Sync Pong
    Async Pong
    
    // On your terminal where you ran the app
    Sync Ping
    Async Ping
    

    It is recommended not to perform computation of heavy/ blocking tasks on the renderer process. Always use IPC to delegate these tasks to the main process. This helps in maintaining the pace of your application.

  • Native Node Libraries

    We used a node module, fs, in the previous chapter. We will now look at some other node modules that we can use with Electron.

    OS module

    Using the OS module, we can get a lot of information about the system our application is running on. Following are a few methods that help while the app is being created. These methods help us customize the apps according to the OS that they are running on.

    Sr.NoFunction & Description
    1os.userInfo([options])The os.userInfo() method returns information about the currently effective user. This information can be used to personalize the application for the user even without explicitly asking for information.
    2os.platform()The os.platform() method returns a string identifying the operating system platform. This can be used to customize the app according to the user OS.
    3os.homedir()The os.homedir() method returns the home directory of the current user as a string. Generally, configs of all users reside in the home directory of the user. So this can be used for the same purpose for our app.
    4os.arch()The os.arch() method returns a string identifying the operating system CPU architecture. This can be used when running on exotic architectures to adapt your application for that system.
    5os.EOLA string constant defining the operating system-specific end-ofline marker. This should be used whenever ending lines in files on the host OS.

    Using the same main.js file and the following HTML file, we can print these properties on the screen −

    <html>
       <head>
    
      &lt;title&gt;OS Module&lt;/title&gt;
    </head> <body>
      &lt;script&gt;
         let os = require('os')
         document.write('User Info: ' + JSON.stringify(os.userInfo()) + '&lt;br&gt;' + 
            'Platform: ' + os.platform() + '&lt;br&gt;' + 
            'User home directory: ' +  os.homedir() + '&lt;br&gt;' + 
            'OS Architecture: ' + os.arch() + '&lt;br&gt;')
      &lt;/script&gt;
    </body> </html>

    Now run the app using the following command −

    $ electron ./main.js
    

    The above command will generate the following output −

    User Info: {"uid":1000,"gid":1000,"username":"ayushgp","homedir":"/home/ayushgp",
       "shell":"/usr/bin/zsh"}
    Platform: linux
    User home directory: /home/ayushgp
    OS Architecture: x64
    

    Net Module

    The net module is used for network related work in the app. We can create both servers and socket connections using this module. Generally, the use of wrapper module from npm is recommended over the use of the net module for networking related tasks.

    The following tables lists down the most useful methods from the module −

    Sr.NoFunction & Description
    1net.createServer([options][, connectionListener])Creates a new TCP server. The connectionListener argument is automatically set as a listener for the ‘connection’ event.
    2net.createConnection(options[, connectionListener])A factory method, which returns a new ‘net.Socket’ and connects to the supplied address and port.
    3net.Server.listen(port[, host][, backlog][, callback])Begin accepting connections on the specified port and host. If the host is omitted, the server will accept connections directed to any IPv4 address.
    4net.Server.close([callback])Finally closed when all connections are ended and the server emits a ‘close’ event.
    5net.Socket.connect(port[, host][, connectListener])Opens the connection for a given socket. If port and host are given, then the socket will be opened as a TCP socket.

    The net module comes with a few other methods too. To get a more comprehensive list, see this.

    Now, let us create an electron app that uses the net module to create connections to the server. We will need to create a new file, server.js −

    var net = require('net');
    var server = net.createServer(function(connection) { 
       console.log('Client Connected');
       
       connection.on('end', function() {
    
      console.log('client disconnected');
    }); connection.write('Hello World!\r\n'); connection.pipe(connection); }); server.listen(8080, function() { console.log('Server running on http://localhost:8080'); });

    Using the same main.js file, replace the HTML file with the following −

    <html>
       <head>
    
      &lt;title&gt;net Module&lt;/title&gt;
    </head> <body>
      &lt;script&gt;
         var net = require('net');
         var client = net.connect({port: 8080}, function() {
            console.log('Connection established!');  
         });
         
         client.on('data', function(data) {
            document.write(data.toString());
            client.end();
         });
         
         client.on('end', function() { 
            console.log('Disconnected :(');
         });
      &lt;/script&gt;
    </body> </html>

    Run the server using the following command −

    $ node server.js
    

    Run the application using the following command −

    $ electron ./main.js
    

    The above command will generate the following output −

    Net Module

    Observe that we connect to the server automatically and automatically get disconnected too.

    We also have a few other node modules that we can be used directly on the front-end using Electron. The usage of these modules depends on the scenario you use them in.

  •  File Handling

    File handling is a very important part of building a desktop application. Almost all desktop apps interact with files.

    We will create a form in our app that will take as input, a Name and an Email address. This form will be saved to a file and a list will be created that will show this as output.

    Set up your main process using the following code in the main.js file −

    const {app, BrowserWindow} = require('electron')
    const url = require('url')
    const path = require('path')
    
    let win
    
    function createWindow() {
       win = new BrowserWindow({width: 800, height: 600})
       win.loadURL(url.format ({
    
      pathname: path.join(__dirname, 'index.html'),
      protocol: 'file:',
      slashes: true
    })) } app.on('ready', createWindow)

    Now open the index.html file and enter the following code in it −

    <!DOCTYPE html>
    <html>
       <head>
    
      &lt;meta charset = "UTF-8"&gt;
      &lt;title&gt;File System&lt;/title&gt;
      &lt;link rel = "stylesheet" 
         href = "./bower_components/bootstrap/dist/css/bootstrap.min.css" /&gt;
      
      &lt;style type = "text/css"&gt;
         #contact-list {
            height: 150px;
            overflow-y: auto;
         }
      &lt;/style&gt;
    </head> <body>
      &lt;div class = "container"&gt;
         &lt;h1&gt;Enter Names and Email addresses of your contacts&lt;/h1&gt;
         &lt;div class = "form-group"&gt;
            &lt;label for = "Name"&gt;Name&lt;/label&gt;
            &lt;input type = "text" name = "Name" value = "" id = "Name" 
               placeholder = "Name" class = "form-control" required&gt;
         &lt;/div&gt;
         
         &lt;div class = "form-group"&gt;
            &lt;label for = "Email"&gt;Email&lt;/label&gt;
            &lt;input type = "email" name = "Email" value = "" id = "Email" 
               placeholder = "Email" class = "form-control" required&gt;
         &lt;/div&gt;
         
         &lt;div class = "form-group"&gt;
            &lt;button class = "btn btn-primary" id = "add-to-list"&gt;Add to list!&lt;/button&gt;
         &lt;/div&gt;
         
         &lt;div id = "contact-list"&gt;
            &lt;table class = "table-striped" id = "contact-table"&gt;
               &lt;tr&gt;
                  &lt;th class = "col-xs-2"&gt;S. No.&lt;/th&gt;
                  &lt;th class = "col-xs-4"&gt;Name&lt;/th&gt;
                  &lt;th class = "col-xs-6"&gt;Email&lt;/th&gt;
               &lt;/tr&gt;
            &lt;/table&gt;
         &lt;/div&gt;
         
         &lt;script src = "./view.js" &gt;&lt;/script&gt;
      &lt;/div&gt;
    </body> </html>

    Now we need to handle the addition event. We will do this in our view.js file.

    We will create a function loadAndDisplayContacts() that will initially load contacts from the file. After creating the loadAndDisplayContacts() function, we will create a click handler on our add to list button. This will add the entry to both the file and the table.

    In your view.js file, enter the following code −

    let $ = require('jquery')
    let fs = require('fs')
    let filename = 'contacts'
    let sno = 0
    
    $('#add-to-list').on('click', () => {
       let name = $('#Name').val()
       let email = $('#Email').val()
    
       fs.appendFile('contacts', name + ',' + email + '\n')
    
       addEntry(name, email)
    })
    
    function addEntry(name, email) {
       if(name && email) {
    
      sno++
      let updateString = '&lt;tr&gt;&lt;td&gt;'+ sno + '&lt;/td&gt;&lt;td&gt;'+ name +'&lt;/td&gt;&lt;td&gt;' 
         + email +'&lt;/td&gt;&lt;/tr&gt;'
      $('#contact-table').append(updateString)
    } } function loadAndDisplayContacts() { //Check if file exists if(fs.existsSync(filename)) {
      let data = fs.readFileSync(filename, 'utf8').split('\n')
      
      data.forEach((contact, index) =&gt; {
         let &#91; name, email ] = contact.split(',')
         addEntry(name, email)
      })
    } else {
      console.log("File Doesn\'t Exist. Creating new file.")
      fs.writeFile(filename, '', (err) =&gt; {
         if(err)
            console.log(err)
      })
    } } loadAndDisplayContacts()

    Now run the application, using the following command −

    $ electron ./main.js
    

    Once you add some contacts to it, the application will look like −

    File

    For more fs module API calls, please refer to Node File System tutorial.

    Now we can handle files using Electron. We will look at how to call the save and open dialog boxes(native) for files in the dialogs chapter.

  •  Building UIs

    The User Interface of Electron apps is built using HTML, CSS and JS. So we can leverage all the available tools for front-end web development here as well. You can use the tools such as Angular, Backbone, React, Bootstrap, and Foundation, to build the apps.

    You can use Bower to manage these front-end dependencies. Install bower using −

    $ npm install -g bower
    

    Now you can get all the available JS and CSS frameworks, libraries, plugins, etc. using bower. For example, to get the latest stable version of bootstrap, enter the following command −

    $ bower install bootstrap
    

    This will download bootstrap in bower_components. Now you can reference this library in your HTML. Let us create a simple page using these libraries.

    Let us now install jquery using the npm command −

    $ npm install --save jquery
    

    Further, this will be required in our view.js file. We already have a main.js setup as follows −

    const {app, BrowserWindow} = require('electron')
    const url = require('url')
    const path = require('path')
    
    let win
    
    function createWindow() {
       win = new BrowserWindow({width: 800, height: 600})
       win.loadURL(url.format ({
    
      pathname: path.join(__dirname, 'index.html'),
      protocol: 'file:',
      slashes: true
    })) } app.on('ready', createWindow)

    Open your index.html file and enter the following code in it −

    <!DOCTYPE html>
    <html>
       <head>
    
      &lt;meta charset = "UTF-8"&gt;
      &lt;title&gt;Hello World!&lt;/title&gt;
      &lt;link rel = "stylesheet" 
         href = "./bower_components/bootstrap/dist/css/bootstrap.min.css" /&gt;
    </head> <body>
      &lt;div class = "container"&gt;
         &lt;h1&gt;This page is using Bootstrap and jQuery!&lt;/h1&gt;
         &lt;h3 id = "click-counter"&gt;&lt;/h3&gt;
         &lt;button class = "btn btn-success" id = "countbtn"&gt;Click here&lt;/button&gt;
         &lt;script src = "./view.js" &gt;&lt;/script&gt;
      &lt;/div&gt;
    </body> </html>

    Create view.js and enter the click counter logic in it −

    let $ = require('jquery')  // jQuery now loaded and assigned to $
    let count = 0
    $('#click-counter').text(count.toString())
    $('#countbtn').on('click', () => {
       count ++ 
       $('#click-counter').text(count)
    }) 

    Run the app using the following command −

    $ electron ./main.js
    

    The above command will generate the output as in the following screenshot −

    UI

    You can build your native app just like you build websites. If you do not want users to be restricted to an exact window size, you can leverage the responsive design and allow users to use your app in a flexible manner

  • How Electron Works

    Electron takes a main file defined in your package.json file and executes it. This main file creates application windows which contain rendered web pages and interaction with the native GUI (graphical user interface) of your Operating System.

    As you start an application using Electron, a main process is created. This main process is responsible for interacting with the native GUI of the Operating System. It creates the GUI of your application.

    Just starting the main process does not give the users of your application any application window. These are created by the main process in the main file by using the BrowserWindow module. Each browser window then runs its own renderer process. The renderer process takes an HTML file which references the usual CSS files, JavaScript files, images, etc. and renders it in the window.

    The main process can access the native GUI through modules available directly in Electron. The desktop application can access all Node modules like the file system module for handling files, request to make HTTP calls, etc.

    Difference between Main and Renderer processes

    The main process creates web pages by creating the BrowserWindow instances. Each BrowserWindow instance runs the web page in its own renderer process. When a BrowserWindow instance is destroyed, the corresponding renderer process is also terminated.

    The main process manages all web pages and their corresponding renderer processes. Each renderer process is isolated and only cares about the web page running in it.

  •  Installation

    To get started with developing using the Electron, you need to have Node and npm(node package manager) installed. If you do not already have these, head over to Node setup to install node on your local system. Confirm that node and npm are installed by running the following commands in your terminal.

    node --version
    npm --version
    

    The above command will generate the following output −

    v6.9.1
    3.10.8
    

    Whenever we create a project using npm, we need to provide a package.json file, which has all the details about our project. npm makes it easy for us to set up this file. Let us set up our development project.

    • Fire up your terminal/cmd, create a new folder named hello-world and open that folder using the cd command.
    • Now to create the package.json file using npm, use the following command.
    npm init
    
    • It will ask you for the following information −
    Package.json creation

    Just keep pressing Enter, and enter your name at the “author name” field.

    Create a new folder and open it using the cd command. Now run the following command to install Electron globally.

    $ npm install -g electron-prebuilt
    

    Once it executes, you can check if Electron is installed the right way by running the following command −

    $ electron --version
    

    You should get the output −

    v1.4.13
    

    Now that we have set up Electron, let us move on to creating our first app using it.

  •  Overview

    Why Electron?

    Electron enables you to create desktop applications with pure JavaScript by providing a runtime with rich native (operating system) APIs.

    This does not mean Electron is a JavaScript binding to graphical user interface (GUI) libraries. Instead, Electron uses web pages as its GUI, so you can also see it as a minimal Chromium browser, controlled by JavaScript. So all the electron apps are technically web pages running in a browser that can leverage your OS APIs.

    Who Uses Electron?

    Github developed Electron for creating the text editor Atom. They were both open sourced in 2014. Electron is used by many companies like Microsoft, Github, Slack, etc.

    Electron has been used to create a number of apps. Following are a few notable apps −

    • Slack desktop
    • WordPress desktop app
    • Visual Studio Code
    • Caret Markdown Editor
    • Nylas Email App
    • GitKraken git client