How do I use the filedialog in electron? - javascript

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!

Related

XMLHttpRequest and Fetch not functioning

So I am working on my first project that uses JavaScript with HTML, its a basic weather webpage. The user inputs their location and it displays the current weather. Got the HTML working smoothly but the JavaScript keeps stuffing up. The weird part is that there are no errors in the console so it should be working just fine.
Here is the part of the HTML:
<section id="weather">
<div id="nav">
<div id="locate">
<div id="container">
<form id="location-form">
<label for="location">Location (eg. London,uk):</label>
<input type="text" id="location" name="location" required>
<button type="submit" form="location-form" formnovalidate>Let's Go!</button>
</form>
</div>
</div>
<div id="weather-output">
<!-- Output of API goes here -->
</div>
</div>
</section>
Here is my JavaScript. I have embedded it using script tags because it wasn't detecting button presses.
<script type="text/JavaScript">
function fetchAPIData() {
const locationInput = document.getElementById("location");
const locationValue = locationInput.value;
const url = `http://api.openweathermap.org/data/2.5/weather?q=${locationValue}&APPID=API_URL`;
// do the URL Request
async function xmlrequest() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
// Typical action to be performed when the document is ready:
document.getElementById("weather-output").innerHTML = xhttp.responseText;
}
};
xhttp.open("GET", url, true);
xhttp.send();
}
xmlrequest();
}
//listens for button on html side and runs fetchAPIData()
document.addEventListener("DOMContentLoaded", () => {
document.querySelector("button[type=submit]").addEventListener("click", fetchAPIData);
}
);
</script>
I have tried it with both XMLHttpRequest (shown above) and Fetch. Neither work, they do not function nor displayed errors. I am using OpenWeatherMap I have an API key and it works as a plain url but when it the script it doesn't. Any help would be amazing!
mrt
Edit: Firebase analytics is working, its status code is '200'.
There are not any status codes nor error messages for the API though. Updated the code now there is an error '400' so it still doesn't work but I can troubleshoot it now. Thanks everyone for you help!
Solution 1
The problem is that you wrote type of button as "submit":
<button type="submit" form="location-form" formnovalidate>Let's Go!</button>
Try type="button":
<button type="button" form="location-form" formnovalidate>Let's Go!</button>
and amend js:
document.querySelector("button[type=button]").addEventListener("click", fetchAPIData);
Solution 2
use e.preventDefault() function:
function fetchAPIData(e) {
e.preventDefault();
const locationInput = document.getElementById("location");
const locationValue = locationInput.value;
.....
In addition to what James wrote fetch with async/await should still be the way forward here.
A basic (contrived) example:
// Make this function async
async function fetchAPIData() {
// Get the value from your input
const { value } = locationInput;
const endpoint = `http://api.openweathermap.org/data/2.5/weather?q=${value}&APPID=API_URL`;
// Await the response
const response = await fetch(endpoint);
// If it's ok parse the data and update the DOM
// otherwise throw an error
if (response.ok) {
const data = await response.json();
output.innerHTML = data;
} else {
throw new Error('Bad response');
}
}

Uncaught SyntaxError: Identifier '$......' has already been declared

so i have a code like this , i used it for a component
<p><input id="note" ></p>
<div><span class="dialog"></span></div>
<script>
let $mathElement = $('.dialog');
let dialSpan = $mathElement[0];
let noteSpan = document.getElementById('note');
try {
// a function to render
render(noteSpan.value, dialSpan);
}
catch(e) {
}
</script>
and there's also a function to allow me to hide and show this component.
but the problem is after i hide and reopen it there is a eror that said "$element" has already been declared ,
can you guys tell me how to avoid this error
the error :
You can't redeclare/override a let variable.
You may want to use some var here.
var $mathElement = $('.dialog');
var dialSpan = $mathElement[0];
let noteSpan = document.getElementById('note');

Why am I getting 'Uncaught TypeError: window[i] is not a function' when submitting authorize.net's AcceptUI form?

I'm trying to use the authorize.net AcceptUI hosted form in a Vue.js component. https://developer.authorize.net/api/reference/features/acceptjs.html#Integrating_the_Hosted_Payment_Information_Form
The button to launch the form and the form show up correctly. After entering some test payment information and hitting submit, the form kind or reloads but doesn't disappear as it should. In the console I get the error AcceptUI.js:1 Uncaught TypeError: window[i] is not a function.
The relevant section of the AcceptUI script is this:
A = function(t) {
"function" == typeof i ? i.call(null, t) : window[i](t)
};
I have a responseHandler function defined. I'm not sure why it's not working. I stripped the code down to be almost identical to the sample that authorize.net provides but I'm assuming something with Vue.js or Typescript is interfering.
Please ignore any unrelated issues with the code. I'm only concerned about getting the responseHandler to work then I'll build out the rest of the functionality.
<template>
<div>
<form id="paymentForm" method="POST">
<button type="button"
class="AcceptUI"
data-billingAddressOptions='{"show":true, "required":false}'
data-apiLoginID="<redacted>"
data-clientKey="<redacted>"
data-acceptUIFormBtnTxt="Submit"
data-acceptUIFormHeaderTxt="Card Information"
data-responseHandler="responseHandler">Pay
</button>
</form>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import Component from 'vue-class-component';
#Component
export default class SubscriptionManager extends Vue {
protected responseHandler(response: any) {
console.log(response);
}
protected paymentFormUpdate(opaqueData: any) {
const dataDescriptor: any = document.getElementById("dataDescriptor");
dataDescriptor.value = opaqueData.dataDescriptor;
const dataValue: any = document.getElementById("dataValue")
dataValue.value = opaqueData.dataValue;
// If using your own form to collect the sensitive data from the customer,
// blank out the fields before submitting them to your server.
const cardNumber: any = document.getElementById("cardNumber");
cardNumber.value = "";
const expMonth: any = document.getElementById("expMonth")
expMonth.value = "";
const expYear: any = document.getElementById("expYear")
expYear.value = "";
const cardCode: any = document.getElementById("cardCode")
cardCode.value = "";
const paymentForm: any = document.getElementById("paymentForm")
paymentForm.submit();
}
}
</script>
I just ran into this same problem today. My problem was that the function named in data-responseHandler couldn't be found when it was time to execute the response callback. I had defined data-responseHandler="authnetResponseHandler" but forgot to define function authnetResponseHandler(response) before testing it.
https://developer.authorize.net/api/reference/features/acceptjs.html#Handling_the_Response

How to load data from external JSON API onclick (Cant use fetch)

I am trying to create an IE11 compatible webpage which will sit on a few users desktops, which will grab some data from a JSON API and display it.
The user will type in their individual API key before pressing a button, revealing the API data.
Could you please help where my code has gone wrong? The error message I get from the console is: "Unable to get property 'addEventListener' of undefined or null reference. " So it looks like it is not even making the call to the API.
<script>
var btn = document.getElementById("btn");
var apikey = document.getElementById("apikey").value
btn.addEventListener("click", function() {
var ourRequest = new XMLHttpRequest();
ourRequest.open('GET', 'http://example.example?&apikey=' + document.getElementById("apikey").value);
ourRequest.onload = function() {
if (ourRequest.status >= 200 && ourRequest.status < 400) {
var ourData = JSON.parse(ourRequest.responseText);
document.getElementById("title").textContent = ourData.data[0]["name"];
}}}
);
</script>
.
<body>
Enter API key: <input type="text" id="apikey">
<button id="btn">Click me</button>
<p id="title"></p>
</body>
The API data which I am trying to just extract the name from, looks something like this:
{"data":[{"name":"This is the first name"},{"name":"This is the second name"}]}
It's likely that you're including the Javascript in the page before the HTML. As Javascript is executed as soon as the browser reaches it, it will be looking for the #btn element which will not have been rendered yet. There are two ways to fix this:
Move the Javascript to the bottom of the <body> tag, making it run after the HTML has been output to the page.
Wrap the Javascript in a DOMContentLoaded event, which will defer the script until the page has finished loading. An example is as follows:
window.addEventListener('DOMContentLoaded', function() {
var btn = document.getElementById('btn');
var apikey = document.getElementById("apikey").value;
[...]
});

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?
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.

Categories

Resources