0

This is the project structure I would like to have.

enter image description here

kareljs folder is an Electron app and if I run

npm start

inside that folder, a window pop up and run method of karel.js is executed when the button Run Karel is clicked.

./eg01/hello_karel.js, ./eg02/pick_newspaper.js etc., are where I program Karel using the methods provided by ./kareljs/karel/karel.js module. Running the program must bring up the same window above, but when Run Karel button clicked, the run method in these Karel program files should be called, instead of the default one. (What I need is sort of method overriding in Java. Here I override run method via new js file).

And to run the program, I should just be able to

node ./eg01/hello_karel.js

Or

node hello_karel.js

if I am in eg01 folder. Ideally, no config file should be required to run these programs. I mean inside eg01, eg02 folders. Of course, Karel is for newbies, and for them, the simpler the better it is.

What are the possible mechanisms to achieve my requirements?

Code:

// main.js
const { app, BrowserWindow } = require('electron/main')

const createWindow = () => {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: { 
        contextIsolation: false,
        nodeIntegration: true
       },
  })

  win.loadFile('index.html')
}

app.whenReady().then(() => {
  createWindow()

  app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) {
      createWindow()
    }
  })
})

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src 'self'; script-src 'self'"
    />
    <meta
      http-equiv="X-Content-Security-Policy"
      content="default-src 'self'; script-src 'self'"
    />
    <title>Hello from Electron renderer!</title>
  </head>
  <body>
    <h1>Hello from Electron renderer ... !</h1>
    <p>👋</p>
    <p id="info"></p>
    <p><button class="run">Run Karel</button></p>
    <script src="./renderer.js"></script>  
  </body>  
  
</html>
// karel.js
const move = () => {
    alert("Karel moves one step forward!")
};

const turnLeft = () => {
    alert("Kare turns left!")
};

const pickBeeper = () => {
    alert("Karel picks a beeper!")
};

const putBeeper = () => {
    alert("Karel puts a beeper!")
};

const run = () => {
    alert("This is the default run method!")
}
    

// Updated: to write user_defined.js file with content from hello_karel.js
const { exec,spawn } = require("child_process");
const fs  = require("fs");
var path = require('path');

const start_karel = (file_name) => {
    console.log(__dirname)
    console.log("The .js file executed: " + file_name)
    var user_script;
    fs.readFile(file_name, 'utf8', (err, data) => {
        if (err) {
          console.error(err);
          return;
        }
        console.log(data);
        var user_defined = path.join(__dirname, '..', 'user_defined.js');
        var user_script = data.replace(/start_karel\(__filename\)/g, "")
        fs.writeFile(user_defined, user_script, 'utf-8', function (err) {
            console.log(err);
        });
    });
    var index_html = path.join(__dirname, '..', 'index.html');
    
    /*
    fs.readFile(index_html, 'utf8', (err, data) => {
        if (err) {
          console.error(err);
          return;
        }
        console.log("about to replace...")
        console.log(data);
        var replaced = data.replace(/default/g, 'user')

        console.log("replaced " + replaced)

        fs.writeFile(index_html, replaced, 'utf-8', function (err) {
            console.log(err);
        });
        console.log("done replace...")

      });
    */
    
    exec("cd ../kareljs && ./node_modules/.bin/electron main.js " + file_name, (error, stdout, stderr) => {
        if (error) {
            console.log(`error: ${error.message}`);
            return;
        }
        if (stderr) {
            console.log(`stderr: ${stderr}`);
            return;
        }
        console.log(`stdout: ${stdout}`);
    });
    
    /*
    spawn("cd ../kareljs && ./node_modules/.bin/electron main.js " + file_name, {
        stdio: 'inherit',
        shell: true
    })
    */


}

module.exports =  {move, turnLeft, putBeeper, pickBeeper, run, start_karel};
//renderer.js
const {move, turnLeft, putBeeper, pickBeeper, run} = require('./karel/karel')

const btn = document.querySelector('.run')
btn.addEventListener('click', () => {run()})

