Creating properties dynamically, in C++

Example module: hellodyn.

loadOstPropertiesFromFile() is only a convenience: for each property and each element in the JSON, it calls exactly the same primitives that a module can call directly itself, at any point in its lifetime — at instantiation, or later, in reaction to a user action.

Building a property by hand

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 in C++, devcat/group in JSON

PropertyMulti’s constructor names its two hierarchy parameters level1/level2 (and that’s also the name sent back to the client in the JSON dump). But the JSON key in a module’s .json file is devcat/group, not level1/level2 — two different names for the same thing, depending on whether you’re writing C++ or JSON. An easy mix-up to make.

Every element type has the same constructor signature: ElementXxx(key, label, order, tooltip). addElt() must be called after createProperty().

For ElementInt/ElementFloat, the numeric constraints (min/max/step/format — the same keys as in JSON, see The type catalog) are set after construction, through dedicated setters:

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);

The last argument (false here) is the same emitEvent as setValue() — no point notifying the client of a metadata change at a time when the element doesn’t exist yet on the frontend.

Creating a property on demand, not at startup

A button (ElementBool, directedit:true) can trigger the creation instead of doing it at startup:

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);
    }
}

Each branch resets its own button to false once the action has been handled — an action button is a one-shot trigger, not a persistent toggle.

Adding or removing an element from an existing property

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() notify the client of the change (a new element appears, or disappears, without needing a page reload).

Keep track of the keys you added

PropertyMulti doesn’t number its elements itself — it’s up to the module to generate unique keys (a counter, for example) to know which ones exist and in what order to remove them.