Windows

Overview

The Window Management API enables you to create and manipulate io.Connect Windows and is the basis of the App Management API. It allows users to group io.Connect Windows so that they move, maximize and minimize together, and provides the following features, not found in any normal browser:

  • 4 types of window modes: flat, tab, HTML and frameless;

  • Complete control and customization of the windows:

    • visibility - create hidden windows, show them later;
    • bounds - set window location and size;
    • user interaction - allow a window to be sticky, enable or disable "Minimize", "Maximize" and "Close" buttons;
    • add custom frame buttons to the windows and respond accordingly to user interaction with them;
    • organize windows into tabs that the user can also tear off;

Native apps, as opposed to web apps, can have more than one window. After you interop-enable your native app, your app windows aren't automatically registered as io.Connect Windows. You can choose which ones to register as io.Connect Windows so that they can use io.Connect Desktop functionalities.

Web and native windows are handled by io.Connect Desktop as window abstractions, which means that:

  • You can use any technology adapter provided by io.Connect (JavaScript, .NET, Java, and many more) to control any window, web or native.
  • From an end user perspective, there is no difference between web or native windows.
  • Feature parity is provided by the different technology adapters.

Window Modes

io.Connect supports four different window modes: flat, tab, HTML and frameless. The window mode is controlled by the "mode" window setting, specified in the app definition or as a setting when opening an io.Connect Window programmatically.

Specifying the window mode in the app definition:

{
    "title": "My App",
    "type": "window",
    "name": "my-app",
    "details": {
        "url": "http://localhost:3000",
        "mode": "html"
    }
}

The "mode" property can be set to "flat" (default), "tab", "html" and "frameless".

Flat Windows

Flat windows are available for web and native apps. They have a caption bar that contains the app title and standard system buttons ("Minimize", "Maximize", "Close"):

Flat Window

Tab Windows

Tab windows are available for web and native apps. The user can group them in or tear them off a tab group. They have a tab caption bar which contains that window tabs and standard system buttons ("Minimize", "Maximize", "Close"):

Tab Window

HTML Windows

HTML windows are available for web apps only. The HTML page spans the entire window, there is no caption bar, and the standard system buttons ("Minimize", "Maximize", "Close") become visible when the user hovers over them:

HTML Window

Frameless Windows

Frameless windows are based on HTML windows, but allow for creating apps with freeform (non-rectangular) shapes and transparent areas. By default, they don't have the usual io.Connect Window decorations - title bars, standard system buttons ("Minimize", "Maximize", "Close"), resizing areas (unless you specify resizing areas in the app definition), and can't be dragged (unless you define a custom draggable area within the web app), can't be dropped in Workspaces or stuck to other io.Connect Windows. Frameless windows can be saved and restored in Layouts and can use all io.Connect functionalities provided by the io.Connect libraries.

The following example demonstrates a freeform frameless window, registered in io.Connect Desktop as an interop-enabled app, and using the App Management API to start another app. The window is draggable, because the web app has a defined draggable area:

Frameless Window

Flydown & Popup Windows

The io.Connect JavaScript library offers convenient facilities for creating flydown and popup windows. Flydown and popup windows can be very useful for dropdown menus and helper windows for your app, but when implementing them from scratch, it may be hard to control their behavior as there are many corner cases to consider. The io.Connect library resolves these issues and provides an easy way for creating and manipulating flydown and popup windows.

Flydown windows appear when the user hovers over a predefined area in your app:

Flydown

Popup windows appear when the user clicks on a predefined area in your app:

Popup

Tab Overflow

Available since io.Connect Desktop 9.4

⚠️ Note that this feature is available only for web groups.

When tab overflow is enabled, the extra tabs on the right side of the tab header will be collected in a dropdown menu in their original order instead of becoming smaller and smaller each time a new tab is added to the group. The user can open the menu and select the desired tab (or close a tab directly from the menu). The tabs will be scrolled horizontally and the selected tab will be focused. This may improve the user experience, as the window tabs won't shrink and their titles will still be visible, irrespective of the number of windows in the group:

Tab Overflow

To enable tab overflow, use the "overflow" property found under the "tabs" property of the "group" object under the "windowManagement" top-level key in the system.json system configuration file:

{
    "windowManagement": {
        "group": {
            "tabs": {
                "overflow": true
            }
        }
    }
}

Renaming Group & Window Captions

When Using Classic Groups

Users can manually change the caption titles of classic groups, as well as the captions of flat and tab io.Connect Windows participating in them. The user-defined names of groups and windows saved in a Layout will be preserved when the Layout is restored.

Renaming Groups

To allow the users to manually modify the caption titles of groups and of the windows participating in them when using classic groups, set the "enableWindowGroupsCaptionEdit" and "enableWindowsCaptionEdit" properties in the stickywindows.json configuration file of io.Connect Desktop to true:

{
    "enableWindowGroupsCaptionEdit": true,
    "enableWindowsCaptionEdit": true
}

When Using Web Groups

Renaming the captions of groups and of flat and tab windows participating in them is possible also for web groups. No additional configuration is necessary to allow caption editing when using web groups.

Individual Windows

You can rename the captions of individual (not joined in a group) flat and tab windows when using classic groups or web groups:

Individual Windows

