Retour à l'éditeur

Documentation — Commandes JavaScript des nœuds

Cette page décrit les commandes et le contexte JavaScript utilisables dans les nœuds (script et évaluation des sorties) du moteur de prévisualisation, ainsi que la création de nouveaux types de nœuds.

0. Créer un nouveau type de nœud (YAML)

Depuis l'éditeur, le bouton « Nouveau type de nœud » ouvre un modal contenant un éditeur YAML. Vous y décrivez la définition complète du nœud : identifiant technique, nom, catégorie, entrées, sorties, paramètres, HTML et script.

Exemple minimal :

type: question_perso
name: Question personnalisée
description: Ma question
category: page_elements

inputs:
  - name: In
    type: flow

outputs:
  - name: Out
    type: flow

parameters:
  - key: variable
    name: Nom de la variable
    default: reponse
  - key: label
    name: Libellé de la question
    default: "Votre réponse :"

html: |
  <div class="exp-block">
    <label class="exp-label">{{ label }}</label>
    <textarea class="node-input js-textarea"></textarea>
  </div>

script: |
  const { root, answers, getParam } = ctx;
  const variable = getParam('variable', '');
  if (!variable) return;
  const textarea = root.querySelector('.js-textarea');
  if (!textarea) return;
  const current = answers[variable] != null ? String(answers[variable]) : '';
  textarea.value = current;
  textarea.addEventListener('input', () => {
    answers[variable] = textarea.value;
  });

