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

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?

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;
});

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!

i wouldnt necessarily recommend this way since it may interfere with other functioninality but its a way more concise approach
const str = `
<form action="form://submit" >
<input name="test" >
<button type="submit"> OK </button>
</form>
`
promptWindow.loadURL(`data:text/html;base64,${Buffer.from(str).toString("base64")}`)
promptWindow.webContents.session.protocol.registerStringProtocol("form", e => {
const request = new URL(e.url)
const data = request.searchParams // {test:"inputvalue"}
debugger;
})

Remote is great way to share data. Using global variables and share them with other pages of our electron application. So, based on the following IPC approach, I was able to manage it this way :
1) Add this code in the main.js file :
global.MyGlobalObject = {
variable_1: '12345'
}
2) Use this on your 1st page to update global variable value :
require('electron').remote.getGlobal('MyGlobalObject').variable_1= '4567'
3) Lastly, use something like this on your 2nd page where you'll access the modified global variable and print it :
console.log(require('electron').remote.getGlobal('MyGlobalObject').variable_1)
You can find the same thing in electron's documentation.

Related

How do I use the filedialog in electron?

I am new to electron and I am trying to open a filedialog that lets the user select a specific file using this code:
const {remote} = require("remote");
const {dialog} = require('electron').remote;
function openFileDialog() {
const savePath = dialog.showSaveDialog();
console.log(savePath)
}
However when I try this I get an error in the console saying:
Uncaught TypeError: Cannot read property 'showSaveDialog' of
undefined.
Does anyone know what I am doing wrong?
EDIT:
I am using this piece of code now as it was proposed below:
var remote = require("remote");
var dialog = require('dialog').remote;
function openFileDialog() {
const savePath = dialog.showSaveDialog(null);
console.log(savePath)
}
inside a file named settings.js which I invoke using this code:
<input class="btn btn-dark" type="button" value="Input" onclick="openFileDialog();">
And I import the script using this code:
<script src="./../javascript/settings.js"></script>
I have tried both with and without remote. I still get the same error
This should work if you're using it in the main process, if you want to use it from a renderer process:
const { dialog } = require('electron').remote;
Also, it's better to define the event-handler in the script. Here is a functional example:
<input class="btn btn-dark" type="button" value="Input" id="dialogBtn">
const { dialog } = require('electron').remote;
document.getElementById("dialogBtn").addEventListener("click", openFileDialog);
async function openFileDialog() {
try {
const savePath = await dialog.showSaveDialog(null);
console.log('savePath: ', savePath);
} catch (e) {
console.log('Error:', e);
}
}
I just solved it using these pieces of code. The first problem was that I needed to enable remoteModule enableRemoteModule: true, then that I needed to wait for the DOM to load using this code:
window.onload=function(){
document.getElementById("dialogBtn").addEventListener("click", openFileDialog);
}
Thank you Majed Badawi for the help!

Haskell Webviewhs getElementById from a webpage