To allow caption editing when using classic groups, you must set the "enableWindowsCaptionEdit" property in the stickywindows.json configuration file of io.Connect Desktop to true:

{
    "enableWindowsCaptionEdit": true
}

When using web groups, no configuration change is necessary.

Sticky Button

The "Sticky" button in the app window header allows the users to toggle the stickiness of individual windows from the UI:

Stickiness

The "Sticky" button is disabled by default and can be enabled globally from the system configuration of io.Connect Desktop, or per app from the app definition file. The app definition will override the global system configuration.

Enable the "Sticky" button globally for all apps from the system.json configuration file using the "windows" top-level key:

{
    "windows": {
        "showStickyButton": true
    }
}

Disable the "Sticky" button for an app from its definition:

{
    "details": {
        "showStickyButton": false
    }
}

Feedback Button

The Feedback Form can be opened directly from an app instance by using the "Feedback" button in the app window header.

Feedback Button

The "Feedback" button is disabled by default and can be enabled globally from the system configuration of io.Connect Desktop or per app from the app definition file. The app definition will override the global system configuration.

Enable the "Feedback" button globally for all apps from the system.json configuration file using the "windows" top-level key:

{
    "windows": {
        "showFeedbackButton": true
    }
}

Disable the "Feedback" button for an app from its definition:

{
    "details": {
        "showFeedbackButton": false
    }
}

Use the "supportEmails" top-level key to specify the emails of the app owners. The email addresses defined in this property will be added to the Feedback Form if it has been triggered from that app:

{
    "supportEmails": ["app.owner1@example.com", "app.owner2@example.com"]
}

Clone Button

Available since io.Connect Desktop 9.2

⚠️ Note that this feature is available only for web groups.

The "Clone" button in the app window header can be used to duplicate the current window with its current state. If the cloned window is a tab window, it will be added to the same tab group. All other types of windows will be cascaded:

Cloning Windows

The "Clone" button is disabled by default and can be enabled globally from the system configuration of io.Connect Desktop, or per app from the app definition file. The app definition will override the global system configuration.

Enable the "Clone" button globally for all apps from the system.json configuration file using the "windows" top-level key:

{
    "windows": {
        "showCloneButton": true
    }
}

Disable the "Clone" button for an app from its definition:

{
    "details": {
        "showCloneButton": false
    }
}

Collapse & Expand

io.Connect Windows can be collapsed and expanded, including when in a window group:

Collapse Expand

By default, the button for collapsing and expanding windows is disabled. To enable it, use the "allowCollapse" property of the "details" top-level key in the app definition file.

The following example demonstrates how to enable the "Collapse/Expand" button for an app:

{
    "details": {
        "allowCollapse": true
    }
}

Window Groups

Besides providing full control over individual windows, io.Connect Desktop also enables you to arrange io.Connect Windows visually by sticking them together and forming window groups. Grouping separate io.Connect Windows in a single frame (window) allows the users to move, resize, minimize, maximize, restore and close all participating windows together using the standard buttons on the window group frame. Grouped windows have a single customizable taskbar icon, can be resized within the group frame and can be torn off from the group. Tab windows can be grouped in tab groups that in turn can be stuck to other io.Connect Windows or groups.

Window Groups

The standard buttons ("Minimize", "Maximize", "Restore", "Close") of the individual io.Connect Windows participating in the group can also be used which will result in the following behavior:

  • "Minimize" - will minimize the entire group.
  • "Maximize" - will maximize only the window, breaking it off from the window group. If none of the other windows in the group has been manipulated in any way (moved, resized or changed its state), the group will be restored when the maximized window is restored.
  • "Restore" - restores a maximized window to its normal state, restoring also the window group, unless any of the other windows participating in it has been manipulated in any way (moved, resized or changed its state).
  • "Close" - will close only the window.

Group Window Buttons

Groups can be saved and restored in Layouts, saving users time and effort to find and rearrange the necessary apps again after closing the group.

io.Connect Window groups can be controlled programmatically - maximize and restore, show and hide the group header, get and set the group title, find an individual window or all windows participating in the group, find and list window groups, subscribe for group-related events.

io.Connect Desktop offers two implementations of io.Connect Window groups, which are visually indistinguishable to the user - classic and web groups.

Using Window Groups

Apps in Groups

The titles of the app windows participating the group are visible when right clicking on the group taskbar icon. When restoring a minimized group, the selected app is focused.

Group Apps Taskbar Menu

Navigating Window Tabs

You can jump to the next or the previous window in a tab group by using keyboard shortcuts:

Tab Navigation

Shortcut Description
CTRL + TAB Jump to next tab.
CTRL + SHIFT + TAB Jump to previous tab.
CTRL + PgDn Jump to next tab.
CTRL + PgUp Jump to previous tab.

Resizing Grouped Windows

Use the internal separators to resize windows in a group.

To set the resize behavior when resizing io.Connect Windows within a group by dragging an inner separator, use the "sizingMode" property of the stickywindows.json file for configuring io.Connect Windows. If you use "Proportional" as a value, the windows will be resized proportionally within the group without changing the group size:

{
    "sizingMode": "Proportional"
}

Proportional Sizing

If you use "Single" as a value, only the respective window will be resized while all other windows will retain their current size which will result in changing the size of the entire group:

{
    "sizingMode": "Single"
}

Single Sizing

Grouped windows have common outer borders when aligned to each other. Use that border to resize the entire group, or hold ALT to resize a single window, disregarding the common outer border:

Resizing

To align grouped windows manually in height or width, double click on the window border if you want to increase the window height or width, or hold ALT and double-click if you want to decrease it:

Aligning Windows

Preventing Snap & Drop

To prevent windows from snapping to other windows or dropping them in a window group while dragging them, hold ALT. The following demonstrates the user dragging a window over a window group. When the user holds ALT, the highlighted areas disappear and the window can be placed at any position on the screen without it snapping to other windows or being dropped in a window group:

Preventing Snap & Drop

Classic Groups

The io.Connect classic groups are based on WPF components and are the default group implementation in io.Connect Desktop. To switch to the io.Connect classic groups, use the "groupType" property in the stickywindows.json configuration file of io.Connect Desktop located in the %LocalAppData%/interop.io/io.Connect Desktop/Desktop/config folder:

{
    "groupType": "Classic"
}

io.Connect classic groups allow limited visual customization through the themes.json configuration file of io.Connect Desktop. For more details, see the Developers > Configuration > Themes section.

Web Groups

The io.Connect web groups are based on web technologies and allow for complete customization of the containing group frame and all elements of the different io.Connect Window types (flat, tab and HTML) that may be a part of it. To switch to the io.Connect web groups, use the "groupType" property in the stickywindows.json configuration file of io.Connect Desktop located in the %LocalAppData%/interop.io/io.Connect Desktop/Desktop/config folder:

{
    "groupType": "Web"
}

io.Connect web groups can be customized visually by supplying custom CSS styles for the default Web Group App in io.Connect Desktop, or you can fashion your own, completely tailored Web Group App using the @interopio/groups-ui-react library.

Extending Web Groups

⚠️ Note that the @interopio/groups-ui-react library doesn't support some of the features supported by the io.Connect classic groups. For more details, see the Limitations section.

The @interopio/groups-ui-react library provides all functionalities necessary for building a Web Group App as a single React component - <Group />. The <Group /> component provides extensibility points for passing custom components to it (see Group Component).

See below an example of a Web Group App with the following customized elements:

Custom Web Group App

To open a console window for the Web Group App when testing or debugging, press SHIFT + F12.

⚠️ Note that the @interopio/groups-ui-react library doesn't include a built Web Group App. A Web Group App is provided in io.Connect Desktop. You can also use and customize the Web Group App template.

See also the Web Groups with Banners example on GitHub.

Configuration

Your custom Web Group App, as every interop-enabled app, must have an app definition file. It's "type" property must be set to "webGroup".

⚠️ Note that, by default, io.Connect Desktop will search for a registered app with the name "web-group-application" and if one is available, will use it. If no such app is found, the first available app definition of type "webGroup" will be used. io.Connect Desktop expects only one app definition for a Web Group App - i.e., one configuration file with "type" property set to "webGroup". If multiple Web Group App definitions are present, the first one will be used.

io.Connect Desktop comes with a Web Group App and a definition file for it named webGroup.json and located in %LocalAppData%/interop.io/io.Connect Desktop/Desktop/config/apps. Modify or replace this file with your own definition file, or delete it, if your app definitions are stored at another location.

The following is an example definition of a Web Group App:

{
    "name": "web-group-app",
    "type": "webGroup",
    "hidden": true,
    "details": {
        "url": "http://localhost:3000/",
        "autoOpenDevTools": true,
        "captureErrors": true,
        "preloadScripts": ["https://example.com/my-script.js"],
        "pool": {
            "min": 5
        }
    }
}

The "url" property is required and must point to the location of your custom Web Group App.

Use the "autoOpenDevTools" property to automatically open the Chrome Dev Tools (disabled by default) when debugging your Web Group App.

Use the "captureErrors" property to instruct io.Connect Desktop to log errors from the Web Group App in the application.log file located in %LocalAppData%/interop.io/io.Connect Desktop/UserData/<ENV>-<REG>/logs, where <ENV>-<REG> represents the environment and region of io.Connect Desktop (e.g., DEMO-INTEROP.IO).

Use the "preloadScripts" property to specify a list of URLs pointing to scripts that will be loaded and executed before loading the Web Group App.

Use the "pool" property to specify the minimum number of cached Web Group App instances (3 is the default and also the minimum possible value) used for improving group performance and user experience. The higher the number, the more memory will be consumed; the lower the number, the higher the chance to experience delay during web group operations.

The top-level "hidden" property is set to true in order to hide the Web Group App from the io.Connect launcher, because this is a service app used directly by io.Connect Desktop to host io.Connect Windows.

For more details about global settings for web groups, see the Developers > Configuration > System > Window Management section.

Using io.Connect APIs in the Web Group App

The Web Group App is a fully-featured io.Connect client, so you can use all io.Connect APIs in it.

The only thing to keep in mind when using the Window Management API in a Web Group App is that the io.windows.my() method by design won't work (will return undefined). The reason for this is that the Web Group App is a special app (and not a typical io.Connect Window) whose main purpose is to host regular io.Connect Windows allowing them to form window groups - either single windows (i.e., a group with a single window in it), windows in a tabbed group, or windows that stick to each other to form a window group.