package.json

{
  "name": "hello-electron-app",
  "version": "1.0.0",
  "description": "test",
  "main": "main.js",
  "scripts": {
    "start": "electron .",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "electron": "^28.2.1"
  }
}
//hello_karel.js
const {move, turnLeft, putBeeper, pickBeeper, run, start_karel} = require('../kareljs/karel/karel')

function turnRight() {
    turnLeft()
    turnLeft()
    turnLeft()
}

start_karel()

Also have GitHub repository here.

Update: I can now start the app just by running

node hello_karel.js

Still need to figure out how to change the behavior of the run method which is called via the button click. (Please see the update in karel.js above.)

5
  • If I can pass the file name (hello_karel.js above) to the app somehow, there should be a way to call the run method in the file dynamically.
    – Pyi Soe
    Commented Feb 7 at 7:04
  • Fortunately, I think I can achieve what I want soon. But sadly, I haven't gotten any response from SO and Electron Discord Channel.
    – Pyi Soe
    Commented Feb 7 at 11:59
  • After running node hello_karel.js, the files contents can be read from main.js. After this, I should be able to share the content (of hello_karel.js in this case) to renderer process. Or may be I just extract the run and other user defined functions , and then write them to index.html, like <script>user defined scripts<\script>
    – Pyi Soe
    Commented Feb 7 at 15:07
  • Now, at least I can achieve the requirement above. From the karel.js module, I just write the hello_karel.js content to a new file user_defined.js file, which is included in index.html file with <script src="./user_defined.js"> </script> . I need to remove start_karel(__filename) from hello_karel.js, otherwise, infinite Electron app start one after another without stopping (I was thinking only one more app will get started when the script is loaded in index.html). Don't know why. I don't know what problem the current method can have. But at least, it give the result I want.
    – Pyi Soe
    Commented Feb 8 at 2:34
  • I would really really appreciate if anyone can suggest a better approach which I should try.
    – Pyi Soe
    Commented Feb 8 at 2:45

1 Answer 1

0

Using IPC to pass function references from the main process to the renderer? Something like this:

// main.js

ipcMain.on('runFunction', (e, fn) => {
  if (fn === 'helloKarel') {
    let helloKarelFn = require('./eg01/hello_karel.js').run;
    e.sender.send('runFunction', helloKarelFn);
  }
});

// renderer.js
const { ipcRenderer } = require("electron");

const btn = document.querySelector(".run");
btn.addEventListener("click", () => {
    ipcRenderer.send('sendFunction', 'helloKarel')
});

ipcRenderer.on('runFunction', (e, fn) => {
  fn();
});

5
  • Hi, I am really glad to receive the first answer. I'll test and get back to you. Seems to be better than what I did. @normalcepalin
    – Pyi Soe
    Commented Feb 8 at 16:16
  • How start_karel(__filename, yourRunFn) call would pass yourRunFn to renderer.js is a little more complex than what we think I guess. Because in Electron, main and renderer are separate processes, so IPC has to be used to share data between the two. @normalcepalin
    – Pyi Soe
    Commented Feb 9 at 2:22
  • For sure, data can be shared between main and renderer. But, I still can't find a simple working example yet, there are many examples online though. That's why I tried writing to user_defined.js.
    – Pyi Soe
    Commented Feb 9 at 2:27
  • Other than yourRunFn, we will also have to share other user defined functions, such as turnRight(). Sort of combining karel.js and hello_karel.js into a single module or loading hello_karel.js module dynamically. For Python, there is a good example to do that explained here. JS must be capable of that as well. Just I don't know how.
    – Pyi Soe
    Commented Feb 9 at 2:35
  • I think import() function can be used to load hello_karel.js on the fly. Just have to pass the correct file path to renderer process. This again goes back to IPC. The file name has to be passed from main to renderer.
    – Pyi Soe
    Commented Feb 9 at 2:43

Not the answer you're looking for? Browse other questions tagged or ask your own question.