Still most potential for a GUI to Haskell for me, but missing some essential info in the examples, being a noob Haskeller. Assuming one of the examples:
{-
webviewhs
(C) 2018 David Lettier
lettier.com
-}
{-# LANGUAGE
OverloadedStrings
#-}
import qualified Graphics.UI.Webviewhs as WHS
main :: IO ()
main =
WHS.createWindowAndBlock
WHS.WindowParams
{ WHS.windowParamsTitle = "webviewhs - How do I create a window and have it run itself?"
-- This could be a localhost URL to your single-page application (SPA).
, WHS.windowParamsUri = "https://lettier.github.com"
, WHS.windowParamsWidth = 800
, WHS.windowParamsHeight = 600
, WHS.windowParamsResizable = True
, WHS.windowParamsDebuggable = True
}
This creates a window in which I can load a custom webpage. Assuming this webpage has a <input type="text" id="mytext"> and there's a button next to it. Not that it matters but <button type="submit" id="sendtohaskell">. How would I go about getting the info in the textfield to Haskell by pressing the button? There's no example like that in the tutorial. For me it is the missing link to get info from a webapp, processing it in Haskell and returning it to eg. SQLite.
As the github page shows, you can receive data from JS with a callback, and execute arbitrary JS in the window from Haskell. This is enough to do any sort of communication you might want, here's an example that that executes some Haskell on a button press and then shows the result in the webpage:
{-# LANGUAGE OverloadedStrings, QuasiQuotes #-}
module Main where
import System.Directory(getCurrentDirectory)
import Text.Printf
import Control.Monad(void)
import Language.Javascript.JMacro
import qualified Graphics.UI.Webviewhs as WHS
import qualified Data.Text as T
windowCallback window = do
return True
handleJSRequest window request = void . WHS.runJavaScript window $ [jmacro|
show_response `(printf "'Got response: %s'" request :: String)`
|]
main :: IO ()
main = void $ do
dir <- getCurrentDirectory
WHS.withWindowLoop
WHS.WindowParams
{ WHS.windowParamsTitle = "Test"
, WHS.windowParamsUri = T.pack $ printf "file://%s/example.html" dir
, WHS.windowParamsWidth = 800
, WHS.windowParamsHeight = 600
, WHS.windowParamsResizable = True
, WHS.windowParamsDebuggable = True
}
handleJSRequest
windowCallback
<html>
<head>
<title>Example</title>
<meta charset="utf-8">
</head>
<body>
<script type="text/javascript">
function show_response(response) {
document.getElementById('response').innerHTML = response;
}
function submit() {
var value = document.getElementById('textbox').value;
window.external.invoke(value)
}
</script>
<input type="text" id="textbox"/>
<input value="say hello" type="button" onclick="submit()"/>
<p id="response"></p>
</body>
</html>
You should note though that the haskell webview library has only 2 commits, the last of which was more than 7 months ago, so it's not exactly being actively developed at the moment.

nw.js: How do I build a file dialog to open a file?

My app is built with a placeholder where a file is read directly when "load" is clicked.
<button id="loadbutton" type="button" class="btn btn-success" onclick="showTheFile()">Load</button></a>
showthefile() does some stuff and then makes a call to...
var keyMapLoc = '\\path\\to\\file.txt';
function readKeys(ffile) {
// read the keyfile
var ffile = ffile || keyMapLoc;
return fs.readFileSync(ffile, 'utf8');
}
This reads the file into the app where it is parsed, yotta yotta.
I followed these instructions and used the demo. The file dialog pops as soon as the app is opened, which I get.
<html>
<body>
<input style="display:none;" id="fileDialog" type="file" />
<script>
function chooseFile(name) {
var chooser = document.querySelector(name);
chooser.addEventListener("change", function(evt) {
console.log(this.value);
}, false);
chooser.click();
}
chooseFile('#fileDialog');
</script>
</body>
</html>
However, even though I understand how to make a file dialog pop and I understand how to read/parse a file, I'm having a hard time working this very abstract example into my existing nwjs app.
Based on the above sample of my app, how should I blend in the demo so that the "load" button operates as expected for loading a file?
Since you didn't provide your code, I'll go off the demo. What you need to do
is trigger the click event of the file input element and then upon a
change event, call readKeys().
<!DOCTYPE html>
<html>
<body>
<input style="display:none;" id="fileDialog" type="file"/>
<button id="loadButton" type="button" class="btn btn-success"
onclick="showTheFile()">Load</button>
<script>
var fs = require('fs');
var keyMapLoc = '\\path\\to\\file.txt';
var chooser = document.querySelector("#fileDialog");
// Set up the file chooser for the on change event
chooser.addEventListener("change", function(evt) {
// When we reach this point, it means the user has selected a file,
// so invoke readKeys().
// this.value contains the path to the selected file
console.log(readKeys(this.value));
}, false);
function showTheFile() {
// Trigger click event on the chooser, this will bring up the
// dialog
chooser.click()
}
function readKeys(ffile) {
var ffile = ffile || keyMapLoc;
return fs.readFileSync(ffile, 'utf8');
}
</script>
</body>
</html>

PHP "PUSH" To Javascript

Right now we have a web application that is ran on a local network where the clients run everything in javascript. In order to make sure everything is in sync these clients currently utilize an AJAX request to the server by sending the last "syncId" that it has recieved. The server then responds with an array tree of commands to get this client up to date. This is executed every second and has yet to cause any issues with network bandwith or latency however we are installing a system in a bigger client next month that may push the limits of this method and I am wondering if it is feasible to have the server "PUSH" the sync events to the clients in real time.
srvResponse=httpGet("CDSSync.php?sessionKey="+sessionKey+"&lastUpdate="+lastUpdate);
if(srvResponse!=0){
syncEvents=srvResponse.split(";");
for(var i=0; i<syncEvents.length; i++){
syncItem=syncEvents[i].split(",");
window["syncFunction_"+syncItem[1]](syncItem[2]);
lastUpdate=syncItem[0];
}
}
The above is where my system checks for events to be synced where syncItem[0] is a autoIncrement ID, syncItem[1] is a code for the event being handled and syncItem[2] is a parameter for the function. the httpGet function being called although not in this code is just a function that fetches from the server and returns the response.
Take a look at www.firebase.com, you can set up a push service from javascript clients to all others in minutes. Try the simple tutorial first:
<!doctype html>
<html>
<head>
<script src='https://cdn.firebase.com/js/client/2.2.1/firebase.js'></script>
<script src='https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js'></script>
<link rel='stylesheet' type='text/css' href='/resources/tutorial/css/example.css'>
</head>
<body>
<div id='messagesDiv'></div>
<input type='text' id='nameInput' placeholder='Name'>
<input type='text' id='messageInput' placeholder='Message'>
<script>
var myDataRef = new Firebase('https://hbw30ob2a8y.firebaseio-demo.com/');
$('#messageInput').keypress(function (e) {
if (e.keyCode == 13) {
var name = $('#nameInput').val();
var text = $('#messageInput').val();
myDataRef.push({name: name, text: text});
$('#messageInput').val('');
}
});
myDataRef.on('child_added', function(snapshot) {
var message = snapshot.val();
displayChatMessage(message.name, message.text);
});
function displayChatMessage(name, text) {
$('<div/>').text(text).prepend($('<em/>').text(name+': ')).appendTo($('#messagesDiv'));
$('#messagesDiv')[0].scrollTop = $('#messagesDiv')[0].scrollHeight;
};
</script>
</body>
</html>

post form values to html

I want to POST form values and display them on another html page using Javascript. No server-side technology should be used. I have a function that posts the values but to read the values to another html page, I think I am missing something. Below is the code.
Any help? Thanks in advance.
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title>Untitled Page</title>
<script type="text/javascript">
function post_to_page(path, params, method) {
method = method || "post"; // Set method to post by default, if not specified.
var form = document.createElement("form");
form.setAttribute("method", method);
form.setAttribute("action", path);
form.setAttribute("target", "formresult");
for (var key in params) {
if (params.hasOwnProperty(key)) {
var hiddenField = document.createElement("input");
hiddenField.setAttribute("type", "hidden");
hiddenField.setAttribute("name", key);
hiddenField.setAttribute("value", params[key]);
form.appendChild(hiddenField);
}
}
document.body.appendChild(form);
// creating the 'formresult' window with custom features prior to submitting the form
window.open('target.htm', 'formresult', 'scrollbars=no,menubar=no,height=600,width=800,resizable=yes,toolbar=no,status=no');
form.submit();
}
</script>
</head>
<body>
<form id="form1" action="target.htm" method="post">
<div>
USB No: <input name="usbnum" id="usbnum" type="text"/><br />
USB Code: <input name="usbcode" id="usbcode" type="text"/>
</div>
<button onclick="post_to_page()">Try it</button>
</div>
</form>
</body>
</html>
Here is a simple example of moving data from one Window to another
<!-- HTML -->
<textarea id="foo"></textarea><br/>
<input id="bar" value="click" type="button"/>
and the real code to make it work, which assumes you pass the same origin policy
// JavaScript
var whatever = 'yay I can share information';
// in following functions `wnd` is the reference to target window
function generateWhatever(wnd, whatever) { // create the function actually doing the work
return function () {wnd.document.getElementById('foo').innerHTML = whatever};
} // why am I using a generator? You don't have to, it's a choice
function callWhenReady(wnd, fn) { // make sure you only invoke when things exist
if (wnd.loaded) fn(); // already loaded flag (see penultimate line)
else wnd.addEventListener('load', fn); // else wait for load
}
function makeButtonDoStuff() { // seperated button JS from HTML
document
.getElementById('bar')
.addEventListener('click', function () {
var wnd = window.open(window.location); // open new window, keep reference
callWhenReady(wnd, generateWhatever(wnd, whatever)); // set up function to be called
});
}
window.addEventListener('load', function () {window.loaded = true;}); // set loaded flag (do this on your target, this example uses same page)
window.addEventListener('load', makeButtonDoStuff); // link button's JavaScript to HTML when button exists
You can't get POST values using JavaScript. You can use GET method to pass values.
If you are using html5 you can use localStorage. Otherwise a query string or cookies are your other options.
You said you didn't want the server involved...why are you calling submit?
[Edit]
#Paul S's comment/answer looks very helpful. But you might look at something like the jQuery PostMessage plugin if you need it to be cross browser compatible.
http://benalman.com/projects/jquery-postmessage-plugin/
You don't require a POST request to send data from one page to another. Simply use LocalStorage to do the trick. Just call a Javascript function on form submission. This may help:
HTML:
<form id="form1" action="target.htm" method="post">
<div>
USB No: <input name="usbnum" id="usbnum" type="text"/><br />
USB Code: <input name="usbcode" id="usbcode" type="text"/>
</div>
<button onclick="post_to_page()">Try it</button>
</form>
Javascript:
function post_to_page() {
localStorage.value = "Your content here";
window.location = "nextpage.html";
}
This will save the data locally and go to the next page. In the next page, simply call this function to retrieve the stored data:
function get_stored_data() {
alert(localStorage.value);
}
You can simply assign it to a div, textbox other Javascript variable.

Categories

Resources