Group Elements

It's possible to add your custom components to the Web Group App or customize the default ones. This section demonstrates the available customizable zones and the default components of the Web Group App.

⚠️ Note that when adding custom components to the Web Group App, you can use the -webkit-app-region: drag and -webkit-app-region: no-drag styles to define custom draggable areas. This is especially useful if you are adding custom content inside components that already contain draggable areas (like the <GroupCaptionBar />, <FlatCaptionBar /> or the tab header of a tabbed group) and want to include or exclude this content from the draggable area.

Group Caption Bar

The Group Caption Bar element is located at the topmost part of the window group:

Group Caption Bar Zone

The Group Caption Bar element hosts the <GroupCaptionBar /> component which contains the <GroupMoveArea />, <GroupCaption />, <GroupCaptionEditor /> and <GroupButtons /> components. The following demonstrates the default structure of the <GroupCaptionBar /> component:

<GroupCaptionBar>
    <GroupMoveArea>
        <GroupCaption />
        <GroupCaptionEditor />
    </GroupMoveArea>
    <GroupButtons>
        <MinimizeButton />
        <MaximizeButton />
        <RestoreButton />
        <CloseButton />
    </GroupButtons>
</GroupCaptionBar>

For an example usage, see the Group Caption Bar Components section.

Flat Windows

The io.Connect flat windows have a Flat Caption Bar element located at the topmost part of a flat window:

Flat Caption Bar Zone

The Flat Caption Bar element hosts the <FlatCaptionBar /> component which contains the <FlatChannelSelector />, <FlatMoveArea />, <FlatCaption />, <FlatCaptionEditor /> and <FlatButtons /> components. The following demonstrates the default structure of the <FlatCaptionBar /> component:

<FlatCaptionBar>
    <FlatChannelSelector />
    <FlatMoveArea>
        <FlatCaption />
        <FlatCaptionEditor />
    </FlatMoveArea>
    <FlatButtons>
        <MinimizeButton />
        <MaximizeButton />
        <RestoreButton />
        <CloseButton />
    </FlatButtons>
</FlatCaptionBar>

For an example usage, see the Flat Window Components section.

Tab Windows

The Tab Header element of a tab group isn't available as a single customizable component, but contains several customizable zones and components. One or more Tab elements hold the individual tabs and the Tab Header Buttons element holds the standard system buttons:

Tab and Button Zones

There are four additional customizable zones that are located before and after the tabs, above and below the tab header - the Before Tabs, After Tabs, Above Tabs and Below Tabs zones:

Surrounding Tab Zones

Each Tab element hosts an individual <Tab /> component. Each <Tab /> component contains the <TabChannelSelector />, <TabCaption />, <TabCaptionEditor /> and <TabCloseButton /> components. The Tab Header Buttons element hosts the <TabHeaderButtons /> component. The following demonstrates the default structure of the <Tab /> and the <TabHeaderButtons /> components:

<Tab>
    <TabChannelSelector />
    <TabCaption />
    <TabCaptionEditor />
    <TabCloseButton />
</Tab>

<TabHeaderButtons>
    <MinimizeButton />
    <MaximizeButton />
    <RestoreButton />
    <CloseButton />
</TabHeaderButtons>

The move area for a tab group is automatically created between the After Tabs zone and the Tab Header Buttons element. There is no component for it and it isn't necessary to create it explicitly.

For an example usage, see the Tab Window Components section.

HTML Windows

The io.Connect HTML windows have only a single HTML Buttons element holding standard and custom buttons:

HTML Buttons

Available since io.Connect Desktop 9.2

The HTML Buttons element hosts the <HtmlButtons /> component. The following demonstrates the default structure of the <HtmlButtons /> component:

<HtmlButtons>
    <MinimizeButton />
    <MaximizeButton />
    <RestoreButton />
    <CloseButton />
</HtmlButtons>

For an example usage, see the HTML Window Components section.

Other Zones

The @interopio/groups-ui-react library offers other customizable zones that don't contain default components and can be used for additional customizations:

  • On a group level, you can use an Overlay zone to show a custom element anywhere within the group bounds. The bounds and lifetime of this element are determined by your custom styles and logic. For an example usage, see the Overlays section.

  • On a window level, you can use an Overlay zone to show a custom element anywhere within the bounds of all window types in the group. The bounds and lifetime of this element are determined by your custom styles and logic. The window-level overlay can be shown either in all windows simultaneously, or you can target a specific window. For an example usage, see the Overlays section.

  • On a window level, you can use a Below Window zone to show custom content that will be situated at the bottom of all window types. For example usage, see Common Window Components.

Using the Components

All default components can be reused and composed with custom code. If usage of such component has been detected, its default behavior will be applied. For instance, if you use the <CloseButton /> component, it will automatically close the window or group when the button is clicked, without the need of custom code to induce this behavior. If you pass the same component more than once, an error will be thrown.

To remove a component and make the respective zone or element empty, pass a <Fragment /> component.

