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.
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: ""
Dans les définitions de nœuds (JSON), le JavaScript intervient à deux endroits :
script) : exécuté lors du rendu de la page pour les éléments de page (questions, HTML personnalisé, groupe, image, etc.). Il reçoit un objet ctx avec notamment root, getParam, answers, getInput, renderNode, syncForm, next.outputs[].evaluate) : exécuté quand le moteur a besoin de la valeur d’une sortie (flux logique, conditions, Get, Aléatoire, etc.). Le code reçoit ctx avec node, answers, getParam, getInput, randomState et doit retourner cette valeur.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.node | Le nœud courant (objet avec id, type, parameters, etc.). |
ctx.root | L’é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.answers | Proxy 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). |
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; });
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';
}
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);
}
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);
});
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.node | Le nœud dont on évalue la sortie. |
ctx.answers | Proxy 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.randomState | Objet partagé pour stocker un état déterministe (ex. tirage aléatoire une seule fois par nœud). Clé conseillée : '_aleatoire_' + ctx.node.id. |
var v = ctx.answers[ctx.getParam('variable', '')];
return v === undefined || v === null ? '' : String(v);
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;
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';
});
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]);
{{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];
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', '>=');
Selon que l’entrée est définie avec "multiconnect": true ou non :
getInput('Nom') retourne une seule valeur (ou le nœud pour les sorties page_element), ou undefined si rien n’est connecté.getInput('Nom') retourne un tableau des valeurs/nœuds connectés (éventuellement vide).// 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) { /* ... */ });
root), lire/écrire answers, utiliser getParam, getInput, renderNode, syncForm, next. Pas de valeur de retour utilisée.getParam, getInput, answers, randomState, et la retourner avec return.{{i}}, {{j}} pour être indexés par les boucles.evaluate) la valeur retournée est null.