Data Sharing

Enabling Channels

Channels Component

In io.Connect Desktop, the Channels API is disabled by default. To enable it, set the channels property of the configuration object to True when initializing the io.Connect library. Instantiate the Channels component and pass an id for it:

import dash
import dash_glue42

init_settings = {
    "desktop": {
        "config": {
            "channels": True
        }
    }
}

app = dash.Dash(__name__)

# Initializing the io.Connect library with custom settings.
app.layout = dash_glue42.Glue42(id="io-connect", settings=init_settings, children=[
    # Instantiating the Channels component.
    dash_glue42.Channels(id="io-connect-channels")
])

See the Dash Channels example on GitHub.

Channel Selector

To add the Channel Selector to your window, set "allowChannels" to true in your app definition file under the "details" top-level key:

{
    "title": "Dash App",
    "type": "window",
    "name": "dash-app",
    "details": {
        "url": "http://127.0.0.1:5000/dash-app",
        "mode": "tab",
        "allowChannels": true
    }
}

For more information on configuring your apps, see the Developers > Configuration > Application section.

Discovering Channels

Use the all and list properties of the Channels component to discover all available Channels or retrieve their context data. The values of both properties are assigned by the framework and must not be altered by client code. The all property returns a collection of the names of all available Channels, while the list property returns a collection of objects containing the Channel name, context data and meta data. The JavaScript equivalent of the objects returned by the list property is the ChannelContext object.

The following example demonstrates how to extract and display the names of all available Channels using the list property:

def channels_contexts_to_dpd_options(channelsContexts):
    no_channel = {"label": "No Channel", "value": ""}

    if channelsContexts is not None:
        options = map(lambda channel: {
                      "label": channel.get('name'), "value": channel.get('name')}, channelsContexts)
        return [no_channel] + list(options)
    return [no_channel]

@app.callback(
    Output("channels-list", "options"),
    Input("io-connect-channels", "list")
)
def update_channels_list(contexts):
    return channels_contexts_to_dpd_options(contexts)

io.Connect Desktop provides a Channel Selector which you can add to your app (see Enabling Channels > Channel Selector), but if you are working on an io.Connect Browser project, you will have to create your own Channel Selector.

The all property of the Channels component returns the names of all Channels and you can use it to discover and display all available Channels in the UI of your app. Define an app callback that will be triggered when the property changes in order to update the list of Channels. For Input of the callback pass the ID of the Channels component and its all property. For Output of the callback pass the component in which you want to display the Channels:

@app.callback(
    Output("channels-list", "options"),
    Input("io-connect-channels", "all")
)
def update_channels_list(all_channels):
    return channels_to_dpd_options(all_channels)

The following complete example demonstrates how to discover and display all available io.Connect Channels:

import dash
import dash_html_components as html
import dash_core_components as dcc
import dash_glue42

# Dropdown option that will be used to leave the current Channel.
no_channel = {"label": "No Channel", "value": ""}

app = dash.Dash(__name__)

app.layout = dash_glue42.Glue42(id="io-connect", settings=init_settings, children=[
    dash_glue42.Channels(id="io-connect-channels"),

    # This is an example visual representation. You can use Dash components of your choice.
    html.Div(children=[
        html.Label("Select Channel: "),
        dcc.Dropdown(id="channels-list", clearable=False)
    ]),
])


def channels_to_dpd_options(channels):

    if channels is not None:
        options = map(lambda channel: {
                      "label": channel, "value": channel}, channels)
        return [no_channel] + list(options)

    return [no_channel]


@app.callback(
    Output("channels-list", "options"),
    Input("io-connect-channels", "all")
)
def update_channels_list(all_channels):
    return channels_to_dpd_options(all_channels)

Join & Leave Channels

To join or leave a Channel, define callbacks and use the ID and the join and leave properties of the Channels component for Output. For Input, use the component from which the user will select the Channels.

The following example demonstrates how to handle joining and leaving Channels:

# Joining a Channel.
@app.callback(
    Output("io-connect-channels", "join"),
    Input("channels-list", "value")
)
def join_channel(channel_name):

    if channel_name != no_channel["value"]:
        return {
            "name": channel_name
        }

# Leaving a Channel.
@app.callback(
    Output("io-connect-channels", "leave"),
    Input("channels-list", "value")
)
def leave_channel(channel_name):

    if channel_name == no_channel["value"]:
        return {}

Subscribing for Data

To subscribe for Channel data, use the my property of the Channels component. The value of this property is assigned by the framework and must not be altered by client code. The my property holds an object representing the context of the currently selected Channel. This object corresponds to the JavaScript ChannelContext object, but with an additional extraData property. The extraData object has an updaterId property holding the ID of the updating Interop instance, and an isMyUpdate Boolean property indicating whether the current Interop instance has updated the context. Use this information to avoid potential infinite loops if the subscribed app is also publishing updates to the same Channel context.

Define a callback that will be triggered each time the context object in the my property is updated or the Channel is changed. For Input of the callback pass the ID of the Channels component and its my property. For Output of the callback pass the component and a property you want to update with the Channel data.

The following example demonstrates how to extract and use the updated Channel data. The first Output updates the background color of the "channels-list" component with the color of the current Channel. The second Output displays the current Channel data:

# Handling Channel updates.
@app.callback(
    [
        Output("channels-list", "style"),
        Output("channel-data", "children")
    ],
    Input("io-connect-channels", "my")
)
def channel_changed(channel):

    if channel is None:
        return [None, ""]

    channel_name = channel["name"]
    color = channel.get("meta", {"color": ''})["color"]
    data = channel.get("data", {})
    time_stamp = data.get("time")

    return [
        {"backgroundColor": color},
        "Received time: {}".format(time_stamp) if (
            time_stamp is not None) else "No time currently on Channel {}".format(channel_name)
    ]

Publishing Data

To publish data to the current Channel, use the publish property of the Channels component. Define a callback that will be triggered each time you want your app to publish data to the Channel - e.g., the user selects an item, clicks a button, etc. For Input of the callback pass the ID and a property of the component from which your app will publish data. For Output of the callback pass the ID of the Channels component and its publish property.

The callback must return an object with a data property which holds an object with the Channel context data.

The following example demonstrates how to publish data (a timestamp) to the current Channel when the user clicks a button:

import time

# Publishing data to the current Channel.
@app.callback(
    Output("io-connect-channels", "publish"),
    Input("publish-data", "n_clicks"),
    prevent_initial_call=True
)
def publish_data(_):

    now = time.time()

    return {
        "data": {
            "time": now
        }
    }

To publish data to a Channel that your app isn't currently on, specify the name of the Channel in the returned object:

import time

# Publishing data to a different Channel.
@app.callback(
    Output("io-connect-channels", "publish"),
    Input("publish-data", "n_clicks"),
    prevent_initial_call=True
)
def publish_data(_):

    now = time.time()

    return {
        "name": "Green",
        "data": {
            "time": now
        }
    }