There are several prerequisites when creating a custom Web Group App:

  • The size of the <Group /> component must always be 100% of the window to avoid unexpected behavior and errors.
  • The <Group /> component must not be nested in other components, as its bounds are determined by the windows forming the window group and the group may also have an irregular shape. Wrapping the <Group /> component will result in unexpected behavior.
  • The CSS files must be added manually (see Styles).

Group Component

The <Group /> component has two props - components and styles.

Use the components prop to define the components for the various group and window elements and zones - Group Caption Bar Components, Flat Window Components, Tab Window Components, Common Window Components and Overlays.

Use the styles prop to supply custom classes and CSS for some of the group elements (see Styles).

The following example demonstrates the structure of the <Group /> component, its props and default values:

<Group
    components={{
        group: {
            CaptionBar: () => {
                return (
                    <GroupCaptionBar>
                        <GroupMoveArea>
                            <GroupCaption />
                            <GroupCaptionEditor />
                        </GroupMoveArea>
                        <GroupButtons>
                            <MinimizeButton />
                            <MaximizeButton />
                            <RestoreButton />
                            <CloseButton />
                        </GroupButtons>
                    </GroupCaptionBar>
                );
            },
            Overlay: () => <></>
        },
        frame: {
            Overlay: () => <></>,
            WindowContentOverlay: () => <></>,
            AboveWindow: () => <></>,
            BelowWindow: () => <></>,
            LoadingAnimation: <FrameLoadingAnimation>
        },
        flat: {
            CaptionBar: () => {
                return (
                    <FlatCaptionBar>
                        <FlatChannelSelector />
                        <FlatMoveArea>
                            <FlatCaption />
                            <FlatCaptionEditor />
                        </FlatMoveArea>
                        <FlatButtons>
                            <MinimizeButton />
                            <MaximizeButton />
                            <RestoreButton />
                            <CloseButton />
                        </FlatButtons>
                    </FlatCaptionBar>
                );
            }
        },
        tabs: {
            Element: () => {
                return (
                    <Tab>
                        <TabChannelSelector />
                        <TabCaption />
                        <TabCaptionEditor />
                        <TabCloseButton />
                    </Tab>
                );
            },
            Before: () => <></>,
            After: () => <></>,
            Above: () => <></>,
            Below: () => <></>,
            Buttons: () => {
                return (
                    <TabHeaderButtons>
                        <MinimizeButton />
                        <MaximizeButton />
                        <RestoreButton />
                        <CloseButton />
                    </TabHeaderButtons>
                )
            }
        },
        html: {
            Buttons: () => {
                return (
                    <HtmlButtons>
                        <MinimizeButton />
                        <MaximizeButton />
                        <RestoreButton />
                        <CloseButton />
                    </HtmlButtons>
                )
            }
        }
    }}

    styles={{
        frame: {
            element: {
                classes: [],
                css: {}
            }
        },
        tabs: {
            header: {
                classes: [],
                css: {}
            }
            moveArea: {
                classes: [],
                css: {}
            }
        },
    }}
/>

Group Caption Bar Components

The group property of the components object in the <Group /> component is used for defining the structure and content of the group caption bar and the group overlay.

The following example demonstrates how to create a custom group caption bar and add a custom button in it. The default functionality for editing the caption is preserved by using the default <GroupCaptionEditor /> component:

import {
    GroupMoveArea,
    GroupCaption,
    GroupCaptionEditor,
    GroupButtons
} from "@interopio/groups-ui-react";
import CustomButton from "./CustomButton";

const CustomGroupCaptionBar = ({
    moveAreaId,
    captionEditor,
    targetId,
    caption,
    notifyCaptionBoundsChanged,
    ...rest
}) => {
    return (
        <div className="custom-group-caption-bar">
            <GroupMoveArea moveAreaId={moveAreaId}>
            {captionEditor.show ?
                <GroupCaptionEditor {...captionEditor} groupId={targetId} caption={captionEditor.text} /> :
                <GroupCaption caption={caption} notifyBoundsChanged={notifyCaptionBoundsChanged} />}
            </GroupMoveArea>
            <CustomButton />
            <GroupButtons {...rest} />
        </div>
    );
};

export default CustomGroupCaptionBar;

Using the custom group caption bar in your Web Group App:

import Group from "@interopio/groups-ui-react";
import CustomGroupCaptionBar from "./CustomGroupCaptionBar";

const App = () => {
    return (
        <Group
            components={{
                group: {
                    CaptionBar: CustomGroupCaptionBar
                }
            }}
        />
    );
};

export default App;

Custom group caption bar with a custom button in it:

Custom Caption Bar

See also the Web Groups with Banners example on GitHub.

Flat Window Components

The flat property of the components object in the <Group /> component is used for defining the structure and content of the caption bar for flat windows.

The following example demonstrates how to create a custom caption for flat windows:

import { useRef } from "react";
import { useEditableCaption } from "@interopio/groups-ui-react";

const CustomCaption = ({ caption, notifyBoundsChanged }) => {
    const ref = useRef(null);

    // Use this hook to notify the web groups framework that
    // this component is the editable caption of the Flat Caption Bar element.
    useEditableCaption(ref, { notifyBoundsChanged });

    return <div ref={ref} className="custom-caption">Custom Caption: {caption}</div>
};

export default CustomCaption;

