# JavaScript Tutorial

Source: https://docs.interop.io/desktop/tutorials/javascript/index.html

## Overview

This tutorial is designed to walk you through every aspect of **io.Connect Desktop** - [defining apps](https://docs.interop.io/desktop/developers/configuration/application/index.md), initializing the necessary io.Connect libraries, and extending your apps with [Shared Contexts](https://docs.interop.io/desktop/capabilities/data-sharing/shared-contexts/index.md), [Interop](https://docs.interop.io/desktop/capabilities/data-sharing/interop/index.md), [Window Management](https://docs.interop.io/desktop/capabilities/windows/window-management/index.md), [Channels](https://docs.interop.io/desktop/capabilities/data-sharing/channels/index.md), [App Management](https://docs.interop.io/desktop/capabilities/app-management/index.md), [Workspaces](https://docs.interop.io/desktop/capabilities/windows/workspaces/overview/index.md) and other io.Connect [capabilities](https://docs.interop.io/desktop/capabilities/launcher/index.md).

This guide uses plain JavaScript and its goal is to allow you to put the basic concepts of **io.Connect Desktop** to practice.

After completing the tutorial, you will be able to:

- Create and modify [app definitions](https://docs.interop.io/desktop/developers/configuration/application/index.md) for your apps in order to include them in the io.Connect framework and provide various settings for them.
- Interop-enable your apps by [initializing](https://docs.interop.io/desktop/getting-started/how-to/interop-enable-your-apps/javascript/index.md) and configuring the [`@interopio/desktop`](https://www.npmjs.com/package/@interopio/desktop) library in your apps.
- Use different io.Connect APIs in order to launch apps programmatically, share data and functionality between your apps, create and modify Workspaces, raise notifications, and more.

## Introduction

You are a part of the IT department of a big multi-national bank and you have been tasked to lead the creation of a project which will be used by the Asset Management department of the bank. The project will begin with two apps:

- **Clients** - Displays a full list of clients and details about them.
- **Stocks** - Displays a full list of stocks with prices. When the user clicks on a stock, details about the selected stock should be displayed.

Later on, the plan is to expand the project with more apps in order to take advantage of other io.Connect functionalities and enhance the user experience of the Asset Management team. All apps are being developed by different teams within the organization and therefore are being hosted at different origins.

As an end result, the users want to be able to run the apps in separate windows in order to take advantage of their multi-monitor setups. Also, they want the apps, even though in separate windows, to be able to communicate with each other. For example, when a client is selected in the **Clients** app, the **Stocks** app should display only the stocks of the selected client.

## Prerequisites

You must have the **io.Connect Desktop** trial version or your licensed copy of the platform installed.

This tutorial assumes that you are familiar with the concepts of JavaScript and asynchronous programming.

It's also recommended to have the **io.Connect Desktop** [API Reference](https://docs.interop.io/desktop/reference/javascript/io.connect%20desktop/index.md) documentation available.

Each main chapter demonstrates basic usage of a different io.Connect feature and contains links to the relevant documentation for further details.

## Tutorial Structure

The tutorial code is located in the [`desktop-tutorials`](https://github.com/InteropIO/desktop-tutorials) GitHub repo with the following structure:

```cmd
/javascript
    /solution
    /start
/rest-server
```

| Directory | Description |
|-----------|-------------|
| `/javascript` | Contains the starting files for the tutorial and also a full solution. |
| `/rest-server` | Simple server used in the tutorial to serve the necessary JSON data. |

## 1. Initial Setup

Clone the [`desktop-tutorials`](https://github.com/InteropIO/desktop-tutorials) GitHub repo to get the tutorial files.

### 1.1. Start Files

Next, go to the `/javascript/start` directory which contains the starting files for all apps that will be used in the project. The tutorial examples assume that you will be working in the `/start` directory, but you can also move the files and work from another directory.

The `/start` directory contains the following:

| Directory | Description |
|-----------|-------------|
| `/app-definitions` | Contains JSON file templates for creating io.Connect [definitions](#2_app_definitions) for your apps. |
| `/client-details` | This is the **Client Details** app which will be used later in the tutorial (in the [9. Workspaces](#9_workspaces) chapter) to display detailed information about a selected client. The directory contains the basic files for a web app: an `index.html` file, an `index.js` file, and a `/lib` directory. |
| `/clients` | This is the **Clients** app. The directory contains the basic files for a web app: an `index.html` file, an `index.js` file, and a `/lib` directory. |
| `/layouts` | Contains a JSON file representing a Workspace Layout that will be used in the [9. Workspaces](#9_workspaces) chapter. |
| `/portfolio-downloader` | This is the **Portfolio Downloader** app, which will be used in the [10. Intents](#10_intents) chapter. The directory contains the basic files for a web app: an `index.html` file, an `index.js` file, and a `/lib` directory. |
| `/stocks` | This is the **Stocks** app. The directory contains the basic files for a web app: an `index.html` file, an `index.js` file, and a `/lib` directory. It also contains a **Stock Details** view in the `/stocks/details` directory. |
| `package.json` | Standard `package.json` file. |

All apps are fully functional. To run them, execute the following commands in the `/start` directory:

```cmd
npm install
npm start
```

This will install the necessary dependencies and launch separate servers hosting all apps as follows:

| URL | App |
|-----|-----|
| `http://localhost:9000/` | **Clients** |
| `http://localhost:9100/` | **Stocks** |
| `http://localhost:9200/` | **Client Details** |
| `http://localhost:9300/` | **Portfolio Downloader** |

If you load the app URLs directly in a web browser, you will notice that the "io.Connect is unavailable" badge will be displayed. This is because the apps haven't been defined within the io.Connect framework and the [`@interopio/desktop`](https://www.npmjs.com/package/@interopio/desktop) library hasn't been initialized yet. You will do this in the following chapters of the tutorial.

### 1.2. Solution Files

Before you continue, take a look at the solution files. You are free to use the solution as you like - you can check after each section to see how it solves the problem, or you can use it as a reference point in case you get stuck.

The solution files are located in the `/javascript/solution` directory. The `/solution` directory has the same structure as the `/start` directory described in the [1.1 Start Files](#1_initial_setup-11_start_files) section. In order to allow running the start and the solution files simultaneously, there are several differences in file naming and content between the `/start` and the `/solution` directories:

- The names of the app definition files in the `/solution/app-definitions` directory are suffixed with `-solution` (e.g., `client-solution.json`).
- The names of the solution apps specified under the `"name"` property of the respective app definitions are suffixed with `-solution` (e.g., `"clients-solution"`). The same is valid for all app names used throughout the code of the tutorial solution.
- The titles of the solution apps specified under the `"title"` property of the respective app definitions are suffixed with `(solution)` (e.g., `"Clients (solution)"`) and this is how they will appear in the [io.Connect launcher](https://docs.interop.io/desktop/capabilities/launcher/index.md).
- The solution apps will be grouped in an "Asset Management (solution)" folder in the io.Connect launcher.
- The Workspace Layout file located in the `/solution/layouts` directory and the name of the Workspace Layout itself are suffixed with `(solution)` (`"Client Space (solution)"`) and this is how this Workspace Layout will appear in the Layouts section of the io.Connect launcher.
- The name of the Intent used in the [10. Intents](#10_intents) chapter is suffixed with `(solution)` (`"ExportPortfolio (solution)"`) both in the app definition of the **Portfolio Downloader** app and in the code of the tutorial solution.

Go to the `/rest-server` directory and start the REST Server (as described in the [1.3. REST Server](#1_initial_setup-13_rest_server) chapter).

Go to the `/solution/app-definitions` directory and copy all app definitions. Paste them in the `<installation_location>/interop.io/io.Connect Desktop/UserData/<ENV>-<REG>/apps` folder where `<ENV>-<REG>` represents the [environment and region](https://docs.interop.io/desktop/developers/configuration/system/index.md#environments__regions) of **io.Connect Desktop** (e.g., `DEMO-INTEROP.IO`). Assuming **io.Connect Desktop** is installed at the default location, with the default settings for environment and region, this would be the `%LocalAppData%/interop.io/io.Connect Desktop/UserData/DEMO-INTEROP.IO/apps` folder on Windows.

Go to the `/solution` directory, open a command prompt, and run the following commands:

```cmd
npm install
npm start
```

This will install the necessary dependencies and launch separate servers hosting all apps as follows:

| URL | App |
|-----|-----|
| `http://localhost:9001/` | **Clients** |
| `http://localhost:9101/` | **Stocks** |
| `http://localhost:9201/` | **Client Details** |
| `http://localhost:9301/` | **Portfolio Downloader** |

You can now access the tutorial apps from the io.Connect launcher. All apps will be grouped in an "Asset Management (solution)" folder.

![Chapter 1.2](https://docs.interop.io/desktop/images/tutorials/javascript/chapter-1.2.mp4)

### 1.3. REST Server

Before starting with the project, go to the `/rest-server` directory and start the REST server that will host the necessary data for the apps:

```cmd
npm install
npm start
```

This will launch the server at port 8080.

## 2. App Definitions

io.Connect [app definitions](https://docs.interop.io/desktop/developers/configuration/application/index.md) allow your apps to be included in the io.Connect framework and be accessible from the [io.Connect launcher](https://docs.interop.io/desktop/capabilities/launcher/index.md). App definitions are created in the form of JSON files. Each definition file can contain a single definition object for a single app or an array of definition objects for multiple apps.

The required top-level properties to create a basic app definition are `"name"`, `"type"`, and `"details"`. The `"details"` object has a required `"url"` property which you must use to provide the location of your app. The `"name"` property must be unique within the io.Connect framework. It allows the framework to identify your app among all other interop-enabled apps. The `"type"` property must be set to `"window"` for web apps.

Optionally, you can use the `"title"` top-level property to provide a user-friendly title for an app. The specified title will be used by the io.Connect launcher to display the app in the list of available apps. If no title is specified, the value of the `"name"` property will be displayed instead.

Optionally, you can use the `"folder"` property of the `"customProperties"` top-level key to specify a folder in which to [group the apps](https://docs.interop.io/desktop/developers/configuration/application/index.md#grouping_apps). This is useful if you want to group your apps in the io.Connect launcher by some criterion or for easier access.

Go to the `/app-definitions` folder and use the JSON file templates to create basic definitions for all apps. Use the app URLs specified in the [1. Initial Setup](#1_initial_setup) section. Group the apps in an "Asset Management" folder so that the users may identify them more easily among all other interop-enabled apps in the io.Connect launcher.

- Open the `clients.json` file and create a definition object for the **Clients** app:

```json
{
    "name": "clients",
    "title": "Clients",
    "type": "window",
    "details": {
        "url": "http://localhost:9000/"
    },
    "customProperties": {
        "folder": "Asset Management"
    }
}
```

- Open the `stocks.json` file and create an array of definition objects for the **Stocks** and **Stock Details** apps:

```json
[
    {
        "name": "stocks",
        "title": "Stocks",
        "type": "window",
        "details": {
            "url": "http://localhost:9100/"
        },
        "customProperties": {
            "folder": "Asset Management"
        }
    },
    {
        "name": "stock-details",
        "title": "Stock Details",
        "type": "window",
        "details": {
            "url": "http://localhost:9100/details"
        },
        "customProperties": {
            "folder": "Asset Management"
        }
    }
]
```

- Open the `client-details.json` file and create a definition object for the **Client Details** app:

```json
{
    "name": "client-details",
    "title": "Client Details",
    "type": "window",
    "details": {
        "url": "http://localhost:9200/"
    },
    "customProperties": {
        "folder": "Asset Management"
    }
}
```

- Open the `portfolio-downloader.json` file and create a definition object for the **Portfolio Downloader** app:

```json
{
    "name": "portfolio-downloader",
    "title": "Portfolio Downloader",
    "type": "window",
    "details": {
        "url": "http://localhost:9300/"
    },
    "customProperties": {
        "folder": "Asset Management"
    }
}
```

App definitions can be stored locally or remotely. For the purpose of this tutorial, you will use one of the default local app stores of **io.Connect Desktop**.

Copy all created app definitions from the `/app-definitions` folder and paste them in the `<installation_location>/interop.io/io.Connect Desktop/UserData/<ENV>-<REG>/apps` folder where `<ENV>-<REG>` represents the [environment and region](https://docs.interop.io/desktop/developers/configuration/system/index.md#environments__regions) of **io.Connect Desktop** (e.g., `DEMO-INTEROP.IO`). Assuming **io.Connect Desktop** is installed at the default location, with the default settings for environment and region, this would be the `%LocalAppData%/interop.io/io.Connect Desktop/UserData/DEMO-INTEROP.IO/apps` folder on Windows. All data in the `/UserData` folder is preserved across installations, so it's convenient to store there the local definitions of apps that you don't want to remove when you upgrade or reinstall **io.Connect Desktop**.

Some of the following tutorial chapters will require you to modify the existing app definitions in order to achieve a certain result. App definitions are monitored dynamically, so it isn't necessary to restart **io.Connect Desktop** for the changes to take effect. You may want to keep the app definitions open in your editor for easier access.

You can now start each app from the io.Connect launcher. The "io.Connect is unavailable" badge will be displayed by all apps, because the [`@interopio/desktop`](https://www.npmjs.com/package/@interopio/desktop) library hasn't been initialized yet and the apps don't have access to the io.Connect functionalities.

![Chapter 2](https://docs.interop.io/desktop/images/tutorials/javascript/chapter-2.mp4)

## 3. Library Initialization

The [`@interopio/desktop`](https://www.npmjs.com/package/@interopio/desktop) library enables you to configure and use all available io.Connect functionalities.

> ℹ️ *For descriptions and example usage of the available io.Connect functionalities, see the [Capabilities](https://docs.interop.io/desktop/capabilities/launcher/index.md) section. For a complete reference of the io.Connect APIs, see the [API Reference](https://docs.interop.io/desktop/reference/javascript/io.connect%20desktop/index.md) documentation.*

To be able to use the io.Connect functionalities, you must [reference and initialize](https://docs.interop.io/desktop/getting-started/how-to/interop-enable-your-apps/javascript/index.md) the `@interopio/desktop` library in your apps. This will allow the apps to connect to the io.Connect framework and interoperate with each other.

Open the `index.html` files of all apps, add a new `<script>` tag below the `TODO: Chapter 3` comment and reference the `@interopio/desktop` library from the `/lib` directory:

```html
<script src="/lib/desktop.umd.js"></script>
```

Next, open the `index.js` files of all apps and find the `TODO: Chapter 3` comment inside their `start()` functions. Initialize the `@interopio/desktop` library by using the `IODesktop()` factory function injected in the global `window` object. Assign the returned API object as a property of the global `window` object for easier access:

```javascript
// In `start()`.

const io = await IODesktop();

window.io = io;
```

Find the `toggleIOAvailable()` function marked with a `TODO: Chapter 3` comment and uncomment it. Call it after the `IODesktop()` factory function has resolved:

```javascript
// In `start()`.

const io = await IODesktop();

window.io = io;

toggleIOAvailable();
```

Now all apps will show the "io.Connect available" label after they initialize the `@interopio/desktop` library.

![Chapter 3](https://docs.interop.io/desktop/images/tutorials/javascript/chapter-3.mp4)

Next, you will begin adding io.Connect functionalities to the apps.

## 4. Window Management

The goal of this chapter is to start building the user flow of the entire project. The end users will open the **Clients** app and will be able to open the **Stocks** app from the "Stocks" button in it. Clicking on a stock in the **Stocks** app will open the **Stock Details** app.

Currently, the "Stocks" button doesn't work and the **Stock Details** app is a separate view of the **Stocks** app. The end users have multiple monitors and would like to take advantage of that - they want clicking on a stock to open a new window with the respective stock. The new window for the selected stock must also have specific dimensions and position on the screen. The windows of the **Clients** and the **Stocks** apps must have minimum height and width beyond which the users won't be able to resize them. To achieve all this, you will use both the [Window Management API](https://docs.interop.io/desktop/reference/javascript/windows/api/index.md) and the settings in the app definition files.

> ℹ️ *See also the [Capabilities > Windows > Window Management](https://docs.interop.io/desktop/capabilities/windows/window-management/javascript/index.md) documentation.*

### 4.1. Opening Windows Dynamically

Instruct the **Clients** app to open the **Stocks** app in a new window when the user clicks on the "Stocks" button. Go to the `index.js` file of the **Clients** app and find the `TODO: Chapter 4.1` comment inside the `stocksButtonHandler()` function. Use the [`open()`](https://docs.interop.io/desktop/reference/javascript/windows/api/index.md#API-open) method to open the **Stocks** app in a new window. The `instanceID` and `counter` variables ensure that the name of each new **Stocks** instance will be unique, which is required for io.Connect Windows:

```javascript
const stocksButtonHandler = (client) => {

    // The `name` and `url` parameters are required. The window name must be unique.
    const name = `Stocks-${instanceID || counter}`;
    const URL = "http://localhost:9100/";

    io.windows.open(name, URL).catch(console.error);
};
```

Clicking on the "Stocks" button will now open the **Stocks** app.

To complete the user flow, instruct the **Stocks** app to open a new window each time the user clicks on a stock. Remember that each io.Connect Window must have a unique name. To avoid errors resulting from attempting to open io.Connect Windows with conflicting names, check whether the clicked stock has already been opened in a new window.

Go to the `index.js` file of the **Stocks** app and find the `TODO: Chapter 4.1` comment in the `stockClickedHandler()` function. Currently, it rewrites the value of `window.location.href` to redirect to the **Stock Details** view. Remove that and use the [`open()`](https://docs.interop.io/desktop/reference/javascript/windows/api/index.md#API-open) method instead. Use the [`list()`](https://docs.interop.io/desktop/reference/javascript/windows/api/index.md#API-list) method to get a collection of all io.Connect Windows and check whether the clicked stock is already open in a window. It's safe to search by `name`, because all io.Connect Window instances must have a unique `name` property:

```javascript
const stockClickedHandler = (stock) => {
    const name = `${stock.BPOD} Details`;
    const URL = "http://localhost:9100/details/";

    // Check whether the clicked stock has already been opened in a new window.
    const stockWindowExists = io.windows.list().find(w => w.name === name);

    if (!stockWindowExists) {
        io.windows.open(name, URL).catch(console.error);
    }
};
```

After refreshing, when you click on a stock, a separate **Stock Details** window will be opened.

![Chapter 4.1](https://docs.interop.io/desktop/images/tutorials/javascript/chapter-4.1.mp4)

Currently, all fields in the **Stock Details** app are empty - the **Stocks** app will be instrumented to pass the stock details as window context to the newly opened window instance.

### 4.2. Window Settings

The users want the **Stocks** and the **Stock Details** windows to be opened with specific bounds on the screen. To achieve this, extend the logic in the [`open()`](https://docs.interop.io/desktop/reference/javascript/windows/api/index.md#API-open) method by passing an optional [`WindowCreateOptions`](https://docs.interop.io/desktop/reference/javascript/windows/windowcreateoptions/index.md) object containing specific values for the window size (`width` and `height`) and position (`top` and `left`).

Go to the `stocksButtonHandler()` function in the `index.js` file of the **Clients** app and specify the desired bounds for the **Stocks** window:

```javascript
const stocksButtonHandler = () => {
    const name = `Stocks-${instanceID || counter}`;
    const URL = "http://localhost:9100/";
    // Optional configuration object for the newly opened window.
    // Specify size for the "Stocks" app window.
    const config = {
        width: 500,
        height: 450
    };

    io.windows.open(name, URL, config).catch(console.error);
};
```

Go to the `stockClickedHandler()` in the `index.js` file of the **Stocks** app and specify the desired bounds for the **Stock Details** window:

```javascript
const stockClickedHandler = (stock) => {
    const name = `${stock.BPOD} Details`;
    const URL = "http://localhost:9100/details/";
    // Optional configuration object for the newly opened window.
    // Specify bounds for the "Stock Details" app window.
    const config = {
        left: 100,
        top: 100,
        width: 550,
        height: 350
    };

    const stockWindowExists = io.windows.list().find(w => w.name === name);

    if (!stockWindowExists) {
        io.windows.open(name, URL, config).catch(console.error);
    }
};
```

You have received feedback that some users are frustrated with the fact they can shrink the **Clients** and **Stocks** windows to an extremely small size by resizing them manually. To remedy the situation, you will set the minimum width and height for these apps by using the `"minWidth"` and `"minHeight"` properties of the `"details"` top-level key in the app definitions.

Go to the `clients.json` file and specify minimum width and height in pixels for the **Clients** app:

```json
// In `clients.json`.
{
    "details": {
        "minWidth": 400,
        "minHeight": 400
    }
}
```

Go to the `stocks.json` file and specify minimum width and height for the **Stocks** app:

```json
// In `stocks.json`.
[
    {
        "details": {
            "minWidth": 450,
            "minHeight": 400
        }
    },
    {
        // "Stock Details" app definition.
    }
]
```

The settings for minimum width and height specified in the app definition files will have effect only if the apps are started manually from the io.Connect launcher, because the launcher uses internally the [App Management API](#8_app_management) to start apps. This will work fine for the **Clients** app, but the **Stocks** app can also be started as a floating window via the Window Management API from the "Stocks" button in the **Clients** app. This means that you must also specify minimum width and height in the optional `WindowCreateOptions` object passed to the `open()` method in order to prevent the users from resizing the **Stocks** window beyond the specified minimum bounds:

```javascript
const stocksButtonHandler = () => {
    const name = `Stocks-${instanceID || counter}`;
    const URL = "http://localhost:9100/";
    const config = {
        width: 500,
        height: 450,
        // Minimum width and height for the "Stocks" window.
        minWidth: 450,
        minHeight: 400
    };

    io.windows.open(name, URL, config).catch(console.error);
};
```

Close and open the apps again to test whether you are able to resize them manually beyond the specified minimum bounds.

![Chapter 4.2](https://docs.interop.io/desktop/images/tutorials/javascript/chapter-4.2.mp4)

### 4.3. Window Context

To allow the **Stock Details** app to display information about the selected stock, pass the `stock` object in the **Stocks** app as a context to the newly opened **Stock Details** window. The **Stock Details** window will then access its context and extract the necessary stock information.

Go to the `stockClickedHandler()` in the `index.js` file of the **Stocks** app. Add a `context` property to the `WindowCreateOptions` object and assign the `stock` object as its value:

```javascript
const stockClickedHandler = (stock) => {
    const name = `${stock.BPOD} Details`;
    const URL = "http://localhost:9100/details/";
    const config = {
        left: 100,
        top: 100,
        width: 550,
        height: 350,
        // Set the `stock` object as a context for the new window.
        context: stock
    };

    const stockWindowExists = io.windows.list().find(w => w.name === name);

    if (!stockWindowExists) {
        io.windows.open(name, URL, config).catch(console.error);
    }
};
```

Update the **Stock Details** app to retrieve the `stock` object. Find the `TODO: Chapter 4.3` comment in the `index.js` file of the **Stock Details** app. Get a reference to the current window by using the [`my()`](https://docs.interop.io/desktop/reference/javascript/windows/api/index.md#API-my) method and retrieve its context with the [`getContext()`](https://docs.interop.io/desktop/reference/javascript/windows/ioconnectwindow/index.md#IOConnectWindow-getContext) method of the [`IOConnectWindow`](https://docs.interop.io/desktop/reference/javascript/windows/ioconnectwindow/index.md) object:

```javascript
// In `start()`.

const myWindow = io.windows.my();
const stock = await myWindow.getContext();
```

Now, when you click on a stock in the **Stocks** app, the **Stock Details** app will open in a new window displaying information about the selected stock.

![Chapter 4.3](https://docs.interop.io/desktop/images/tutorials/javascript/chapter-4.3.mp4)

## 5. Interop

Next, you will use the [Interop API](https://docs.interop.io/desktop/reference/javascript/interop/api/index.md) to pass the portfolio of the selected client to the **Stocks** app and show only the stocks present in their portfolio.

> ℹ️ *See also the [Capabilities > Data Sharing > Interop](https://docs.interop.io/desktop/capabilities/data-sharing/interop/javascript/index.md) documentation.*

### 5.1. Registering Interop Methods & Creating Streams

When a user clicks on a client, the **Stocks** app should show only the stocks owned by this client. You can achieve this by registering an Interop method in the **Stocks** app which, when invoked, will receive the portfolio of the selected client and re-render the stocks table. Also, the **Stocks** app will create an Interop stream to which it will push the new stock prices. Subscribers to the stream will get notified when new prices have been generated.

Go to the `index.js` file of the **Stocks** app and find the `TODO: Chapter 5.1` comment in the `start()` function. Use the [`register()`](https://docs.interop.io/desktop/reference/javascript/interop/api/index.md#API-register) method to register an Interop method. Pass to `register()` the name of the method (`"SelectClient"`) and a callback for handling method invocations. The callback will expect as an argument an object with a `client` property, which in turn holds an object with a `portfolio` property. Filter all stocks and pass to the `setupStocks()` function only the ones present in the portfolio of the client.

```javascript
// In `start()`.

// Define a method name and a callback that will handle method invocations.
const methodName = "SelectClient";
const methodHandler = (args) => {
    const clientPortfolio = args.client.portfolio;
    const stockToShow = stocks.filter(stock => clientPortfolio.includes(stock.RIC));

    setupStocks(stockToShow);
};

// Register an Interop method.
io.interop.register(methodName, methodHandler);
```

Interop streams can be described as special Interop methods. Use the [`createStream()`](https://docs.interop.io/desktop/reference/javascript/interop/api/index.md#API-createStream) method to create a stream called `"LivePrices"` and assign it to the global `window` object for easier access:

```javascript
// In `start()`.

const methodName = "SelectClient";
const methodHandler = (args) => {
    const clientPortfolio = args.client.portfolio;
    const stockToShow = stocks.filter(stock => clientPortfolio.includes(stock.RIC));

    setupStocks(stockToShow);
};

io.interop.register(methodName, methodHandler);

// Create an Interop stream.
window.priceStream = await io.interop.createStream("LivePrices");
```

Finally, go to the `newPricesHandler()` function and find the `TODO: Chapter 5.1` comment in it. This function is invoked every time new prices are generated. Push the updated prices to the stream if it exists:

```javascript
// Update the `newPricesHandler()` to push the new prices to the stream.
const newPricesHandler = (priceUpdate) => {
    priceUpdate.stocks.forEach((stock) => {
        const row = document.querySelectorAll(`[data-ric='${stock.RIC}']`)[0];

        if (!row) {
            return;
        }

        const bidElement = row.children[2];
        bidElement.innerText = stock.Bid;

        const askElement = row.children[3];
        askElement.innerText = stock.Ask;
    });

    // Check whether the stream exists and push the new prices to it.
    if (priceStream) {
        priceStream.push(priceUpdate);
    }
};
```

Next, you will find and invoke the registered Interop method from the **Clients** app.

### 5.2. Method Discovery

Go to the `index.js` file of the **Clients** app, find the `TODO: Chapter 5.2.` comment, and extend the `clientClickedHandler()`. This function is invoked every time the user clicks on a client. Use the [`methods()`](https://docs.interop.io/desktop/reference/javascript/interop/api/index.md#API-methods) method to check for a registered Interop method with the name `"SelectClient"`:

```javascript
// In `clientClickedHandler()`.

// Get a list of all registered Interop methods and filter them by name.
const selectClientStocks = io.interop.methods().find(method => method.name === "SelectClient");
```

### 5.3. Method Invocation

Next, invoke the Interop method if it has been registered.

Find the `TODO: Chapter 5.3.` comment in the `clientClickedHandler()` function and invoke the method if it has been registered. Use the [`invoke()`](https://docs.interop.io/desktop/reference/javascript/interop/api/index.md#API-invoke) method and pass the previously found method object to it as a first argument. Wrap the `client` object received by the `clientClickedHandler()` in another object and pass it as a second argument to [`invoke()`](https://docs.interop.io/desktop/reference/javascript/interop/api/index.md#API-invoke):

```javascript
// In `clientClickedHandler()`.

// Check if the method exists and invoke it.
if (selectClientStocks) {
    io.interop.invoke(selectClientStocks, { client });
}
```

The updated handler should now look like this:

```javascript
const clientClickedHandler = (client) => {
    const selectClientStocks = io.interop.methods().find((method) => method.name === "SelectClient");

    if (selectClientStocks) {
        io.interop.invoke(selectClientStocks, { client });
    }
};
```

Now, when you click on a client in the **Clients** app, the **Stocks** app will display only the stocks that are in the portfolio of the selected client.

![Chapter 5.3](https://docs.interop.io/desktop/images/tutorials/javascript/chapter-5.3.mp4)

### 5.4. Stream Subscription

Use the [Interop API](https://docs.interop.io/desktop/reference/javascript/interop/api/index.md) to subscribe the **Stock Details** app to the previously created Interop stream.

Go to the `index.js` file of the **Stock Details** app and find the `TODO: Chapter 5.4` comment in the `start()` function. Use the [`subscribe()`](https://docs.interop.io/desktop/reference/javascript/interop/api/index.md#API-subscribe) method to subscribe to the `"LivePrices"` stream and use the [`onData()`](https://docs.interop.io/desktop/reference/javascript/interop/subscription/index.md#Subscription-onData) method of the returned [`Subscription`](https://docs.interop.io/desktop/reference/javascript/interop/subscription/index.md) object to assign a handler for the received stream data:

```javascript
// In `start()`.

// Create a stream subscription.
const subscription = await io.interop.subscribe("LivePrices");

// Define a handler for the received stream data.
const streamDataHandler = (streamData) => {
    const updatedStocks = streamData.data.stocks;
    const selectedStockPrice = updatedStocks.find(updatedStock => updatedStock.RIC === stock.RIC);

    updateStockPrices(selectedStockPrice.Bid, selectedStockPrice.Ask);
};

// Handle the received stream data.
subscription.onData(streamDataHandler);
```

Now, the **Stock Details** app will show live prices for the selected stock.

![Chapter 5.4](https://docs.interop.io/desktop/images/tutorials/javascript/chapter-5.4.mp4)

> ⚠️ *Note that each new instance of the **Stocks** app will create a new stream instance. In real-life scenarios, this should be handled differently - e.g., by a [service window](https://docs.interop.io/desktop/capabilities/more/features/index.md#service_windows) acting as a designated data provider.*

## 6. Shared Contexts

The next request of the users is to be able to see in the **Stock Details** app whether the selected client has the selected stock in their portfolio. This time you will use the [Shared Contexts API](https://docs.interop.io/desktop/reference/javascript/shared%20contexts/api/index.md) to connect the **Clients**, **Stocks** and **Stock Details** apps by using shared context objects.

> ℹ️ *See also the [Capabilities > Data Sharing > Shared Contexts](https://docs.interop.io/desktop/capabilities/data-sharing/shared-contexts/javascript/index.md) documentation.*

### 6.1. Updating Contexts

Go to the `index.js` file of the **Clients** app and find the `TODO: Chapter 6.1.` comment in the `clientClickedHandler()` function. Comment out or delete the existing code that uses the Interop API. Use the [`update()`](https://docs.interop.io/desktop/reference/javascript/shared%20contexts/api/index.md#API-update) method to create and set a shared context object by providing a name for the context and a value that will hold the selected client. Other apps will be able to subscribe for updates to this context and be notified when its value changes:

```javascript
const clientClickedHandler = (client) => {
    // The `update()` method updates the value of a specified context object.
    // If the specified context doesn't exist, it will be created.
    io.contexts.update("SelectedClient", client).catch(console.error);
};
```

### 6.2. Subscribing for Context Updates

Next, go to the `index.js` file of the **Stocks** app and find the `TODO: Chapter 6.2` comment in the `start()` function. Comment out or delete the code that uses the Interop API to register the `"SelectClient"` method (but leave the code that registers the `"LivePrices"` stream). Use the [`subscribe()`](https://docs.interop.io/desktop/reference/javascript/shared%20contexts/api/index.md#API-subscribe) method to subscribe for updates to the `"SelectedClient"` context object:

```javascript
// In `start()`.

// Define a function that will handle the context updates.
const updateHandler = (client) => {
    const clientPortfolio = client.portfolio;
    const stockToShow = stocks.filter(stock => clientPortfolio.includes(stock.RIC));

    setupStocks(stockToShow);
};

// Subscribe for updates to the context.
io.contexts.subscribe("SelectedClient", updateHandler);
```

Go to the `index.html` file of the **Stock Details** app, find the `TODO: Chapter 6.2` comment, and uncomment the `<div>` element that will hold the client status.

Go to the `index.js` file of the **Stock Details** app, find the `TODO: Chapter 6.2` comment, and uncomment the `updateClientStatus()` function. Find the `TODO: Chapter 6.2` comment in the `start()` function  and subscribe for the `"SelectedClient"` context. Invoke the `updateClientStatus()` function and pass the selected client and stock to it:

```javascript
// In `start()`.

// Define a function that will handle the context updates.
const updateHandler = (client) => {
    updateClientStatus(client, stock);
};

// Subscribe for updates to the context.
io.contexts.subscribe("SelectedClient", updateHandler);
```

Now, the **Stocks** app will show only the stocks of the selected client and the **Stock Details** app will show whether the client selected in the **Clients** app has the displayed stock in their portfolio.

![Chapter 6.2](https://docs.interop.io/desktop/images/tutorials/javascript/chapter-6.2.mp4)

The `"SelectedClient"` context object shares the lifetime of the io.Connect platform. This means that each time the **Stocks** app is closed and opened again while the platform is running, it will subscribe to the already existing shared context and display the stocks of the most recently selected client in the **Clients** app. If you restart the platform and open the **Stocks** app before selecting a client in the **Clients** app, it will display all available stocks, because the shared context object hasn't been created yet. The same applies to the **Stock Details** app - it will show the status of the most recently selected client unless the platform is restarted and the user opens the **Stock Details** app before selecting a client in the **Clients** app.

## 7. Channels

The latest requirement from the users is to be able to work with multiple clients simultaneously by having multiple instances of the **Stocks** app show the portfolios of different clients. Currently, no matter how many instances of the **Stocks** app are running, they are all listening for updates to the same context and therefore all show information about the same selected client.

In this chapter, you will use the [Channels API](https://docs.interop.io/desktop/reference/javascript/channels/api/index.md) to allow each instance of the **Stocks** app to subscribe for updates to the context of a different Channel. The different Channels are color-coded and the user will be able to select a Channel from the [Channel Selector](https://docs.interop.io/desktop/capabilities/data-sharing/channels/overview/index.md#channel_selector). The **Clients** app will update the context of the currently selected Channel when the user clicks on a client.

> ℹ️ *See also the [Capabilities > Data Sharing > Channels](https://docs.interop.io/desktop/capabilities/data-sharing/channels/javascript/index.md) documentation.*

### 7.1. Enabling Channels

The Channel Selector and the Channels API are disabled by default.

To enable the Channel Selector for the **Clients** and the **Stocks** apps, go to their app definition files and set to `true` the `"enabled"` property of the `"channelSelector"` object under the `"details"` top-level key:

```json
// In the app definitions of the "Clients" and "Stocks" apps.
{
    "details": {
        "channelSelector": {
            "enabled": true
        }
    }
}
```

This setting will enable the Channel Selector for the apps when the user starts them from the io.Connect launcher. To enable the Channel Selector for the **Stocks** app when opening it as a window via the "Stocks" button, you have to modify the [`WindowCreateOptions`](https://docs.interop.io/desktop/reference/javascript/windows/windowcreateoptions/index.md) object in the `stocksButtonHandler()` function in the `index.js` file of the **Clients** app:

```javascript
const stocksButtonHandler = () => {

    const name = `Stocks-${instanceID || counter}`;
    const URL = "http://localhost:9100/";
    const config = {
        width: 500,
        height: 450,
        // Enabling the Channel Selector for the "Stocks" app when opening it as a window.
        channelSelector: {
            enabled: true
        }
    };

    io.windows.open(name, URL, config).catch(console.error);
};
```

To enable the Channels API for the **Clients** and the **Stocks** apps, go to the `start()` functions in their respective `index.js` files. The `IODesktop()` factory function accepts as an optional argument a [`Config`](https://docs.interop.io/desktop/reference/javascript/io.connect%20desktop/config/index.md) object which you will use to enable the Channels API.

Define a configuration object, set its `channels` property to `true`, and pass it to the `IODesktop()` factory function:

```javascript
// In `start()` of the "Clients" and the "Stocks" apps.

// Optional configuration for initializing the `@interopio/desktop` library.
const config = {
    channels: true
};

const io = await IODesktop(config);
```

### 7.2. Publishing & Subscribing

Next, you will enable the **Clients** app to publish updates to the current Channel context and the **Stocks** app to subscribe for these updates.

Go to the `index.js` file of the **Clients** app and find the `TODO: Chapter 7.2.` comment in the `clientClickedHandler()` function. Use the [`publish()`](https://docs.interop.io/desktop/reference/javascript/channels/api/index.md#API-publish) method and pass the selected client as an argument to update the Channel context when a new client is selected. The `publish()` method will throw an error if the app tries to publish data but isn't on a Channel. Use the [`my()`](https://docs.interop.io/desktop/reference/javascript/channels/api/index.md#API-my) method to check for the current Channel:

```javascript
// In `clientClickedHandler()`.

const currentChannel = io.channels.my();

if (currentChannel) {
    io.channels.publish(client).catch(console.error);
}
```

Next, go to the `index.js` file of the **Stocks** app and comment out or delete the code in the `start()` function that uses the Shared Contexts API to listen for updates of the `"SelectedClient"` context. Find the `TODO: Chapter 7.2` comment and use the [`subscribe()`](https://docs.interop.io/desktop/reference/javascript/channels/api/index.md#API-subscribe) method instead to enable the **Stocks** app to listen for updates of the current Channel context. Provide the same callback you used in Chapter 5.2. to handle context updates, but modify it to check for the client portfolio. This is necessary in order to avoid errors if the user decides to change the Channel of the **Stocks** app manually - the context of the new Channel will most likely be an empty object, which will lead to `undefined` values:

```javascript
// In `start()`.

const updateHandler = (client) => {
    if (client.portfolio) {
        const clientPortfolio = client.portfolio;
        const stockToShow = stocks.filter(stock => clientPortfolio.includes(stock.RIC));

        setupStocks(stockToShow);
    }
};

io.channels.subscribe(updateHandler);
```

Now, when the **Clients** and the **Stocks** apps are on the same Channel, the **Stocks** app will be updated with the portfolio of the selected client.

![Chapter 7.2](https://docs.interop.io/desktop/images/tutorials/javascript/chapter-7.2.mp4)

## 8. App Management

Up until now, you had to use the Window Management API to open new windows when the user clicks on the "Stocks" button in the **Clients** app or on a stock in the **Stocks** app. This works fine for small projects, but doesn't scale well for larger ones, because this way each app must know all details (URL, start position, initial context, etc.) of every app it starts.

In this chapter, you will replace the Window Management API with the [App Management API](https://docs.interop.io/desktop/reference/javascript/app%20management/api/index.md) which will allow you to decouple the **Clients** app from the **Stocks** app and the **Stocks** app from the **Stock Details** app. You will need only the names of the apps to be able to start them.

> ℹ️ *See also the [Capabilities > App Management](https://docs.interop.io/desktop/capabilities/app-management/javascript-legacy/index.md) documentation.*

### 8.1. Starting Apps

Go the the `index.js` file of the **Clients** app and comment out or delete the code in the `stocksButtonHandler()` that uses the Window Management API (including the code related to the `counter` and `instanceID` variables, as it won't be necessary to create unique window names).

Find the `TODO: Chapter 8.1` comment and retrieve the [`Application`](https://docs.interop.io/desktop/reference/javascript/app%20management/application/index.md) object of the **Stocks** app by using the [`application()`](https://docs.interop.io/desktop/reference/javascript/app%20management/api/index.md#API-application) method and passing the name of the **Stocks** app as an argument. Use the [`start()`](https://docs.interop.io/desktop/reference/javascript/app%20management/application/index.md#Application-start) method of the `Application` object to start the **Stocks** app when the user clicks the "Stocks" button:

```javascript
// In `stocksButtonHandler()`.

const stocksApp = io.appManager.application("stocks");

stocksApp.start().catch(console.error);
```

### 8.2. Starting Context & Options

Some users noticed that now the bounds of the **Stocks** app (size and position on the screen) are automatically saved and restored when the app is restarted. This is due to the fact that you are no longer using the Window Management API to open the **Stocks** app as a floating window. When using the App Management API to start app instances, the io.Connect platform by default saves the last known size and position of the app instance on the screen. The users don't like that and want each time they start the **Stocks** app, its window to appear at the top left corner of the screen and to have specific size as before.

The users liked the Channels functionality you introduced recently and now also want the **Stocks** app to join automatically the same Channel as the **Clients** app when they click the "Stocks" button.

To achieve both goals, you will specify starting context for the **Stocks** app and modify its app definition file.

Go to the `stocksButtonHandler()` in the `index.js` file of the **Clients** app and extend the logic for starting the **Stocks** app. Retrieve the current Channel and define starting context for the **Stocks** app. Pass the context as an argument to the `start()` method:

```javascript
// In `stocksButtonHandler()`.

const stocksApp = io.appManager.application("stocks");
// Retrieving the current Channel.
const currentChannel = io.channels.my();
// Starting context for the "Stocks" app.
const context = { channel: currentChannel };

stocksApp.start(context).catch(console.error);
```

Next, go to the `stocks.json` file and specify a size for the **Stocks** app and instruct the io.Connect platform to ignore its last saved bounds:

```json
// In `stocks.json`.
[
    {
        // Ignore the last saved app bounds for the "Stocks" app.
        "ignoreSavedLayout": true,
        "details": {
            // Specify initial size for the "Stocks" app.
            "width": 500,
            "height": 450
        }
    },
    {
        // "Stock Details" app definition.
    }
]
```

Next, go to the `start()` function in the `index.js` file of the **Stocks** app and [configure the App Management API](https://docs.interop.io/desktop/capabilities/app-management/javascript-legacy/index.md#configuration) to run in `"full"` mode by modifying the optional [`Config`](https://docs.interop.io/desktop/reference/javascript/io.connect%20desktop/config/index.md) object for initializing the `@interopio/desktop` library. This is necessary so that you will be able to retrieve information about the current app [`Instance`](https://docs.interop.io/desktop/reference/javascript/app%20management/instance/index.md) via the `myInstance` property of the App Management API later:

```javascript
// In `start()`.

// Enabling `"full"` mode for the App Management API.
const config = {
    channels: true,
    appManager: "full"
};

const io = await IODesktop(config);
```

Find the `TODO: Chapter 8.2` comment in the `start()` function of the **Stocks** app, retrieve the current app instance, and use the `getContext()` method of the `Instance` object to retrieve the starting context of the **Stocks** app. Extract the name of the Channel to join and join the **Stocks** app to it:

```javascript
// In `start()`

const appContext = await io.appManager.myInstance.getContext();
const channelToJoin = appContext.channel;

if (channelToJoin) {
    await io.channels.join(channelToJoin);
}
```

Now, when you click on the "Stocks" button in the **Clients** app, the **Stocks** app will be opened via the App Management API and it will join automatically the current Channel of the **Clients** app.

![Chapter 8.2](https://docs.interop.io/desktop/images/tutorials/javascript/chapter-8.2.mp4)

### 8.3. App Instances

Next, you will instruct the **Stocks** app to open the **Stock Details** app via the App Management API. As it's expected the users to open the **Stock Details** app primarily by clicking on a stock in the **Stocks** app and not manually from the io.Connect launcher, you will now use an [`ApplicationStartOptions`](https://docs.interop.io/desktop/reference/javascript/app%20management/applicationstartoptions/index.md) object to specify settings for the bounds of the **Stock Details** app instead of its app definition file.

Go to the `index.js` file of the **Stocks** app and find the `TODO: Chapter 8.3` comment in the `stockClickedHandler()`. Comment out or delete the code that uses the Window Management API to open the **Stock Details** app. Use the `application()` method to get the **Stock Details** app. Check whether an instance with the selected stock has already been started by iterating over the contexts of the existing **Stock Details** instances. If there isn't a running instance with the selected stock, define an `ApplicationStartOptions` object with settings for the **Stock Details** app and call the `start()` method of the `Application` object. Pass the selected stock as a first argument to the `start()` method and the starting options as a second argument:

```javascript
// In `stockClickedHandler()`.

const detailsApp = io.appManager.application("stock-details");

// Check whether an instance with the selected stock is already running.
const contexts = await Promise.all(
    // Use the `instances` property to get all running app instances.
    detailsApp.instances.map(instance => instance.getContext())
);
const isRunning = contexts.find(context => context.RIC === stock.RIC);

if (!isRunning) {
    // Starting options for the "Stock Details" app.
    const options = {
        // Ignore the last saved app bounds for the "Stock Details" app.
        ignoreSavedLayout: true,
        left: 100,
        top: 100,
        width: 550,
        height: 350
    };

    // Start the app and pass the `stock` as context.
    detailsApp.start(stock, options).catch(console.error);
}
```

Next, go to the `start()` function in the `index.js` file of the **Stock Details** app and configure the App Management API to run in `"full"` mode by defining an optional `Config` object for initializing the `@interopio/desktop` library:

```javascript
// In `start()`.

// Enabling `"full"` mode for the App Management API.
const config = {
    appManager: "full"
};

const io = await IODesktop(config);
```

Find the `TODO: Chapter 8.3` comment and comment out or delete the code that uses the window context to get the stock object and use the App Management API instead to get the context of the current app instance:

```javascript
// In `start()`.

const stock = await io.appManager.myInstance.getContext();
```

Everything works as before, the difference being that the apps now use the App Management API instead of the Window Management API.

## 9. Workspaces

The latest feedback from the users is that their desktops become cluttered very quickly with multiple floating windows. You will solve this problem by using the io.Connect [Workspaces](https://docs.interop.io/desktop/capabilities/windows/workspaces/overview/index.md).

The new requirement is that when a user clicks on a client in the **Clients** app, a new Workspace is to open displaying detailed information about the selected client in one app and their stock portfolio in another. When the user clicks on a stock, a third app is to appear in the same Workspace displaying more details about the selected stock. You will use the **Client Details** app for displaying information about the selected client.

Go to the `index.html` and `index.js` files of the **Clients** app and comment out or delete the "Stocks" button, the `stocksButtonHandler()`, and all references to the "Stocks" button in the `start()` function. Go to the `clients.json` and `stocks.json` app definitions and remove the `"channelSelector"` object or disable the Channel Selector by setting its `"enabled"` property to `false`.

Remove all logic and references related to Channels from the **Clients** and **Stocks** apps that were introduced in previous chapters. Instead, you will use Workspaces to allow the users to work with multiple clients at once and organize their desktops at the same time. Channels and Workspaces can, of course, be used together to provide extremely enhanced user experience, but in order to focus entirely on working with Workspaces and the [Workspaces API](https://docs.interop.io/desktop/reference/javascript/workspaces/api/index.md), the Channels functionality will be ignored.

> ℹ️ *See also the [Capabilities > Windows > Workspaces](https://docs.interop.io/desktop/capabilities/windows/workspaces/javascript/index.md) documentation.*

### 9.1. Setup

All Workspaces are contained in a specialized standalone web app called [Workspaces App](https://docs.interop.io/desktop/capabilities/windows/workspaces/overview/index.md#workspaces_concepts-workspaces_app). It's outside the scope of this tutorial to cover building and customizing this app, so you will use the default Workspaces UI app that comes with **io.Connect Desktop**.

Go to the `/layouts` directory and copy the `Client Space.json` file. This file represents a [Workspace Layout](https://docs.interop.io/desktop/capabilities/windows/workspaces/overview/index.md#workspaces_concepts-workspace_layout) named "Client Space" that the **Clients** app will use as a blueprint for restoring a Workspace when the user clicks on a client. A Workspace Layout describes the apps participating in the Workspace and their arrangement.

In a real-life scenario, Workspace Layouts, similarly to app definitions and Global Layouts, will most likely be fetched from a web service, but for the purpose of this tutorial, you will use the default local Layout store of **io.Connect Desktop**. Paste the `Client Space.json` file in the `<installation_location>/interop.io/io.Connect Desktop/UserData/<ENV>-<REG>/layouts/Workspace` folder where `<ENV>-<REG>` represents the [environment and region](https://docs.interop.io/desktop/developers/configuration/system/index.md#environments__regions) of **io.Connect Desktop** (e.g., `DEMO-INTEROP.IO`). Assuming **io.Connect Desktop** is installed at the default location, with the default settings for environment and region, this would be the `%LocalAppData%/interop.io/io.Connect Desktop/UserData/DEMO-INTEROP.IO/layouts/Workspace` folder on Windows.

Now, the Workspace Layout can be restored by name via the [Workspaces API](https://docs.interop.io/desktop/reference/javascript/workspaces/api/index.md).

The users also want to be able to add manually the **Clients**, **Stocks**, and **Client Details** apps to a Workspace via the "Add Application" Workspace menu so that they can create and save their own custom Workspace Layouts. To allow these apps to be visible in the "Add Application" menu of a Workspace, go to the respective app definitions and set the `"includeInWorkspaces"` property of the `"customProperties"` top-level key to `true`:

```json
// In the app definitions of the "Clients", "Stocks", and "Client Details" apps.
{
    "customProperties": {
        "folder": "Asset Management",
        "includeInWorkspaces": true
    }
}
```

The apps will now appear in the "Add Application" Workspace menu and the users will be able to add them manually to a Workspace.

![Chapter 9.1](https://docs.interop.io/desktop/images/tutorials/javascript/chapter-9.1.mp4)

### 9.2. Initializing Workspaces

To be able to use Workspaces functionalities, initialize the Workspaces API in the **Clients**, **Client Details**, and **Stocks** apps. The **Stock Details** app will participate in the Workspace, but won't use any Workspaces functionality.

Find the `TODO: Chapter 9.3` comment in the `index.html` files of the **Clients**, **Stocks**, and **Client Details** apps and reference the Workspaces library:

```html
<script src="/lib/workspaces.umd.js"></script>
```

The Workspaces script attaches the `IOWorkspaces()` factory function to the global `window` object. Go to the `index.js` files of the **Clients**, **Client Details**, and **Stocks** apps and modify the optional configuration object for initializing the `@interopio/desktop` library. Use the `libraries` array to pass a reference to the `IOWorkspaces()` factory function in order to initialize the Workspaces API in your apps:

```javascript
// In `start()` of the "Clients", "Client Details", and "Stocks" apps.

const config = {
    // Pass the Workspaces factory function.
    libraries: [IOWorkspaces]
};

const io = await IODesktop(config);

window.io = io;
```

### 9.3. Opening Workspaces

Next, implement opening a new Workspace when the user clicks on a client in the **Clients** app.

Find the `TODO: Chapter 9.3` comment in the `clientClickedHandler()` function in the `index.js` file of the **Clients** app. Restore by name the "Client Space" Workspace Layout and pass the selected client as a starting context for the Workspace. The specified context will be attached as window context to all windows participating in the Workspace:

```javascript
const clientClickedHandler = async (client) => {
    const restoreConfig = {
        context: { client }
    };

    try {
        const workspace = await io.workspaces.restoreWorkspace("Client Space", restoreConfig);
    } catch (error) {
        console.error(error.message);
    }
};
```

If everything is correct, a new Workspace will now open every time you click on a client.

![Chapter 9.3](https://docs.interop.io/desktop/images/tutorials/javascript/chapter-9.3.mp4)

### 9.4. Starting Context

Handle the starting Workspace context to show the details and the portfolio of the selected client in the **Client Details** and **Stocks** apps. Also, set the Workspace title to the name of the selected client.

Go to the `index.js` file of the **Client Details** app and find the `TODO: Chapter 9.4` comment in the `start()` function. Use the [`getMyWorkspace()`](https://docs.interop.io/desktop/reference/javascript/workspaces/api/index.md#API-getMyWorkspace) method to retrieve the [`Workspace`](https://docs.interop.io/desktop/reference/javascript/workspaces/workspace/index.md) object of the current Workspace. Use the [`onContextUpdated()`](https://docs.interop.io/desktop/reference/javascript/workspaces/workspace/index.md#Workspace-onContextUpdated) method of the `Workspace` object to subscribe for context updates. Invoke the `setFields()` function and pass as an argument the value of the `client` property of the Workspace context. Set the title of the Workspace to the name of the selected client:

```javascript
// In `start()`.

const myWorkspace = await io.workspaces.getMyWorkspace();

if (myWorkspace) {
    const updateHandler = (context) => {
        if (context.client) {
            setFields(context.client);
            myWorkspace.setTitle(context.client.name);
        }
    };

    myWorkspace.onContextUpdated(updateHandler);
}
```

Go to the `index.js` file of the **Stocks** app and find the `TODO: Chapter 9.4` comment. Use the `getMyWorkspace()` method to retrieve the `Workspace` object of the current Workspace. Use the `onContextUpdated()` method of the `Workspace` object to subscribe for context updates. Extract the selected client from the Workspace context and set up the stocks. Store the stocks in the `clientPortfolioStocks` and the client name in the `clientName` global variables, which will be used later:

```javascript
// In `start()`.

const myWorkspace = await io.workspaces.getMyWorkspace();

if (myWorkspace) {
    const updateHandler = (context) => {
        if (context.client) {
            const clientPortfolio = context.client.portfolio;
            clientPortfolioStocks = stocks.filter((stock) => clientPortfolio.includes(stock.RIC));
            clientName = context.client.name;

            setupStocks(clientPortfolioStocks);
        }
    };

    myWorkspace.onContextUpdated(updateHandler);
}
```

Now, when you select a client in the **Clients** app, a new Workspace will open with the **Client Details** and **Stocks** apps showing the relevant client information.

![Chapter 9.4](https://docs.interop.io/desktop/images/tutorials/javascript/chapter-9.4.mp4)

### 9.5. Modifying Workspaces

Next, you have to make the **Stock Details** app appear in the same Workspace as a sibling of the **Stocks** app when the user clicks on a stock. You have to check whether the **Stock Details** app has already been added to the Workspace, and if not - add it and update its context with the selected stock, otherwise - only update its context.

*To achieve this functionality, you will have to manipulate a Workspace and its elements. It's recommended that you familiarize yourself with the Workspaces terminology to fully understand the following concepts and steps. Use the available documentation about [Workspaces Concepts](https://docs.interop.io/desktop/capabilities/windows/workspaces/overview/index.md#workspaces_concepts), [Workspace Box Elements](https://docs.interop.io/desktop/capabilities/windows/workspaces/javascript/index.md#workspace-finding_workspace_elements-box_elements), and the [Workspaces API](https://docs.interop.io/desktop/reference/javascript/workspaces/api/index.md).*

The **Stocks** app is a [`WorkspaceWindow`](https://docs.interop.io/desktop/reference/javascript/workspaces/workspacewindow/index.md) that is the only child of a [`Group`](https://docs.interop.io/desktop/reference/javascript/workspaces/group/index.md) element. If you add the **Stock Details** app as a child to that `Group`, it will be added as a second tab window and the user will have to manually switch between both apps. The **Stock Details** app has to be a sibling of the **Stocks** app, but both apps have to be visible within the same parent element. That's why, you have to add a new `Group` element as a sibling of the existing `Group` that contains the **Stocks** app and then load the **Stock Details** app in it.

After the **Stocks Details** app has been opened in the Workspace as a `WorkspaceWindow`, you have to pass the selected stock as its context. To do that, get a reference to the underlying [`IOConnectWindow`](https://docs.interop.io/desktop/reference/javascript/windows/ioconnectwindow/index.md) object by using the [`getGdWindow()`](https://docs.interop.io/desktop/reference/javascript/workspaces/workspacewindow/index.md#WorkspaceWindow-getGdWindow) method of the `WorkspaceWindow` object. Use the [`updateContext()`](https://docs.interop.io/desktop/reference/javascript/windows/ioconnectwindow/index.md#IOConnectWindow-updateContext) method of the `IOConnectWindow` object to update the context of the **Stock Details** window.

Go to the `stockClickedHandler()` function in the `index.js` file of the **Stocks** app and find the `TODO: Chapter 9.5` comment. Comment out or delete the code for starting the **Stock Details** app with the App Management API and add the following:

```javascript
// In `stockClickedHandler()`.

// Reference to the `IOConnectWindow` object of the Stock Details instance.
let detailsWindow;

const myWorkspace = await io.workspaces.getMyWorkspace();

// Reference to the `WorkspaceWindow` object of the Stock Details instance.
let detailsWorkspaceWindow = myWorkspace.getWindow(window => window.appName === "stock-details");

// Check whether Stock Details has already been opened.
if (detailsWorkspaceWindow) {
    detailsWindow = detailsWorkspaceWindow.getGdWindow();
} else {
    // Reference to the current window.
    const myId = io.windows.my().id;
    // Reference to the immediate parent element of the Stocks window.
    const myImmediateParent = myWorkspace.getWindow(window => window.id === myId).parent;
    // Add a `Group` element as a sibling of the immediate parent of the Stocks window.
    const group = await myImmediateParent.parent.addGroup();

    // Open the Stock Details window in the newly created `Group` element.
    detailsWorkspaceWindow = await group.addWindow({ appName: "stock-details" });

    await detailsWorkspaceWindow.forceLoad();

    detailsWindow = detailsWorkspaceWindow.getGdWindow();
}

// Update the window context with the selected stock.
detailsWindow.updateContext({ stock });
```

> ⚠️ *Note that [`forceLoad()`](https://docs.interop.io/desktop/reference/javascript/workspaces/workspacewindow/index.md#WorkspaceWindow-forceLoad) is used to make sure that the **Stock Details** app is loaded and an `IOConnectWindow` instance is available. This is necessary, because [`addWindow()`](https://docs.interop.io/desktop/reference/javascript/workspaces/group/index.md#Group-addWindow) adds a new window to the [`Group`](https://docs.interop.io/desktop/reference/javascript/workspaces/group/index.md) (meaning that it exists as an element in the Workspace), but it doesn't guarantee that the content has loaded.*

Next, go to the `index.js` file of the **Stock Details** app and find the `TODO: Chapter 9.5` comment in the `start()` function. Comment out or delete the existing code for listening for shared context updates (also comment out or delete the code for updating the shared context in the **Clients** app). Comment out or delete the existing code for setting the stock details and handling stream subscription updates and replace it with the following:

```javascript
// In `start()`.

// Reference to the current window.
const myWindow = io.windows.my();
// Retrieving the window context.
const context = await myWindow.getContext();
let selectedStock;

// Retrieve the selected stock from the window context and set the stock details.
if (context && context.stock) {
    selectedStock = context.stock;

    setFields(selectedStock);
}

// Subscribe for context updates.
const updateHandler = (context) => {
    if (context.stock) {
        selectedStock = context.stock;

        setFields(selectedStock);
    }
};

myWindow.onContextUpdated(updateHandler);

const subscription = await io.interop.subscribe("LivePrices");

const streamDataHandler = (streamData) => {
    // Check whether a stock has been selected.
    if (!selectedStock) {
        return;
    }

    const updatedStocks = streamData.data.stocks;
    const selectedStockPrice = updatedStocks.find(updatedStock => updatedStock.RIC === selectedStock.RIC);

    updateStockPrices(selectedStockPrice.Bid, selectedStockPrice.Ask);
};

subscription.onData(streamDataHandler);
```

Now, when you click on a stock in the **Stocks** app, the **Stock Details** app will open below it in the Workspace showing information about the selected stocks.

![Chapter 9.5](https://docs.interop.io/desktop/images/tutorials/javascript/chapter-9.5.mp4)

## 10. Intents

A new requirement coming from the users is to implement a functionality that exports the portfolio of the selected client. Using the [Intents API](https://docs.interop.io/desktop/reference/javascript/intents/api/index.md), you will instrument the **Stocks** app to raise an Intent for exporting the portfolio, and another app will perform the actual action - the **Portfolio Downloader**. The benefit of this is that at a later stage of the project, the app for exporting the portfolio can be replaced, or another app for handling the exported portfolio in a different way can also register the same Intent. In any of these cases, code changes in the **Stocks** app won't be necessary.

> ℹ️ *See also the [Capabilities > Data Sharing > Intents](https://docs.interop.io/desktop/capabilities/data-sharing/intents/javascript/index.md) documentation.*

### 10.1 Registering Intents

In order for the **Portfolio Downloader** app to be targeted as an Intent handler, it must be registered as such. Apps can be registered as Intent handlers either by declaring the Intents they can handle in their app definition by using the `"intents"` top-level key and supplying a handler function via the [`register()`](https://docs.interop.io/desktop/reference/javascript/intents/api/index.md#API-register) method, or dynamically by using only the [`register()`](https://docs.interop.io/desktop/reference/javascript/intents/api/index.md#API-register) method. Using the app definition to register an Intent allows the app to be targeted as an Intent handler even if it isn't currently running. If the app is registered as an Intent handler dynamically, it can act as an Intent handler only during its life span.

Go to the `portfolio-downloader.json` app definition and register the **Portfolio Downloader** app as an Intent handler. It's only required to provide the name of the Intent, but you can optionally specify a display name (e.g., `"Download Portfolio"`, which can later be used in a dynamically generated UI) and a context (predefined data structure, e.g. `"ClientPortfolio"`) with which the app can work:

```json
// In `portfolio-downloader.json`.

{
    // Configuration for handling Intents.
    "intents": [
        {
            "name": "ExportPortfolio",
            "displayName": "Download Portfolio",
            "contexts": [
                "ClientPortfolio"
            ]
        }
    ]
}
```

Go to the `index.js` file of the **Portfolio Downloader** app and find the `TODO: Chapter 10.1` comment. Pass the name of the Intent and the already implemented `intentHandler()` function to the [`register()`](https://docs.interop.io/desktop/reference/javascript/intents/api/index.md#API-register) method. The `intentHandler()` function will be called each time the **Portfolio Downloader** app is targeted as an Intent handler:

```javascript
// In `start()`.

io.intents.register("ExportPortfolio", intentHandler);
```

### 10.2 Raising Intents

The **Stocks** app must raise an Intent request when the user clicks a button for exporting the portfolio of the selected client.

Go to the `index.html` file of the **Stocks** app, find the `TODO: Chapter 10.2` comment and uncomment the "Export Portfolio" button.

Go to the `index.js` file of the **Stocks** app and find the `TODO: Chapter 10.2` comment in the `exportPortfolioButtonHandler()` function. Use the [`find()`](https://docs.interop.io/desktop/reference/javascript/intents/api/index.md#API-find) method to retrieve a list of all registered Intents with name `"ExportPortfolio"`. If such a list exists, create an [`IntentRequest`](https://docs.interop.io/desktop/reference/javascript/intents/intentrequest/index.md) object that will hold the name of the Intent. Use this object to specify targeting behavior and provide context for the Intent. Use the [`raise()`](https://docs.interop.io/desktop/reference/javascript/intents/api/index.md#API-raise) method to raise the Intent and pass the `IntentRequest` object to it:

```javascript
// In `exportPortfolioButtonHandler()`.

try {
    // Retrieve a list of all registered Intents with name "ExportPortfolio".
    const intents = await io.intents.find("ExportPortfolio");

    if (!intents) {
        return;
    }

    // Create an Intent request and raise the Intent.
    const intentRequest = {
        intent: "ExportPortfolio",
        context: {
            type: "ClientPortfolio",
            data: { portfolio, clientName }
        }
    };

    await io.intents.raise(intentRequest);

} catch (error) {
    console.error(error.message);
}
```

Find the `TODO: Chapter 10.2` comment in the `start()` function and uncomment the click event handler for the "Export Portfolio" button.

Now, clicking on the "Export Portfolio" button will start the **Portfolio Downloader** app which will download the portfolio of the currently selected client in JSON format.

![Chapter 10.2](https://docs.interop.io/desktop/images/tutorials/javascript/chapter-10.2.mp4)

## 11. Notifications

A new requirement from the users is to display a notification whenever a new Workspace has been opened. Clicking on the notification must focus the Workspaces App and the Workspace for the respective client.

You will use the [Notifications API](https://docs.interop.io/desktop/reference/javascript/notifications/api/index.md) to raise a notification when the user clicks on a client to open a Workspace. To the `onclick` property of the notification, you will assign a handler for focusing the Workspaces App and the Workspace for the respective client. The handler will be invoked when the user clicks on the notification.

> ℹ️ *See also the [Capabilities > Notifications](https://docs.interop.io/desktop/capabilities/notifications/javascript/index.md) documentation.*

### 11.1 Raising Notifications

Go to the `index.js` file of the **Clients** app and find the `TODO: Chapter 11.1` comment in the `raiseNotificationOnWorkspaceOpen()` function. Define an [`IOConnectNotificationOptions`](https://docs.interop.io/desktop/reference/javascript/notifications/ioconnectnotificationoptions/index.md) object holding a title and body for the notification. Use the [`raise()`](https://docs.interop.io/desktop/reference/javascript/notifications/api/index.md#API-raise) method to raise a notification and pass the options object as an argument:

```javascript
// In `raiseNotificationOnWorkspaceOpen()`.

const options = {
    title: "New Workspace",
    body: `A new Workspace for ${clientName} was opened!`,
};

const notification = await io.notifications.raise(options);
```

Next, go to the `clientClickedHandler()` function and modify the existing code to call the `raiseNotificationOnWorkspaceOpen()` function. Pass to it the client name and the previously obtained [`Workspace`](https://docs.interop.io/desktop/reference/javascript/workspaces/workspace/index.md) object:

```javascript
// In `clientClickedHandler()`.

try {
    const workspace = await io.workspaces.restoreWorkspace("Client Space", restoreConfig);

    await raiseNotificationOnWorkspaceOpen(client.name, workspace);
} catch (error) {
    console.error(error.message);
}
```

Now, a notification will be raised whenever a new Workspace has been opened.

![Chapter 11.1](https://docs.interop.io/desktop/images/tutorials/javascript/chapter-11.1.mp4)

### 11.2 Notification Handler

Go to the `raiseNotificationOnWorkspaceOpen()` function and use the `onclick` property of the previously obtained [`Notification`](https://docs.interop.io/desktop/reference/javascript/notifications/notification/index.md) object to assign a handler for focusing the Workspaces App and the Workspace for the respective client:

```javascript
// In `raiseNotificationOnWorkspaceOpen()`.

notification.onclick = () => {
    // This will focus the Workspaces App.
    workspace.frame.focus().catch(console.error);
    // This will focus the Workspace for the respective client.
    workspace.focus().catch(console.error);
};
```

Now, when the user clicks on a notification, the Workspaces App and the Workspace for the respective client will be focused.

![Chapter 11.2](https://docs.interop.io/desktop/images/tutorials/javascript/chapter-11.2.mp4)

## 12. Themes

Currently, your apps are using only the dark [io.Connect theme](https://docs.interop.io/desktop/capabilities/windows/themes/overview/index.md) and it isn't possible to switch them to the light theme. The users are requesting that you update all apps to synchronize automatically with the current platform theme which they can change manually from the io.Connect launcher.

To achieve this, you will use the [Themes API](https://docs.interop.io/desktop/reference/javascript/themes/api/index.md).

> ℹ️ *See also the [Capabilities > Windows > Themes](https://docs.interop.io/desktop/capabilities/windows/themes/javascript/index.md) documentation.*

### 12.1 Subscribing for Theme Changes

The default io.Connect themes are controlled by the `"dark"` and `"light"` CSS classes applied to the `<html>` element of the document. Currently, the `"dark"` class is hard-coded in the `index.html` files of all your apps. You will use the Themes API to subscribe for theme changes and retrieve the name of the newly selected theme. Then, you will apply that name to the `<html>` element of your app as a class in order to change the theme.

The io.Connect platform saves the currently selected theme on shutdown. All apps that have subscribed for theme changes will be notified about the current theme on startup. This means that your apps will still update to the currently selected theme, even if the dark theme is hard-coded.

Go to the `start()` functions in the `index.js` files of all apps and find the `TODO: Chapter 12.1` comment. Use the `onChanged()` method to subscribe for theme changes and update your apps to the current theme by replacing the class name of the `<html>` element:

```javascript
const themeHandler = (newTheme) => {
    // The `html` element is already defined at the top of the `start()` function.
    html.className = newTheme.name;
};

io.themes.onChanged(themeHandler);
```

### 12.2 Retrieving the Initial Platform Theme

Some users have noticed that if the light theme is the initial platform theme, for a short moment, when your apps are still loading, the dark theme is applied and then is immediately replaced by the light theme. This happens because:

- the class for the dark theme is hard-coded and is applied immediately after the CSS file for the io.Connect themes is loaded;
- the `onChanged()` method can be used to detect the current platform theme only after the `@interopio/desktop` library has been fully initialized in your apps, which leads to a small delay in retrieving and applying the current theme;

To avoid these limitations, you can use the `iodesktop` object that the io.Connect platform injects in the global `window` object for all web apps. The `iodesktop` object allows early access to some of the io.Connect functionalities, before the `@interopio/desktop` library has been initialized.

Go to the `start()` functions in the `index.js` files of all apps and use the `theme` property of the `iodesktop` object to retrieve the initial platform theme. Do this at the top of the `start()` functions, after the `<html>` element definition, and before initializing the `interopio/desktop` library:

```javascript
const html = document.documentElement;
const initialTheme = iodesktop.theme;

html.className = initialTheme;
```

Now, when you start your apps, they won't flicker between the two themes and will update to the current platform theme when users change it from the io.Connect launcher:

![Chapter 12.2](https://docs.interop.io/desktop/images/tutorials/javascript/chapter-12.2.mp4)

## 13. Hotkeys

Several power users in the Asset Management team want to be able to start the **Stocks** app and toggle between the two platform themes via keyboard combinations while the **Clients** app is running. This will allow them to execute these tasks immediately, instead of wasting time and effort in looking for the io.Connect launcher on their multi screen setups, navigating to the respective sections in it, and searching for the **Stocks** app or the theme settings.

The [Hotkeys API](https://docs.interop.io/desktop/reference/javascript/hotkeys/api/index.md) enables you to register key combinations and receive notifications when a key combination is pressed by the user.

> ℹ️ *See also the [Capabilities > More > APIs > Hotkeys](https://docs.interop.io/desktop/capabilities/more/apis/index.md#hotkeys) documentation.*

Go to the `index.js` file of the **Clients** app and find the `TODO: Chapter 13` comment. Define hotkeys and handlers for starting the **Stocks** app and toggling the platform theme. Pass the hotkey definition and handler to the [`register()`](https://docs.interop.io/desktop/reference/javascript/hotkeys/api/index.md#API-register) method to register the hotkey:

```javascript
// Define a hotkey and a handler for opening the "Stocks" app.
const stocksAppHotkey = {
    // Keyboard combination to use as a shortcut.
    hotkey: "alt+shift+s",
    // Optional description for the hotkey.
    description: "Starts the Stocks app."
};

const stocksAppHotkeyHandler = () => io.appManager.application("stocks").start();

// Define a hotkey and a handler for toggling the platform theme.
const themeHotkey = {
    hotkey: "alt+shift+t",
    description: "Toggles the platform theme."
};

const themeHotkeyHandler = async () => {
    const currentTheme = await io.themes.getCurrent();
    const themeToSelect = currentTheme.name === "dark" ? "light" : "dark";

    io.themes.select(themeToSelect);
};

// Register the hotkeys.
io.hotkeys.register(stocksAppHotkey, stocksAppHotkeyHandler);
io.hotkeys.register(themeHotkey, themeHotkeyHandler);
```

The hotkeys will be registered when the **Clients** app is started and will work as long as the app is running. Now, the users will have the option to start the **Stocks** app and toggle the platform theme via a keyboard shortcut instead of having to navigate the io.Connect launcher UI:

![Chapter 13](https://docs.interop.io/desktop/images/tutorials/javascript/chapter-13.mp4)

## Congratulations!

You have successfully completed the **io.Connect Desktop** JavaScript tutorial!

To deepen your understanding of the io.Connect framework and start using more advanced io.Connect functionalities, explore the [Capabilities](https://docs.interop.io/desktop/capabilities/launcher/index.md) and the [Developers](https://docs.interop.io/desktop/developers/configuration/overview/index.md) sections.
