Understanding /EDIT/MERGE

receiveOsc({
            address: '/EDIT/MERGE',
            args: [
            { type: 's', value: 'button_test' },
            { type: 's', value: {'css' : 'display:show' } }
            ]
        })

This seems to replace everything that was previously in the css field of the widget.
Am I using /EDIT/MERGE wrong?

The css property is a string, /EDIT/MERGE can only merge objects properties, not strings. Here is an example : https://github.com/jean-emmanuel/open-stage-control/issues/433#issuecomment-440228660. In your case, the most simple way could be using osc listeners instead to define your css conditionnally:

#{
OSC{/button_test/display, 1} ? "display:none;" : ""
}

https://openstagecontrol.ammd.net/docs/advanced-property-syntax/#osc-listeners-osc

Ok, that makes sense. I am working in the custom-module and am looping through buttons based on the articulation and track objects. I’m then attempting to arrange them on screen based on articulation types (short, long etc) and hiding buttons that have no articulation associated. As I am inexperienced, I’m not sure how to do this looping with the OSC listener. But no worries. I can hard code the css I need for the buttons in the custom module. Thanks so much for your time. Such a great project.

You could also leave the button container empty in your session and create the buttons on the fly from the custom module, it might be more efficient and easier to maintain. Here’s an exemple, you’ll need to adjust it of course:


// mapping between the daw's track identifiers and the articulation's labels
var tracks = {
  0: ['label_1'],
  1: ['label_1', 'label_2']
}

// articulations details that will be used to create buttons
var articulations = {
  'label_1': {
    address: '/address_1',
    color: 'red',
    // etc
  }
  'label_2': {
    address: '/address_2',
    color: 'blue',
    // etc
  }
}


////////////////////////////////////////////////////
/* This part would go in the oscInFilter function */
////////////////////////////////////////////////////

// let's say current track is 0
var n = 0 // this should be retreived from osc data

// retreive articulations that should be displayed
var available_articulations = tracks[n]

// create button objects
var buttons = []
for (var i in available_articulations) {
  var label = available_articulations[i]
  buttons.push({
    type: 'toggle',
    label: label,
    address: articulations[label].address,
    color: articulations[label].color,
    // etc
  })
}

// replace the container's children with our list of button
receive('/EDIT', 'button_container_id', {widgets: buttons})

It’s expected, the color property is not the same as the css color property, it defines the widget’s custom (accent) color (blue/teal by default).

Aah. Ok. No worries. In the help, it says CSS color code and I think that threw me. Capture

It’s a bit confusing indeed, I’ll try to improve that. It means it’s supposed to be written as a css color.

I’ve combined your example code with your other articulationFilter example code. I have added another type property to the articulation object e.g. (type:short) and am filling button arrays based on the type of articulation, then using /EDIT to show the buttons in their corresponding containers (panel_short, panel long etc). This concept is working well, but I am unsure how I could do this dynamically (i.e. not hard code the button array names, container names etc. If there are only 2 types of articulations for the track, create the container names based on the two types (short, long. Hard mallets, soft mallets. With mute, without mute etc It should get this information from the articulation object). I think I should also make the switch case section much shorter. There is a lot of repeated code. Perhaps a function. As I’m not a coder at all, but am enjoying learning this software, if you have any thoughts on how I could achieve this, or if I am doing anything here that is really terrible practice, that would be great. Appreciate your time. Hope this also helps others. Here is my code:

(function(){
    // mapping between the daw's track identifiers and the articulation's labels
    var tracks = {
        1 : { maker : "VSL", group : "WW", inst : "clarinet", arts : ['stacc' , 'sus' , 'trill']}, //etc
        2 : { maker : "VSL", group : "strings", inst : "violin", arts : ['stacc' , 'trill whole' , 'trem', 'port']}
        // etc
    }

    colors = {
        short:'yellow',
        long: 'pink',
        deco: 'purple',
    }

    // articulations details that will be used to create buttons
    var articulations = {
      'stacc': {
        color: colors.short, 
        type: 'short',
        // etc
      },
      'sus': {
        color: colors.long,
        type: 'long',
        // etc
      },
      'trill': {
        color: colors.deco,
        type: 'deco',
      },
      //etc
    }

    function articulationFilter(address, args){

        // only filter messages that match our needs
        if (address !== '/control') return

        var trackname = 0, //just for testing
            buttons_short = [], //how can I do these dynamically?
            buttons_long = [],
            buttons_deco = [],
            counter = 1,
            selectedTrack = tracks[trackname]

            for (var articulation in articulations) {

                if (selectedTrack !== undefined && selectedTrack.arts.includes(articulation)) {

                    var type = articulations[articulation].type

                    switch (type){
                        case 'short':
                            buttons_short.push({ //how to do this dynamically?
                                type: 'push',
                                id: 'button_' + counter,
                                label: articulation,
                                address: '/control',
                                preArgs: '[1, ' + counter + ']',
                                color: articulations[articulation].color,
                                target: 'midi:buttonsOut',
                                norelease: true,
                                css: `:host{
                                        margin:2px;
                                        color: ${articulations[articulation].color}
                                    }`,
                                // etc
                            })
                            counter++
                            break
                        case 'long':
                            buttons_long.push({
                                type: 'push',
                                id: 'button_' + counter,
                                label: articulation,
                                address: '/control',
                                preArgs: '[1, ' + counter + ']',
                                color: articulations[articulation].color,
                                target: 'midi:buttonsOut',
                                norelease: true,
                                css: `:host{
                                        margin:2px;
                                        color: ${articulations[articulation].color}
                                    }`,
                                // etc
                            })
                            counter++
                            break;
                        case 'deco':
                            buttons_deco.push({
                                type: 'push',
                                id: 'button_' + counter,
                                label: articulation,
                                address: '/control',
                                preArgs: '[1, ' + counter + ']',
                                color: articulations[articulation].color,
                                target: 'midi:buttonsOut',
                                norelease: true,
                                css: `:host{
                                          margin:2px;
                                        color: ${articulations[articulation].color}
                                    }`,
                                // etc
                            })
                            counter++
                            break;
                    }
                }
            }

            // replace the container's children with our list of buttons
            receive('/EDIT', 'panel_short', {widgets: buttons_short}) //how to do this dynamically?
            receive('/EDIT', 'panel_long', {widgets: buttons_long})
            receive('/EDIT', 'panel_deco', {widgets: buttons_deco})

    } //end articulation filter

    return {

        oscInFilter:function(data){

        var {address, args, host, port} = data

        // insert our custom filter in the loop
        articulationFilter(address, args)

        return {address, args, host, port}

        }
    }

})()

You could simplify that a bit by having only one container in the interface and generate the 3 panels from the script:

var panels = {}
for (let type of ['short', 'long', 'deco']) {
  panels[type] = {
    type: 'panel',
    label: type,
    widgets: []
  }
}

var type = articulations[articulation].type

panels[type].widgets.push({
  // button properties
})

// Object.values converts the panel object to a an array
receive('/EDIT', 'articulation_panel', {widgets: Object.values(panels)})

Also, if you omit the id property, a new id will be generated automatically.

Fantastic. Thank you.