Créer des propriétés dynamiquement, en C++

Module d’exemple : hellodyn.

loadOstPropertiesFromFile() n’est qu’une commodité : elle appelle, pour chaque propriété et chaque élément du JSON, exactement les mêmes primitives qu’un module peut appeler lui-même directement, à tout moment de sa vie — à l’instanciation, ou bien plus tard, en réaction à une action utilisateur.

Construire une propriété à la main

OST::PropertyMulti *pm = new OST::PropertyMulti(
    "hello", "Hello", OST::Permission::ReadOnly,
    /* level1 */ "", /* level2 */ "", /* order */ "1",
    /* hasProfile */ false, /* hasGrid */ false);
createProperty(pm);

OST::ElementString *message = new OST::ElementString("message", "Message", "1", "");
message->setValue("Hello, world!");
pm->addElt(message);
level1/level2 en C++, devcat/group en JSON

Le constructeur de PropertyMulti nomme ses deux paramètres de hiérarchie level1/level2 (et c’est aussi le nom renvoyé au client dans le dump JSON). Mais la clé JSON d’un fichier .json de module est devcat/group, pas level1/level2 — deux noms différents pour la même chose, selon qu’on écrit du C++ ou du JSON. Une confusion facile à faire.

Chaque type d’élément a le même schéma de constructeur : ElementXxx(clé, libellé, ordre, info-bulle). addElt() doit être appelé après createProperty().

Pour ElementInt/ElementFloat, les contraintes numériques (min/max/step/format — mêmes clés qu’en JSON, voir Le catalogue des types) se posent après construction, par des setters dédiés :

OST::ElementFloat *step = new OST::ElementFloat("step", "Step size", "2", "");
step->setMinMax(1.0, 1000.0, false);
step->setStep(1.0, false);
step->setFormat("%.0f", false);
pm->addElt(step);

Le dernier argument (false ici) est le même emitEvent que setValue() — pas la peine de prévenir le client d’un changement de métadonnée au moment où l’élément n’existe pas encore côté frontend.

Créer une propriété à la demande, pas au démarrage

Un bouton (ElementBool, directedit:true) peut déclencher la création plutôt que de la faire au démarrage :

void HelloDyn::onExternalEvent(OST::ExtEvent event)
{
    if (event.ev != OST::ExtEvType::SV || event.prpkey != "actions") return;

    if (event.eltkey == "create")
    {
        if (!getStore().contains("hello"))
        {
            OST::PropertyMulti *pm = new OST::PropertyMulti("hello", "Hello",
                    OST::Permission::ReadOnly, "", "", "2", false, false);
            createProperty(pm);
            OST::ElementString *message = new OST::ElementString("message", "Message", "1", "");
            message->setValue("Hello, world! (créé à la demande)");
            pm->addElt(message);
        }
        getEltBool("actions", "create")->setValue(false, true);
    }
}

Chaque branche repasse son propre bouton à false une fois l’action traitée — un bouton d’action est un déclencheur ponctuel, pas un interrupteur persistant.

Ajouter ou retirer un élément d’une propriété déjà existante

if (event.eltkey == "addfield")
{
    OST::ElementString *field = new OST::ElementString(key, label, "", "");
    field->setValue(value);
    getProperty("hello")->addElt(field);
}

if (event.eltkey == "removefield")
{
    getProperty("hello")->deleteElt(key);
}

addElt()/deleteElt() notifient le client de la modification (un nouvel élément apparaît, ou disparaît, sans qu’un rechargement de page soit nécessaire).

Garder une trace des clés ajoutées

PropertyMulti ne numérote pas ses éléments lui-même — c’est au module de générer des clés uniques (un compteur, par exemple) pour savoir lesquelles existent et dans quel ordre les retirer.