Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the proper way to handle forms in Electron?

The form html and submit event is part of the "renderer". The submitted data should be available in the main process. What's the proper way to submit the form and make that data accessible in main.js ?

Should I simply use the "remote" module to pass the data to a function from main.js or is there a better approach?

like image 468
Elfy Avatar asked Apr 09 '17 20:04

Elfy


2 Answers

We use a service (Angular) to process form data in a window. Then notify the remote, if needed.


From your renderer you can send data to the ipc, then in your main.js you catch this event and the passed form data:

// renderer.js
let ipcRenderer = require('electron').ipcRenderer;
ipcRenderer.send('submitForm', formData);

// main.js
ipcMain.on('submitForm', function(event, data) {
   // Access form data here
});

You can also send messages back to the renderer from the main.js.

Either sync:

// main.js
ipcMain.on('submitForm', function(event, data) {
   // Access form data here
   event.returnValue = {"any": "value"};
});

Or async:

// main.js
ipcMain.on('submitForm', function(event, data) {
   // Access form data here
   event.sender.send('formSubmissionResults', results);
});

// renderer.js
ipcRenderer.on('formSubmissionResults', function(event, args) {
   let results = args.body;
});
like image 118
Adam Eri Avatar answered Oct 10 '22 12:10

Adam Eri


There are several variations on how to do this, but all are via IPC. 

IPC (inter process communication) is the only way to get data from the render process to the main process, and is event driven. The way this works is that you can use custom defined events which the process listens for and returns something when that event happens.

The example stated by @Adam Eri is a variation on the ipcMain example found in the documentation, but this method is not one size fits all.

The reason for saying that is the matter can quickly become complicated if you are trying to send events via the menu (which typically runs on the main process), or via components through a front end framework like Vue or Angular.

I will give a few examples:

Using Remote with WebContents

To your point, yes you can use electron remote, but for the purposes of forms it is not the recommended approach. Based on the documentation, the point of remote is to 

Use main process modules from the renderer process

tl:dr -This process can cause deadlocks due to its synchronous nature, can cause event object leaks (due to garbage collection), and leads to unexpected results with callbacks.

Further explanation can be had from the documentation but ultimately this is set for using items like dialog and menu in the render process.

index.js (main process)

const { app, BrowserWindow, ipcMain } = require('electron');
const path = require ('path');
const fs = require('fs');
const os = require('os');

let window;

function createWindow(){
    window = new BrowserWindow({
        show: false
    });

    window.loadURL(`file://${__dirname}/index.html`);
    window.once('ready-to-show', function (){
        window.show();
    });

    window.webContents.openDevTools();

    let contents = window.webContents;

    window.on('closed', function() {
        window = null;
    });
}

exports.handleForm = function handleForm(targetWindow, firstname) {
    console.log("this is the firstname from the form ->", firstname)
    targetWindow.webContents.send('form-received', "we got it");
};

app.on('ready', function(){
    createWindow();
});

index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Electron App</title>
    </head>

    <body>

        <form action="#" id="ipcForm2">
            First name:<br>
            <input type="text" name="firstname" id="firstname" value="John">
            <br>
            Last name:<br>
            <input type="text" name="lastname" id="lastname" value="Smith">
            <br><br>
            <input id="submit" type="submit" value="submit">
        </form>

        <p id="response"></p>

        <script src='renderFile.js'></script>
    </body>
</html>

renderFile.js (Render Process)

const { remote, ipcRenderer } = require('electron');
const { handleForm} = remote.require('./index');
const currentWindow = remote.getCurrentWindow();

const submitFormButton = document.querySelector("#ipcForm2");
const responseParagraph = document.getElementById('response')

submitFormButton.addEventListener("submit", function(event){
        event.preventDefault();   // stop the form from submitting
        let firstname = document.getElementById("firstname").value;
        handleForm(currentWindow, firstname)
});

ipcRenderer.on('form-received', function(event, args){
    responseParagraph.innerHTML = args
    /*
        you could choose to submit the form here after the main process completes
        and use this as a processing step
    */
});

Traditional IPC

index.js (Main Process)

const { app, BrowserWindow, ipcMain } = require('electron');
const path = require ('path');
const fs = require('fs');
const os = require('os');

let window;

function createWindow(){
    window = new BrowserWindow({
        show: false
    });

    window.loadURL(`file://${__dirname}/index.html`);
    window.once('ready-to-show', function (){
        window.show();
    });

    window.webContents.openDevTools();

    let contents = window.webContents;

    window.on('closed', function() {
        window = null;
    });
}

ipcMain.on('form-submission', function (event, firstname) {
    console.log("this is the firstname from the form ->", firstname)
});

app.on('ready', function(){
    createWindow();
});

index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Electron App</title>
    </head>

    <body>

        <form name="ipcForm" onSubmit="JavaScript:sendForm(event)">
            First name:<br>
            <input type="text" name="firstname" id="firstname" value="John">
            <br>
            Last name:<br>
            <input type="text" name="lastname" id="lastname" value="Smith">
            <br><br>
            <input type="submit" value="Submit">
        </form>

        <script src='renderFile.js'></script>
    </body>
</html>

renderFile.js (Render Process)

const ipcRenderer = require('electron').ipcRenderer;

function sendForm(event) {
    event.preventDefault() // stop the form from submitting
    let firstname = document.getElementById("firstname").value;
    ipcRenderer.send('form-submission', firstname)
}

Using WebContents

A possible third option is webContents.executeJavascript to access the renderer process from the main process. This explanation from the remote documentation section.

Summary

As you can see, there are a few options on how to handle forms with Electron. So long as you use IPC, you should be fine; its just how you use it that can get you into trouble. I have shown plain javascript options for handling forms, but there are countless ways to do so. When you bring a front end framework into the mix, it gets even more interesting.

I personally use the traditional IPC approach when I can.

Hope that clears things up for you!

like image 40
unseen_damage Avatar answered Oct 10 '22 14:10

unseen_damage