Le YAML est converti en JSON et stocké comme une définition de nœud classique (équivalent aux fichiers assets/nodes/*.json).

Exemple avec une sortie calculée via outputs[].evaluate (ET logique) :

type: condition_et
name: Condition ET
description: Sortie true si toutes les entrées sont vraies.
category: logic

inputs:
  - name: Entrées
    type: any
    multiconnect: true

outputs:
  - name: Résultat
    type: boolean
    evaluate: |
      var arr = ctx.getInput('Entrées');
      if (!Array.isArray(arr)) arr = arr != null ? [arr] : [];
      if (arr.length === 0) return false;
      return arr.every(function (v) {
        return v === true || v === 'true' || v === 1 || v === '1';
      });

parameters: []

html: |
  <div class="exp-block">
    <div class="exp-label">Condition ET (non visible par le participant)</div>
  </div>

script: ""

1. Deux types de code JavaScript

Dans les définitions de nœuds (JSON), le JavaScript intervient à deux endroits :

2. Contexte ctx — Script du nœud (éléments de page)

Lors de l’affichage d’une page, chaque élément de page dont la définition contient un script est exécuté avec un seul argument : ctx. Vous pouvez déstructurer : const { node, root, answers, getParam, getInput, renderNode, syncForm, next } = ctx;

PropriétéDescription
ctx.nodeLe nœud courant (objet avec id, type, parameters, etc.).
ctx.rootL’élément DOM racine du bloc rendu pour ce nœud (le contenu du html du nœud). Permet de modifier le contenu, attacher des écouteurs, etc.
ctx.answersProxy vers le stockage des réponses. Lire/écrire ctx.answers[nomVariable] pour accéder ou enregistrer une valeur. Les noms peuvent contenir des templates {{i}}, {{j}} (voir section Variables).
ctx.getParam(key, defaultValue)Retourne la valeur du paramètre key du nœud (ou d’une connexion si le port est connecté). Si absent, retourne defaultValue.
ctx.getInput(name)Retourne la valeur (ou le nœud) connecté à l’entrée name. Si l’entrée est en multiconnect, retourne un tableau ; sinon une seule valeur.
ctx.renderNode(childNode)Rend un nœud enfant (élément de page) et retourne son élément DOM. Utile pour les nœuds « conteneur » (ex. Groupe).
ctx.syncForm()Met à jour le stockage answers à partir des champs du formulaire de la page (avant de changer de page ou d’état).
ctx.next()Passe à la page suivante comme si l’utilisateur avait cliqué sur « Suivant » (synchronise les réponses puis affiche la page suivante). Utile dans un bloc Custom HTML ou un script pour avancer automatiquement (ex. après un délai).

Exemple — Question ouverte (remplir le textarea depuis answers, enregistrer à la saisie)

const { root, answers, getParam } = ctx;
const variable = getParam('variable', '');
if (!variable) return;
const textarea = root.querySelector('.js-textarea');
if (!textarea) return;
const showPrev = getParam('show_previous_value', 'false') === 'true';
const current = showPrev && answers[variable] != null ? String(answers[variable]) : '';
textarea.value = current;
textarea.addEventListener('input', () => { answers[variable] = textarea.value; });

Exemple — Image (remplir src, alt, légende depuis les paramètres)

const { root, getParam } = ctx;
const img = root.querySelector('.js-img');
const caption = root.querySelector('.js-caption');
if (!img) return;
const url = (getParam('url', '') || '').trim();
const alt = getParam('alt', 'Image');
const cap = getParam('caption', '');
img.alt = alt;
if (url) img.src = url;
else {
  img.removeAttribute('src');
  img.style.minHeight = '80px';
  img.style.background = '#222';
}
if (caption) {
  caption.textContent = cap;
  caption.style.display = cap ? '' : 'none';
}

Exemple — Custom HTML : passage à la page suivante après un délai (next)

const { root, next } = ctx;
if (typeof next === 'function') {
  root.querySelector('.my-btn')?.addEventListener('click', () => next());
  // Ou avancer automatiquement après 3 secondes :
  // setTimeout(() => next(), 3000);
}

Exemple — Groupe (entrées multiples, ordre aléatoire, renderNode)

const { root, getParam, getInput, renderNode, syncForm } = ctx;
if (typeof renderNode !== 'function') return;
const randomOrder = getParam('random_order', false) === true || getParam('random_order', '') === 'true';
const oneByOne = getParam('one_by_one', false) === true || getParam('one_by_one', '') === 'true';
let children = getInput('In');
if (!Array.isArray(children)) children = [];
if (randomOrder && children.length > 0) {
  children = children.slice();
  for (let i = children.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [children[i], children[j]] = [children[j], children[i]];
  }
}
children.forEach(function (child) {
  const el = renderNode(child);
  if (!el) return;
  const itemWrap = document.createElement('div');
  itemWrap.className = 'exp-groupe-item';
  itemWrap.appendChild(el);
  root.appendChild(itemWrap);
});

3. Contexte ctx — Évaluation des sorties (evaluate)

Pour les sorties dont la définition contient evaluate (chaîne de code), le moteur exécute ce code avec ctx et utilise la valeur retournée. Si evaluate vaut exactement "self" ou "this", le nœud lui-même est retourné (utilisé pour les sorties de type page_element).

PropriétéDescription
ctx.nodeLe nœud dont on évalue la sortie.
ctx.answersProxy des réponses (lecture/écriture). Les clés avec {{i}}, {{j}} sont résolues selon la pile de boucles courante.
ctx.getParam(key, defaultValue)Paramètre effectif du nœud (valeur du champ ou valeur évaluée si le port est connecté).
ctx.getInput(name)Valeur(s) des nœuds connectés à l’entrée name. Multiconnect ⇒ tableau, sinon une seule valeur.
ctx.randomStateObjet partagé pour stocker un état déterministe (ex. tirage aléatoire une seule fois par nœud). Clé conseillée : '_aleatoire_' + ctx.node.id.

Exemple — Get (lire une variable et la retourner)

var v = ctx.answers[ctx.getParam('variable', '')];
return v === undefined || v === null ? '' : String(v);

Exemple — Condition (comparer une entrée à une valeur de référence)

var actual = ctx.getInput('Valeur');
if (actual === undefined || actual === null) return false;
actual = String(actual).trim();
var op = ctx.getParam('operator', '>=');
var cmp = ctx.getParam('compare_value', '');
var na = Number(actual), nc = Number(cmp);
var num = !isNaN(na) && !isNaN(nc);
var ok = false;
if (num) {
  switch (op) {
    case '>=': ok = na >= nc; break;
    case '>':  ok = na > nc;  break;
    case '<':  ok = na < nc;  break;
    case '<=': ok = na <= nc; break;
    case '==': ok = na === nc; break;
    case '!=': ok = na !== nc; break;
    default:   ok = na >= nc;
  }
} else {
  switch (op) {
    case '==': ok = actual === cmp; break;
    case '!=': ok = actual !== cmp; break;
    default:   ok = actual >= cmp;
  }
}
return ok;

Exemple — ET logique (toutes les entrées true)

var arr = ctx.getInput('Entrées');
if (!Array.isArray(arr)) arr = arr != null ? [arr] : [];
if (arr.length === 0) return false;
return arr.every(function (v) {
  return v === true || v === 'true' || v === 1 || v === '1';
});

Exemple — Aléatoire (un tirage fixe par nœud, stocké dans randomState)

var key = '_aleatoire_' + ctx.node.id;
if (ctx.randomState[key] == null) {
  var lines = (ctx.getParam('textes', '') || '').split(/\r?\n/)
    .map(function (s) { return s.trim(); })
    .filter(Boolean);
  var n = lines.length;
  var choice = n > 0 ? Math.floor(Math.random() * n) : 0;
  ctx.randomState[key] = choice;
  var v = ctx.getParam('variable', '');
  if (v) ctx.answers[v] = lines[choice] || '';
}
return Number(ctx.randomState[key]);

4. Variables et templates {{i}}, {{j}}

Les noms de variables (paramètre « Variable » des nœuds, clés de answers) peuvent contenir des placeholders : {{i}}, {{j}}, etc. Ils sont remplacés par l’index d’itération de la boucle dont l’itérateur correspond (paramètre iterateur du nœud Début de boucle).

Exemple : avec une boucle d’itérateur i (3 répétitions) et une boucle interne j (2 répétitions), la variable answer_{{i}}_{{j}} donne answer_0_0, answer_0_1, answer_1_0, etc. Cela permet d’enregistrer une valeur distincte par passage dans la boucle.

// Dans un script de nœud : écrire dans une variable indexée par l'itération
ctx.answers['reponse_{{i}}'] = inputValue;

// Dans evaluate : lire une variable indexée
var idx = ctx.getParam('index', '0');
return ctx.answers['item_' + idx];

5. getParam et connexions

getParam(key, defaultValue) retourne la valeur effective du paramètre : si le port du paramètre est connecté à une sortie d’un autre nœud, c’est la valeur évaluée de cette sortie qui est retournée ; sinon la valeur saisie dans le nœud. Cela permet de piloter un paramètre (variable, opérateur, valeur de comparaison, etc.) par le graphe.

// Lire un paramètre avec valeur par défaut
var variable = ctx.getParam('variable', '');
var op = ctx.getParam('operator', '>=');

6. getInput — une entrée ou plusieurs

Selon que l’entrée est définie avec "multiconnect": true ou non :

// Une seule entrée (ex. Condition)
var valeur = ctx.getInput('Valeur');

// Plusieurs entrées (ex. ET logique, Groupe)
var arr = ctx.getInput('Entrées');
if (!Array.isArray(arr)) arr = arr != null ? [arr] : [];
arr.forEach(function (v) { /* ... */ });

7. Récapitulatif