Creating a custom caption bar for flat windows containing a Channel Selector and a custom caption. The default functionality for editing the caption is preserved by using the default <FlatCaptionEditor /> component:

import {
    FlatChannelSelector,
    FlatMoveArea,
    FlatCaptionEditor,
    FlatButtons
} from "@interopio/groups-ui-react";
import CustomCaption from "./CustomCaption";

const CustomFlatCaptionBar = ({
    channels,
    moveAreaId,
    captionEditor,
    caption,
    notifyCaptionBoundsChanged,
    ...rest
}) => {
    return (
        <div className="custom-flat-caption-bar">
            {channels.visible && <FlatChannelSelector {...channels} />}
            <FlatMoveArea moveAreaId={moveAreaId}>
                {captionEditor.show ?
                    <FlatCaptionEditor {...captionEditor} caption={captionEditor.text} frameId={rest.frameId} /> :
                    <CustomCaption caption={caption} notifyBoundsChanged={notifyCaptionBoundsChanged} />}
            </FlatMoveArea>
            <FlatButtons {...rest} />
        </div>
    );
};

export default CustomFlatCaptionBar;

Using the custom caption bar for flat windows in your Web Group App:

import Group from "@interopio/groups-ui-react";
import CustomFlatCaptionBar from "./CustomFlatCaptionBar";

const App = () => {
    return (
        <Group
            components={{
                flat: {
                    CaptionBar: CustomFlatCaptionBar
                }
            }}
        />
    );
};

export default App;

Custom caption bar for flat windows containing a custom caption:

Custom Flat Caption

See also the Web Groups with Banners example on GitHub.

Tab Window Components

The tabs property of the components object in the <Group /> component is used for defining the structure and content of each tab element, tab header buttons and the additional zones around the tab elements.

The following example demonstrates how to create a custom "Close" button for tab windows:

import { useEffect, useRef } from "react";

const CustomCloseButton = ({ close }) => {
    const ref = useRef(null);

    useEffect(() => {
        if (!ref.current) { return };

        // Stop the propagation of the `"mousedown"` event,
        // in order to prevent the web groups framework from processing it.
        ref.current.onmousedown = e => e.stopPropagation();
    }, [ref]);

    return <button ref={ref} onClick={() => close()}>x</button>
};

export default CustomCloseButton;

In this example, the propagation of the "mousedown" event is stopped, because otherwise the tab will enter draggable state. The close() handler is attached to the "click" event, because attaching it to the "mousedown" event won't work - the propagation of the "mousedown" event has been stopped using a direct DOM reference, which means that this event will never be fired as a React event.

⚠️ Note that the final implementation of a Web Group App is a mixture of React and pure JavaScript. That's why when preventing the propagation of DOM events, it is strongly recommended to use direct references to the DOM elements. If you use React events, the event notifications may be received too late to stop the propagation.

Creating a custom tab element for tab windows containing a Channel Selector and a custom "Close" button. The default functionality for editing the caption is preserved by using the default <TabCaptionEditor /> component:

import {
    TabChannelSelector,
    TabCaption,
    TabCaptionEditor
} from "@interopio/groups-ui-react";
import CustomCloseButton from "./CustomCloseButton";

const CustomTab = ({
    channels,
    captionEditor,
    selected,
    windowId,
    caption,
    notifyCaptionBoundsChanged,
    close
}) => {
    return (
        <div className="custom-tab">
            {channels.visible && <TabChannelSelector {...channels} />}
            {captionEditor.show ?
                <TabCaptionEditor {...captionEditor} caption={captionEditor.text} selected={selected} windowId={windowId} /> :
                <TabCaption caption={caption} selected={selected} notifyBoundsChanged={notifyCaptionBoundsChanged} />}
            <CustomCloseButton selected={selected} close={close} />
        </div>
    );
};

export default CustomTab;

Using the custom tab element for tab windows in your Web Group App and defining a custom component for the Before Tabs zone:

import Group from "@interopio/groups-ui-react";
import CustomTab from "./CustomTab";

const App = () => {
    return (
        <Group
            components={{
                tabs: {
                    Element: CustomTab,
                    Before: () => {
                        return <div className="custom-before-tab">&#128269;</div>
                    }
                }
            }}
        />
    );
};

export default App;

Custom Before Tabs zone containing a custom icon and a custom tab element containing a custom "Close" button:

Custom Tab

See also the Web Groups with Banners example on GitHub.

HTML Window Components

Available since io.Connect Desktop 9.2

The html property of the components object in the <Group /> component is used for defining the structure and content of the caption bar for flat windows.

The following example demonstrates adding a custom button before the standard buttons inside the <HtmlButtons /> component:

import Group, { HtmlButtons } from "@interopio/groups-ui-react";
import CustomButton from "./CustomButton";

const App = () => {
    return (
        <Group
            components={{
                html: {
                    Buttons: props => <> <CustomButton /> <HtmlButtons {...props} /> </>
                }
            }}
        />
    );
};

export default App;

Common Window Components

The frame property of the components object in the <Group /> component is used for defining the structure and content of window and overlay zones that are common for all window types.

The following example demonstrates how to create a custom component for the Below Window zone that will be shown in all windows in the group, regardless of their type:

import Group from "@interopio/groups-ui-react";

