Electron: Prompt et boite de dialogue modale

Création d'une boite de dialogue modale pour interagir avec l'utilisateur.

Il est possible d'utiliser les fonctions alert et confirm dans une application avec Electron. Mais l'absence de support pour la boite de dialogue prompt, et l'intention affichée de ne jamais l'implémenter, nous oblige à créer un widget de substitution, car il est quelquefois indispensable dans une application de demander une information à l'utilisateur avant de continuer un traitement.
Il faut aussi que la boite de dialogue soit modale pour que les commandes au clavier soient actives dans la boite de dialogue et non dans l'application.

Pour créer cette boite de dialogue nous utilisons l'objet BrowserWindow et une page HTML définissant le contenu de la fenêtre. Il serait plus simple de la créer dans la fenêtre de l'application, mais ce n'est pas possible. Pour être modale, une fenêtre doit indiquer - de par sa définition - la fenêtre parente. Il doit donc se trouver dans le processus principal, car il semble qu'une fenêtre du process renderer ne puisse pas avoir de parent dans le process main, et la fenêtre de l'application s'ouvre dans le process main.

Nous utilisons aussi le protocole de communication IPC d'Electron qui convient mieux que WebSocket grâce à son mode synchrone. Voir IPC vs. WebSocket pour une comparaison des deux protocoles.

Code source de la page HTML

<body>
<p id="label"></p>
<p>
  <input type = "text" id="data" value="">
  <input type = "button" id="ok" value="OK" onclick="response()">
  <input type = "button" value="Cancel" onclick="cancel()">
</p>

<script>
const { ipcRenderer } = require("electron")
function cancel() {
  ipcRenderer.send("closeDialog", "")
  this.close();
}

function response() {
  ipcRenderer.send("closeDialog", document.getElementById("data").value)
  this.close();
}

window.onload=function() {
  var options = ipcRenderer.sendSync("openDialog", "")
  var params = JSON.parse(options)
  document.getElementById("label").innerHTML = params.label;
  document.getElementById("data").value = params.value;
  document.getElementById("ok").value = params.ok;
}
</script>
</body>

Au chargement de la page des éléments de personnalisation sont demandé au backend: un libellé descriptif, une valeur par défaut pour le champ de d'entrée texte, et le libellé du bouton d'action.
Le titre de la boite de dialogue est donné par le backend.

Quand on clique sur le bouton OK, le contenu tapé par l'utilisateur est transmis au backend par le canal "closeDialog".
Quand on clique sur Cancel, on envoie une chaîne vide.

Le code source du backend

La boite de dialogue est créée dans le processus principal, à coté de la fenêtre de l'application dont elle doit être définie comme enfant.

var promptWindow;
var promptOptions
var promptAnswer;

// Creating the dialog

function promptModal(parent, options, callback) {
  promptOptions = options;
  promptWindow = new BrowserWindow({
    width:360, height: 120, 
    'parent': parent,
    'show': false,
    'modal': true,
    'alwaysOnTop' : true, 
    'title' : options.title,
    'autoHideMenuBar': true,
    'webPreferences' : { 
      "nodeIntegration":true,
      "sandbox" : false 
    }   
  });
  promptWindow.on('closed', () => { 
    promptWindow = null 
    callback(promptAnswer);
  })

  // Load the HTML dialog box
  promptWindow.loadURL(path.join(__dirname, "prompt.html"))
  promptWindow.once('ready-to-show', () => { promptWindow.show() })
}

// Called by the dialog box to get its parameters

ipcMain.on("openDialog", (event, data) => {
    event.returnValue = JSON.stringify(promptOptions, null, '')
})

// Called by the dialog box when closed

ipcMain.on("closeDialog", (event, data) => {
  promptAnswer = data
})

// Called by the application to open the prompt dialog

ipcMain.on("prompt",  (event, notused) => {
	promptModal(win, {
	    "title": "Prompt demo",
	    "label":"Fill this input field:", 
	    "value":"example", 
	    "ok": "ok"
	    }, 
	    function(data) {
        event.returnValue = data
      }
    );        
});

Le canal "openDialog" est créé par la page de la boite de dialog lors du chargement, pour récupéer les paramètres de la boite.
Le canal "closeDialog" est ouvert pour retourner la valeur entrée par l'utilisateur, ou une chaîne vide s'il abandonne l'opération.

Un troisième canal, "prompt" doit aussi être ouvert, cette fois par l'application, pour commander l'ouverture de la boite de dialogue. Comme les deux précédent, il est synchrone puisque la fenêtre est modale et interrompt toute autre opération.

En callback, le canal "prompt" appelle la fonction promptModal qui crée la boite de dialogue. La fenêtre parente win est spécifiée. La fonction promptModal a elle-même un callback qui retourne à l'application qui a ouvert le canal "prompt" la valeur entrée par l'utilisateur.

Le code source de l'application

Cette page HTML est chargée dans la fenêtre du processus principal.

<body>
  <h1>Prompt demo</h1>
  <p>
    <input type="button" value="Get a value" onclick="ePrompt()">
  </p>
  <fieldset><div id="answer"></div></fieldset>

<script>
const { ipcRenderer } = require("electron")
function ePrompt() {
  var answer = ipcRenderer.sendSync("prompt", "")
  if(answer != "") 
    document.getElementById("answer").innerHTML = answer;
} </script> </body>

Ce programme de démonstration est plutôt simple: quand on clique sur le bouton, cela appelle la fonction ePrompt(). Celle-ci ouvre le canal synchrone "prompt" avec le backend, ce qui commande à celui-ci d'ouvrir la boite de dialogue.
La réponse de l'utilisateur est la valeur de retour de la fonction sendSync de ipcRenderer.
Cette valeur est alors affichée dans un fieldset.

Téléchargement

Le code complet de la démonstration est disponible dans une archive à télécharger.

Pour lancer l'application, allez dans le répertoire electron-prompt et tapez:

electron prompt.js

La boite de dialogue est utilisée par Advanced Explorer en version 3.3 pour copier un fichier sous un nouveau nom.