# Dash

Source: https://docs.interop.io/desktop/capabilities/data-sharing/channels/dash/index.html

## Enabling Channels

> ℹ️ *For details on how to enable the Channel Selector for your apps, see the [Channels > Overview > Channel Selector](https://docs.interop.io/desktop/capabilities/data-sharing/channels/overview/index.md#channel_selector) section.*

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:

```python
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](https://github.com/InteropIO/dash-example/tree/master/channels) on GitHub.*

## 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`](https://docs.interop.io/desktop/reference/javascript/channels/channelcontext/index.md) object.

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

```python
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](#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:

```python
@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:

```python
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:

```python
# 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](https://docs.interop.io/desktop/reference/javascript/channels/channelcontext/index.md) 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:

```python
# 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:

```python
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:

```python
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
        }
    }
```