const App = () => {
    return (
        <Group
            components={{
                frame: {
                    BelowWindow: () => {
                        return <div className="custom-below-window">Custom Window Content</div>
                    }
                }
            }}
        />
    );
};

export default App;

⚠️ Note that showing content in a Below Window zone effectively reduces the height of the window area.

Custom Below Window zone:

Custom Below Window

See also the Web Groups with Banners example on GitHub.

Loading Animation

To specify a custom loading animation for the windows in your Web Group App, use the frame property of the components object in the <Group /> component:

import Group from "@interopio/groups-ui-react";
import CustomLoadingAnimation from "./CustomLoadingAnimation";

const App = () => {
    return (
        <Group
            components={{
                frame: {
                    LoadingAnimation: CustomLoadingAnimation
                }
            }}
        />
    );
};

export default App;

Overlays

The @interopio/groups-ui-react library allows you to show custom overlays on a group or on a window level. The bounds and lifetime of overlay components is determined by your custom styles and logic. The window-level overlays can be shown either in all windows simultaneously, or you can target a specific window.

To define a window-level overlay, use the Overlay zone of the frame property of the components object in the <Group /> component:

import Group from "@interopio/groups-ui-react";

const App = () => {
    return (
        <Group
            components={{
                frame: {
                    Overlay: () => {
                        return <div className="custom-window-overlay">Custom Content</div>
                    }
                }
            }}
        />
    );
};

export default App;

If you use the Overlay property, your custom overlay will be shown over all window elements, including any Above Window or Below Window zones. If you want to display an overlay only over the actual window contents, use the WindowContentOverlay property of the frame object.

The following examples demonstrate how to create a group-level overlay that will show or hide when the user clicks a button in the group caption bar.

Create a custom group overlay:

const CustomGroupOverlay = () => {
    return (
        <div className="custom-group-overlay">
            <input placeholder="Search instruments" />
            <button>Load Portfolios</button>
        </div>
    );
};

export default CustomGroupOverlay;

Create a custom group caption bar and place a custom button in it that will show or hide the group overlay:

import {
    GroupMoveArea,
    GroupCaption,
    GroupButtons
} from "@interopio/groups-ui-react";
import CustomButton from "./CustomButton";

const CustomGroupCaptionBar = ({ moveAreaId, caption, showOverlay, setShowOverlay, ...props }) => {
    return (
        <div className="custom-group-caption-bar">
            <GroupMoveArea moveAreaId={moveAreaId}>
                <GroupCaption caption={caption} />
            </GroupMoveArea>
            <button onClick={() => setShowOverlay(!showOverlay)}>Search Instruments</button>
            <GroupButtons {...props} />
        </div>
    );
};

export default CustomGroupCaptionBar;

Pass the custom components to your custom Web Groups App and render the group overlay conditionally:

import { useState } from "react";
import Group from "@interopio/groups-ui-react";
import CustomGroupCaptionBar from "./CustomGroupCaptionBar";
import CustomGroupOverlay from "./CustomGroupOverlay";

const App = () => {
    const [showOverlay, setShowOverlay] = useState(false);

    return (
        <Group
            components={{
                group: {
                    CaptionBar: props => {
                                    return (
                                        <CustomGroupCaptionBar
                                            showOverlay={showOverlay}
                                            setShowOverlay={setShowOverlay}
                                            {...props}
                                        />
                                    );
                                },
                    Overlay: () => showOverlay ? <CustomGroupOverlay /> : <></>
                }
            }}
        />
    );
};

export default App;

Custom Group Overlay

See also the Web Groups with Banners example on GitHub.

Requesting Focus

Some components in your custom Web Group App may require keyboard focus when the user clicks on them (e.g., input fields) or when the component that contains them has been mounted. By default, the keyboard focus isn't on the Web Group App (the web page itself), but rather on the apps participating in it. To move the keyboard focus to a component in the Web Group App, use the requestPageFocus() method.

The following examples demonstrate how to move the keyboard focus to a custom input field located in a custom window tab. The input field is used for changing the tab title - when the user double clicks on the tab, the input will be shown, and when they double click again, the new title will be set.

Requesting Focus

Create a custom input field and use the requestPageFocus() method to move the keyboard focus to it every time the component is mounted and when the user clicks on it. You must also use a direct reference to the element and stop the propagation of the "mousedown" and "click" events in order to prevent the web groups framework from processing them. Use the requestPageFocus() method in the "click" event in order to focus the element every time the user clicks on it, in case the element is already rendered, but loses focus, because the user has clicked somewhere else:

import { useState, useEffect, useRef } from "react";
import { requestPageFocus } from "@interopio/groups-ui-react";

const CustomInput = ({ setTabTitle }) => {
    const [newTitle, setNewTitle] = useState("");
    const ref = useRef(null);

    useEffect(() => {
        if (!ref.current) { return };

        // Stop the propagation of the `"mousedown"` and `"click"` events,
        // in order to prevent the web groups framework from processing them.
        ref.current.onmousedown = e => e.stopPropagation();
        ref.current.onclick = (e) => {
            e.stopPropagation();
            // Request keyboard focus when the component is mounted and when the user clicks on it.
            requestPageFocus();
        };
    }, [ref]);

    return <input ref={ref} onDoubleClick={() => setTabTitle(newTitle)} onChange={e => {setNewTitle(e.target.value)}} value={newTitle} />
};

