Manipulate DOM in Electron - javascript

What is the best way to manipulate DOM within an electron app?
I made some tutorials from docs using ipc and webcontents with no luck
My app is so simple, I just want to use the web like a console and showing messages (render proc) comming from the results of several sync functions (main proc)
I updated the question with real code.
I'm going to put another code, more simple to see and more simple to test (I think), is real code and works (but not like I want)
When I launch electron only writes the last message.
Ok, the response is really fast and I may not see the first messsage but to discard that I put a setTimeout and a large for() loop too, to make the uppercase function takes longer
index.js
const electron = require('electron');
const {app} = electron;
const {BrowserWindow} = electron;
const ipc = require('electron').ipcMain
app.on('ready', () => {
let win = new BrowserWindow({width: 800, height: 500});
win.loadURL('file://' + __dirname + '/index.html');
// Emitted when the window is closed.
win.on('closed', () => {
win = null;
});
// Event that return the arg in uppercase
ipc.on('uppercase', function (event, arg) {
event.returnValue = arg.toUpperCase()
})
});
index.html
<html>
<body>
<div id="konsole">...</div>
<script>
const ipc = require('electron').ipcRenderer
const konsole = document.getElementById('konsole')
// Functions
let reply, message
// First MSG
reply = ipc.sendSync('uppercase', 'Hi, I'm the first msg')
message = `Uppercase message reply: ${reply}`
document.getElementById('konsole').innerHTML = message
// Second MSG
reply = ipc.sendSync('uppercase', 'Hi, I'm the second msg')
message = `Uppercase message reply: ${reply}`
document.getElementById('konsole').innerHTML = message
</script>
</body>
</html>

You can comminicate between your frond-end and back-end with webContents and IPC. Then, you'll be able to manipulate your codes in front-end with backend's directive.
For Instance (Backend to Frontend);
Your main.js is sending a message to your front-end.
mainwindow.webContents.send('foo', 'do something for me');
Your frond-end will welcome that message here;
const {ipcRenderer} = require('electron');
ipcRenderer.on('foo', (event, data) => {
alert(data); // prints 'do something for me'
});
For Instance (Frontend to Backend);
Your frond-end;
const {ipcRenderer} = require('electron');
ipcRenderer.send('bar',"I did something for you");
Your back-end;
const {ipcMain} = require('electron');
ipcMain.on('bar', (event, arg) => {
console.log(arg) // prints "I did something for you"
event.sender.send('foo', 'done') // You can also send a message like that
})
UPDATE AFTER UPDATING QUESTION;
I tried your codes on my local, It seems like working.
Could you please try it with insertAdjacentHTML instead of 'innerHTML' to just make sure or just use console.log.
Like this;
document.getElementById('konsole').insertAdjacentHTML('beforeend',message);

"result" is a reference type value. "result" always chenge value when result = funcC() or another; Try this:
$('#msg').text(result.ToString());

Related

Cannot send the data from main process to render in Electron

I am beginner to Electron, and I am developing an educational game. When the user clicks the start button, I want the main process to fetch information from the storage folder (I am ok with data being open to users). The main process accomplishes that without a problem.
main.js
ipcMain.on('load-sets', (event) => {
const directoriesInDIrectory = fs.readdirSync(folderName, { withFileTypes: true })
.filter((item) => item.isDirectory())
.map((item) => item.name);
event.sender.send("loaded-sets", directoriesInDIrectory);
})
Then, I use ipcRenderer in preload.js to receive the message.
preload.js
ipcRenderer.on("loaded-sets", (event, package) => {
window.loaded_sets = [];
for (var i = 0; i < package.length; i++) {
window.loaded_sets[i] = package[i];
}
})
Finally, I expose the sets via contextBridge:
contextBridge.exposeInMainWorld("api", {
LoadFiles,
quit,
sets: () => window.loaded_sets,
sentToRender,
})
However, when I run the following code in render.js:
console.log(window.api.sets())
it outputs undefined.
I've already tried using postMessage and eventListeners associated with it. Also, I've tried to get the variable via another function:
function sentToRender() {
return window.loaded_sets;
The function was also exposed and could be called in the renderer process. Yet, the output was still undefined.
For those wondering why I won't send the data straight to the renderer, the renderer returns error when I try require ipcRenderer and I heard that it is a good practice to navigate data through preload. Is there a solution?
In main.js
ipcMain.on('load-sets', (event) => {
const directoriesInDIrectory = fs.readdirSync(folderName, {
withFileTypes: true })
`enter code here`.filter((item) => item.isDirectory())
.map((item) => item.name);
event.sender.send("loaded-sets", directoriesInDIrectory);
})
In preload.js
contextBridge.exposeInMainWorld("api", (package)=> ipcRenderer.on("loaded-sets", (package)))
In renderer.js
window.api((event, package) => {
window.loaded_sets = [];
for (var i = 0; i < package.length; i++) {
window.loaded_sets[i] = package[i];
}
console.log(window.loaded_sets)
})
Add event emitting from renderer process if it’s wasn’t done.
ipcRenderer.send(“load-sets”);
To make the function work on main process side.
But if you don’t want to send the event from renderer process, then remove ipcMain listener and just send the event by doing
yourRenderedWindow.webContents.send("loaded-sets", directoriesInDIrectory);

How functions io() and feathers() exist?

I am new to Feathers and this could be the most dumb question I have ever asked in my entire life of developer, but I will jump in... hoping you can help me
This extract is from the quick start of Feathers website
<script src="//unpkg.com/#feathersjs/client#^4.3.0/dist/feathers.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.4/socket.io.js"></script>
<script type="text/javascript">
// Set up socket.io
const socket = io("http://localhost:3030");
const app = feathers();
// Register socket.io to talk to the server
app.configure(feathers.socketio(socket));
// Form submission handler that sends a new message
async function sendMessage() {
const messageInput = document.getElementById("message-text");
// Create a new message with the input field value
await app.service("messages").create({
text: messageInput.value,
});
messageInput.value = "";
}
// Renders a single message on the page
function addMessage(message) {
document.getElementById("main").innerHTML += `<p>${message.text}</p>`;
}
const main = async () => {
// Find all existing messages
const messages = await app.service("messages").find();
// Add existing messages to the list
messages.forEach(addMessage);
// Add any newly created message to the list in real-time
app.service("messages").on("created", addMessage);
};
main();
</script>
And I am wondering:
Where the functions io() from const socket = io("http://localhost:3030");and feathers() from const app = feathers(); are defined ? That doesn't make an error
those two functions are defined inside
<script src="//unpkg.com/#feathersjs/client#^4.3.0/dist/feathers.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.4/socket.io.js"></script>
if you want to use them inside webpack project (react, vue, ...) use proper packages:
const feathers = require('#feathersjs/feathers');
const socketio = require('#feathersjs/socketio-client');
const io = require('socket.io-client');
const socket = io('http://api.feathersjs.com');
const app = feathers();

Flask only working as expected in debug mode

I'm creating a desktop application using electron and flask. When I attempt to send a selected path (selected by the user via a folder select dialog. Using electron I am able to get the full path and not something like C:/fake_path. This is because electron provides the full path via .files[0].path) to python using flask, I never receive a response or caught an error (the main.logPython function is never called in either branch following the rq).
However, if I include app.debug = True in the python file to turn on the Flask debugger, it runs as expected and prints out the path. Essentially the python code is not even being reached unless I put it into debug mode.
edit: I know this is true because I've attempted to run python code other than just the return (like creating a new file) and it's never run.
edit #2: It seems this problem is related to the admin.js file. If I attempt to make a similar connection in the main.js file I get the result that I expect, however if I use the same code in the admin.js file I get no response as stated above.
Hopefully I've provided enough information. If you need anything else please let me know.
Sorry for any formatting issues with the JavaScript code, I'm very new to it so I just threw it into dirty markup to clean it up a bit.
calling JavaScript code (admin.js)
const remote = require("electron").remote;
const main = remote.require("./main.js");
var importButton = document.getElementById("importButton");
if (importButton.addEventListener) {
importButton.addEventListener("click", importPackages, false);
} else if (importButton.attachEvent) {
importButton.attachEvent("onclick", importPackages);
}
function importPackages() {
var importDirectory = document.getElementById("importDirectory").files[0].path;
var rq = require('request-promise');
var mainAddr = 'http://localhost:5000';
rq(mainAddr + "/import_packages/" + importDirectory).then(function(htmlString) {
if (htmlString != "false") {
main.logPython(htmlString);
}
}).catch(function(err) {
main.logPython('request not sent');
})
}
python code (database_handler.py)
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from flask import Flask, request
import sqlite3
app = Flask(__name__)
# app.debug = True
#app.route("/")
def hello():
return "true"
#app.route("/login/<username>/<password>")
def login(username, password):
conn = sqlite3.connect("hict.sqlite")
cursor = conn.cursor()
cursor.execute("SELECT firstname, lastname, userclass FROM credentials "
"WHERE username=? AND password=? LIMIT 1;",
(username, password))
data = cursor.fetchall()
if len(data) == 0:
return "false"
else:
return "|".join(map(str, data[0]))
#app.route("/import_packages/<path:import_directory>")
def import_packages(import_directory):
return import_directory
if __name__ == "__main__":
app.run(host='127.0.0.1', port=5000)
Main JavaScript code (main.js)
const electron = require("electron");
const {app, BrowserWindow} = electron;
app.on('window-all-closed', () => {
app.quit();
})
app.on('ready', () => {
var subpy = require('child_process').spawn('python', ['./database_handler.py']);
//var subpy = require('child_process').spawn('./dist/hello.exe');
var rq = require('request-promise');
var mainAddr = 'http://localhost:5000';
var openWindow = () => {
let win = new BrowserWindow({
width: 1920,
height: 1080
});
win.maximize();
win.setMenu(null);
win.loadURL(`file://${__dirname}/login.html`);
win.on('closed', () => {
win = null;
subpy.kill('SIGINT');
})
}
var startUp = () => {
rq(mainAddr).then(function(htmlString) {
console.log('server started!');
openWindow();
}).catch(function(err) {
console.log('waiting for the server start...');
startUp();
})
}
startUp();
})
exports.logPython = (returnData) => {
console.log(returnData);
}
It looks like you are using admin.js as code in your UI, so it is in a renderer process. Your main.js looks like it is running in your project's main process. I am unsure if it actually works to require main.js in admin.js for this case. Why not try using ipc to send the data from your UI to the main process (main.js)?
Also, to eliminate an obvious answer...
console.log() in the main process (main.js) will print out in your terminal as you expect. console.log() in the renderer process (admin.js) prints out in the dev console. On windows I think you open that with by clicking on your UI and then ctrl+shift+i. So if you didn't know this, it might be working but you don't see it.

Trouble getting globalShortcut to register commands to index.js using send function in node/electron javascript app

for some reason my code is compiling with no error, however, my message "Helloworld" is not displaying properly in the console. however my test message is being displayed when i press the bound key combinations. below is my set of code, index.js and main.js
This is written for node/electron.
My main.js file:
//main.js
//requirements
const electron = require('electron');
const app = require('electron').app;
const BrowserWindow = require('electron').BrowserWindow;
const remote = require('electron').remote;
const ipc = require('electron').ipcMain;
const globalShortcut = require('electron').globalShortcut;
var mainWindow = null;
//create app, instantiate window
app.on('ready', function() {
mainWindow = new BrowserWindow({
frame: false,
height: 700,
resizable: false,
width: 368
});
mainWindow.loadURL(`file://${__dirname}/app/index.html`);
//this is the icp bind
globalShortcut.register('ctrl+shift+1', function(){
console.log("test")
mainWindow.webContents.send("testBindicp" ,"HelloWorld");
});
//this is the remote bind
globalShortcut.register('ctrl+shift+2', function(){
console.log("test")
mainWindow.webContents.send("testBindicp" ,"HelloWorld");
});
});
//close the app
ipc.on('close-main-window', function () {
app.quit();
});
below is my entire index.js:
//index.js
const globalShortcut = require('electron').globalShortcut;
const remote = require('electron').remote;
const ipc = require('electron').ipcRenderer;
//testing remote render from remote bind
remote.require('./main.js');
remote.on('testBindRemote', function(event){
console.log(event + " - test - from remote index.js");
});
//testing icpRenderer from icp bind
ipc.on('testBindicp', function (event) {
console.log(event + " - test - from icpRenderer on index.js")
});
//close the app
var closeEl = document.querySelector('.close');
closeEl.addEventListener('click', function() {
ipc.send('close-main-window');
});
the problem i'm having is when i press the keyboard binds, only the console logs from the main.js file are being send to the console. the close command is still working within the rendered window, but any other commands in the index.js window are not being bound to the proper main.js elements.
if i am doing something wrong, please let me know the proper way to implement these methodologies, as the remote and icp structures seem to be confusing to me.
Thank you.
You need to have another argument passed into the listening process ipc.on of index.js file, make it something like this:
ipc.on(<channel name>, function (event, arg) {
console.log(arg + " - test - from icpRenderer on index.js");
});
for more info, visit webContents API docs

Can I mock console in NodeJs?

In my JS test, I need to check if the console.info is called. That's why I want to mock console. However, it seems that the console variable cannot be assigned with a different object. Did I make any mistake?
Here is the code I used:
var oldConsole = console;
var infoContent;
console = {
info: function(content) {
infoContent = content;
}
};
game.process('a command');
infoContent.should.equal('a command is processed');
console = oldConsole;
You can use rewire to replace the whole of console to silence it, or to inject a mock. I use deride but sinon would also work.
var rewire = require('rewire');
var deride = require('deride');
var Game = rewire('../lib/game');
describe('game testing', function() {
var stubConsole, game;
beforeEach(function() {
stubConsole = deride.stub(['info']);
stubConsole.setup.info.toReturn();
Game.__set__({
console: stubConsole
});
game = new Game();
});
it('logs info messages', function() {
game.process('a command');
stubConsole.expect.info.called.withArgs(['a command is processed']);
});
});
I find the solution. I can change the method info of console.
console.info = function(content) {
infoContent = content;
};
The question is now why console object itself cannot be reassigned?
you can use sinon npm to count the call to a function :
it("calls the original function only once", function () {
var callback = sinon.spy();
var proxy = once(callback);
proxy();
proxy();
assert(callback.calledOnce);
// ...or:
// assert.equals(callback.callCount, 1);
});
You can find the docs here : sinonjs.org
I thought I had the same problem and my solution was using this std-mocks module:
https://github.com/neoziro/std-mocks
This has the advantage of not taking over the global "console" but allows you to see what gets logged to the stdout / stderr. This solves the problem in a different way than the question was explicitly looking for; however I believe it is a good answer for the problem the question implies and may be useful for others.
const stdMocks = require('std-mocks');
stdMocks.use(); console.log('test'); stdMocks.restore();
// => undefined [nothing gets output, stdout intercepted]
const logged = stdMocks.flush();
console.log(logged)
// => { stdout: [ 'test\n' ], stderr: [] }

Categories