export default CustomInput;

⚠️ Note that due to the nature of the Web Group App and the fact that React event handlers are always executed after the native DOM event handlers, you must use a direct reference to the DOM elements instead of React events when handling the "mousedown" and "click" events.

⚠️ Note that you shouldn't use requestPageFocus() in the window tab itself, but rather in an element it contains. If you use requestPageFocus() in the window tab, you will have to prevent the propagation of the "click" and "mousedown" events there, which will prevent the web groups framework from processing them, leading to undesirable side effects - the user won't be able to move the window tabs or even switch between them by clicking on them.

Compose a custom window tab containing the custom input field that will render conditionally:

import { useEffect, useState } from "react";
import { TabChannelSelector, TabCaption, TabCloseButton } from "@interopio/groups-ui-react";
import CustomInput from "./CustomInput";

const CustomTab = ({ channels, caption, selected, close }) => {
    const [showInput, setShowInput] = useState(false);
    const [tabTitle, setTabTitle] = useState(caption);

    useEffect(() => setTabTitle(caption), [caption]);

    return (
        <div className="custom-tab" onDoubleClick={() => setShowInput(!showInput)}>
            {channels.visible && <TabChannelSelector {...channels} />}
            {!showInput && <TabCaption caption={tabTitle} selected={selected} />}
            {showInput && <CustomInput setTabTitle={setTabTitle} />}
            <TabCloseButton selected={selected} close={close} />
        </div>
    );
};

export default CustomTab;

Replace the default <Tab /> component with the custom one:

import Group from "@interopio/groups-ui-react";
import CustomTab from "./CustomTab";

const App = () => {
    return (
        <Group
            components={{
                tabs: {
                    Element: CustomTab
                }
            }}
        />
    );
};

export default App;

Styles

To use the default styles for your custom Web Group App, import the following CSS file:

import "@interopio/groups-ui-react/dist/styles/groups.css";

The default styles for the Web Group App must not be removed, because they contain both visual and behavioral styling. To use custom styles, simply import your CSS file after the default CSS import to override it. Two default themes are available - "Day" and "Night" - and the trigger for switching between them is the class property of the <html> element - "light" for the "Day" theme and "dark" for the "Night" theme:

<!-- Day theme -->
<html class="light">

<!-- Night theme -->
<html class="dark">

⚠️ Note that when adding custom components to the Web Group App, you can use the -webkit-app-region: drag and -webkit-app-region: no-drag styles to define custom draggable areas. This is especially useful if you are adding custom content inside components that already contain draggable areas (like the <GroupCaptionBar />, <FlatCaptionBar /> or the tab header of a tabbed group) and want to include or exclude this content from the draggable area.

Available since io.Connect Desktop 9.1

The <Group /> component allows you to supply custom classes and CSS settings for some of the group elements by using its styles prop. Currently, styles can be supplied for the group frame, the tab header of a tab group, and the move area of a tab group:

import Group from "@interopio/groups-ui-react";

const App = () => {
    return (
        <Group
            styles={{
                frame: {
                    element:{
                        classes:  ["my-frame-class", "my-other-frame-class"],
                        css: {
                            borderWidth: "1px"
                            borderColor: "blue"
                        }
                    }
                },
                tabs: {
                    header: {
                        classes: ["my-tab-header-class"],
                        css: { backgroundColor: "red" }
                    },
                    moveArea: {
                        classes:  ["my-move-area-class"],
                        css: { backgroundColor: "green" }
                    }
                }
            }}
        />
    );
};

export default App;

Limitations

You should consider the following technical limitations when using the @interopio/groups-ui-react library:

  • Unless you set the "rendererTransparencyMode" property in the stickywindows.json configuration file of io.Connect Desktop to "Transparent" (usable on Windows 8+), any popups you may use must not have shadows or transparency. If "rendererTransparencyMode" is set to any other value, the shadow or the transparency will blend with the color specified in the "rendererTransparencyKeyColor" property which will result in an undesirable visual effect. Also, the color specified in "rendererTransparencyKeyColor" shouldn't be used individually or in a gradient, because it will always be rendered as transparent. For more details, see the Developers > Configuration > io.Connect Windows section.

  • Hot module reloading may not work well because of the pool with instances of the Web Group App. This pool is configurable and is necessary for ensuring smooth operation and better user experience when performing group operations.

  • It's strongly discouraged to render custom components around the <Group /> component, because most likely they will be visually cut off by the internal logic for handling the cases of irregular (non-rectangular) window groups.

  • The Web Group App doesn't have an io.Connect Window object (io.windows.my() returns undefined). See also Using io.Connect APIs in the Web Group App.

  • Refs to the custom components passed to the <Group /> component shouldn't be used in any parent component of <Group /> because the children of <Group /> aren't immediately rendered and therefore these refs won't work as expected.

The @interopio/groups-ui-react library currently doesn't support the following:

  • maximizing a window participating in a group and returning it to the group on restore;
  • expand and collapse programmatic commands and buttons;
  • focus frames for windows on focus;
  • the "sizingMode": "Single" option for resizing windows in a group;
  • the download bar isn't customizable;