Tabs

With this widget you can show different widgets in the same space by switching between tabs in a given browser tab. Each tab holds a complete 'sub' compilation.

Once you are familiar with the model details, be sure to also refer to the creating and editing tab content section.

Model

{
    "type": "tabs",
    "actions": {},
    "appearance" : {},
    "dataSource": {},
    "options" : {},
    "tabs": [                   // List of tabs. View order depends on the index in the array.
        {
            "id": "",           // Unique id of the tab.
            "name": "",         // Name of the tab as shown on the tab when there is no indicator title.
            "indicator": {},    // Configure the appearance of the tab's indicator. see details below
            "compilation": {}   // Like the a normal WebStudio compilation.
        }
    ],
    "toolbars": {}
}
Field Description

actions

Used to implement action pipelines for selected trigger events

appearance

Configures the appearance of the tabs widget, tabs can be "docked" (default) or "floating"

dataSource

Property used to retrieve data from the system. The data returned must be either a tabs array or a tabs object. See the data sources section for more information.

options

In addition to the default options, the tabs widget supports extra options. See the options section for more detail.

tabs

List of tabs, used to define the tab content

toolbars

Add, customise or hide toolbars. For more information on tab tools, see floating developer tools.

More details for the nested properties are provided below:

Appearance

This property controls the appearance of tab content to be either docked or floating. It has the following fields:

Name Description

type

Switches the tabs widget appearance between:

  • docked: This is the default. In docked mode, the individual tab panels are shown in-line in the compilation occupying the widget area not covered by the tabs-indicator.

  • floating: In this mode, only the tab-indicator bar appears directly in the compilations. The tab-content remains hidden until the tab is activated, causing a pop-up window to be shown over the compilation grid.

onScroll

Determines the compilation overall scroll behavior while a floating tab is visible. The options are:

  • dismiss: The floating tab is dismissed/hidden when the underlining compilation is scrolled up or down.

  • prevent: The compilation will not respond to scroll attempts. Once the floating tab is closed, the normal scrolling behavior will be available again. Note: this feature is still somewhat experimental. If multiple floating tabs are visible, which prevent scrolling and only one of these is closed, the compilation will become scrollable again.

Additional considerations for floating tabs

  • Showing a tab panel: A floating tab is displayed when it is activated, either by clicked on its indicator or it receiving an activate message. Only one tab in the tabs-set can be visible at a time. Consequently, the currently visible tab is replaced by the next one selected.

  • Hiding a tab panel: A visible tab panel can be hidden in the following ways:

    • Click on its indicator a second time.

    • Send the tabs a setActiveTab message using the id of the active tab. This is the equivalent of clicking on the indicator.

    • Send the tabs a setActiveTab message setting the activate property to "none"

  • Panel size: The size of the floating tab panel is determined by setting the width and height in each tab’s compilation.options property to the desired number of pixels. The panel size can be be different for each tab.

  • Popup location: All floating tabs are drawn next to the first tab indictor at the side opposite to the tabAlignment. For example, if the tabAlignment is set to top, the pop-up panel is shown below the first tab, with the left edges aligned.

Options

The options property controls the overall appearance of the tabs indicator:

{
    "options": {
        "tabAlignment": "top",  // Edge to show the tab indicator at
        "showTabBar": true,     // Show or hide the tab indicator bar
        "indicator": {},        // General indicator settings
        "tabBar": {}            // Tab bar style setting
    }
}
Name Description

indicator

The indicator settings provide a means to configure the appearance of the tab selector in the tab bar. Overall settings can be defined at the tabs level or instance specific settings can be supplied per tab. Note: The indicator setting at tabs level does not support title, tooltip or icon properties, since these relate to individual tab instances.

closeButton

Optional configuration to display a button in the tab indicator which allow tabs to be closed. This property is only applied for docked tabs.

enable

Set to true to enable docked tabs to be closed. Default is false.

`closeTab` feature enabled

selector

Optional properties to configure the appearance of the active tab indicator. Configuring this at the tabs level will apply the properties to any active tab instance in the tabs widget. Can also be configured at the individual tab level.

line

Configure the color and alignment of the line drawn on the active tab indicator. This line can also be hidden by setting the line.hidden property to true.

style

Optional styling for the active tab indicator.

styleByTheme

Optional theme-specific styling for the active tab indicator.

style

Optional styling for the tab indicator on all tab instances in the tabs widget

styleByTheme

Optional theme-specific styling for the tab indicator on all tab instances in the tabs widget

showTabBar

The tab-bar can be explicitly hidden. This is useful to maximize the available screen area available to the contained compilations. If the tab bar is hidden, different tab instances can still be selected using send actions as explained below

tabAlignment

Show the tab indicators at any of the widgets edges ("top", "bottom", "left", "right").

tabBar

Use the tabBar property to apply custom style and styleByTheme settings to the area of the tab bar not occupied by the selectable indicators.

Tab Indicator

The appearance of the indicator, used to show which tab is active, can be configured at the tabs level, in which case it applies to all tab instances, or at the individual tab level.

Note: The indicator element of the tab model is optional. If omitted, the indicator title will be set to the value of the tab name field.

{
    "indicator": {
        "title": {},    // Title configuration.
        "icon": {},     // Icon config object.
        "style": {},    // Sets additional style for this indicator. (e.g. Different font or color)
        "selector": {   // Settings relating to the active tab
            "line": {
                "color": "red",
                "alignment": "bottom",
                "hidden": false
            },
            "style": {
                "color": "yellow"
            }
        },
        "tooltip": "Tab 1"
    }
}
Name Description

title

Configuration of the title of the tab indicator. Only relevant at tab level. If provided overrides the name field.

style

Optional styling configuration for the indicator title

styleByTheme

Optional theme-specific styling for the indicator title

text

Text for the tab indicator title

icon

Only relevant at tab level. Used to display an optional icon for the tab. The icon, dark and light properties can support an emoji character or a custom graphics element (base64, mimeType, url).

Tab specific icons can be provided in addition to, or instead of, indicator titles. Set the indicator.title.text property to an empty string ("") if only the icon should be displayed

alignment

If the icon is provided in addition to the indicator title, this property can be used to configure the position of the icon in the tab indicator. Options are:

  • "centerBottom": icon is displayed at the center of the bottom of the tab indicator, below the indicator title

  • "centerTop": icon is displayed at the center of the top of the tab indicator, above the indicator title

  • "leading": icon is displayed before the indicator title (default)

  • "trailing": icon is displayed after the indicator title

dark

Configure an icon for the tab indicator when WebStudio is in dark mode.

icon

Property used to configure the icon displayed on the tab indicator.

light

Configure an icon for tab indicator when WebStudio is in light mode.

style

Provide style specific settings to the icon in the tab indicator

styleByTheme

Configure theme-specific styling to the icon in the tab indicator.

style

Set the "base" appearance of indicators independent of their active state. Settings defined in the selector section will override these.

selector

Configure the appearance of the active tab’s indicator. When defined at the tabs level, the settings are applied to any active tab instances. The selector has the following sub-properties:

icon

Configuration for an optional icon to be displayed on the tab indicator when the tab is active. The icon, dark and light properties can support an emoji character or a custom graphics element (base64, mimeType, url)

alignment

If the icon is provided in addition to the indicator title, this property can be used to configure the position of the icon in the tab indicator. Options are:

  • "centerBottom": icon is displayed at the center of the bottom of the tab indicator, below the indicator title

  • "centerTop": icon is displayed at the center of the top of the tab indicator, above the indicator title

  • "leading": icon is displayed before the indicator title (default)

  • "trailing": icon is displayed after the indicator title

dark

Configure an icon for the tab indicator on the active tab when WebStudio is in dark mode.

icon

Property used to configure the icon displayed on the tab indicator when the tab is active.

light

Configure an icon for the tab indicator on the active tab when WebStudio is in light mode.

line

Configure the color and alignment of the line drawn on an edge of the active tab.

alignment

The position of the line drawn on the edge of the active tab can be configured using the alignment property. Options are bottom, left, right and top. For tab indicators at the top and bottom of the widget, the default line position will be on the inside edge of the tab canvas. When the indicator bar is at the left or right edges, the line defaults to the outside edges of the widget.

color

The color property is used to configure the color of the line drawn on an edge of the active tab.

hidden

The line can be omitted using the hidden property. Note: If the line is hidden, the active tab will be shown with a different background color to allow it to be easily identified. The default color can be overridden in the style property

style

Provide style settings specific to the active tab, such as font and background color settings.

styleByTheme

Provide theme-specific styling to the active tab’s indicator

tooltip

Configure a tooltip to be displayed when hovering over the tab indicator.

Data Sources

As with other widgets, model content can be loaded from a data source. The tabs widget is somewhat unique in this regard, since it provides dataSource properties at both the tabs level and at individual tab level.

Typically the data source will be an advanced endpoint used to call a Lua function in the backend. The script needs to return a Lua table conforming to the model expected by the widget. As always, the returned Lua table is transparently translated to json.

  • Tabs dataSource: The number of tabs returned and their appearance will be under the control of the dataSource. The data returned must be one of the following:

    • Tabs array: An array of objects each element of which defines a tab object as shown above. The compilation content of the tab can be included in the returned dataset, but may be left blank {}, if the tab in question has its own dataSource binding. If the model returned contains data sources at tab level, these are evaluated immediately after the top level update has been done.

    • Tabs object: A single object, containing a field called type with value tabs. In this scenario, valid root level properties present in the data-source are added into the work model. Existing work model settings will be overwritten. Exceptions are the id, captionBar and layout properties, which cannot be overwritten in the work model.

  • Tab dataSource: The tab level dataSource can return one of the following:

    • Compilation object: A single object containing a valid compilation. WebStudio looks for the presence of the version (string) and widgets (array) properties to determine if the returned object is a compilation. If either of these is not present, WebStudio will treat the object as a tab object. For example, if the following JSON is returned, WebStudio knows to assign the data to the compilation property of the tab:

      {
          "version": "1",
          "widgets": [
              {
                  "type" : "text",
                  ...
              }
          ]
      }
    • Tab object: An object with named fields of a tab. Any of the fields can be provided except for the id which cannot be overwritten by the data source at tab level. In other words, this option applies when the returned data is determined not to be a compilation object. Fields that are invalid for a tab instance are ignored. The example below does essentially the same at the previous one, but in this case the compilation field is explicitly set and we have the option to override some of the other tab properties:

      {
          "indicator": {
              "title": "New title"
          },
          "compilation" : {
              "version": "1",
              "widgets": [
                  {
                      "type" : "text",
                      ...
                  }
              ]
          }
      }

      Note: The parent tabs data source can define any field of a tab including the tab-id.

If the dataSource is defined as an object, the message returned by the data is determined by whether the type field matches the widget type. If the type defined in the payload does match the widget type, then any matching model properties from the payload will be merged to the model. However, if the type field does not match or it is not specified, then the tabs field is checked for in the payload and merged into the model. If the type and tabs fields are not defined, then no fields from the payload will be merged to the model.

If the dataSource is defined as an array, then the content of the payload is merged to the tabs property in the model.

DataSource Pipeline

DataSource Examples

The sections below contain examples of the message payload returned by the dataSource on the Tabs widget and the expected merge behavior when applied to the model.

Message payload defined as an object where the type field matches the widget type

Suppose that the dataSource returns the following message payload, where the type defined in the payload matches the widget type. In this case, any matching model properties in the payload will be merged to the model, namely the tabs, options and appearance fields in the following example.

{
    "payload": {
        "type": "tabs",
        "tabs": [
            {
                "id": "tab01",
                "name": "Tab01",
                "compilation": {
                    "version": "1",
                    "widgets": [
                        {
                            "type": "button",
                            "name": "Button",
                            "description": "Button",
                            "label": "Click Here",
                            "actions": {
                                "onClick": {
                                    "type": "notify",
                                    "title": "Button is clicked"
                                }
                            },
                            "layout": {
                                "x": 0,
                                "y": 0,
                                "w": 29,
                                "h": 28,
                                "static": true
                            },
                            "id": "lpwZ"
                        }
                    ],
                    "options": {
                        "stacking": "vertical",
                        "numberOfColumns": 96,
                        "padding": {
                            "x": 0,
                            "y": 2
                        },
                        "spacing": {
                            "x": 2,
                            "y": 2
                        }
                    }
                }
            }
        ],
        "options": {
            "tabAlignment": "left"
        },
        "appearance": {
            "type": "floating"
        }
    }
}

Message payload defined as an object where the type field is not specified but the tabs field is included

Suppose that the message payload returned by the dataSource does not contain a type field. However, as the tabs field is defined, this will be the only field from the payload which is merged to the model.

{
    "payload": {
        "tabs": [
            {
                "id": "tab01",
                "name": "Tab01",
                "compilation": {
                    "version": "1",
                    "widgets": [],
                    "options": {
                        "stacking": "vertical",
                        "numberOfColumns": 96,
                        "padding": {
                            "x": 0,
                            "y": 2
                        },
                        "spacing": {
                            "x": 2,
                            "y": 2
                        }
                    }
                }
            },
            {
                "id": "tab02",
                "name": "Tab02",
                "compilation": {
                    "version": "1",
                    "widgets": [
                        {
                            "type": "text",
                            "name": "Simple Text",
                            "description": "Simple Text",
                            "text": "Your text here",
                            "captionBar": {
                                "hidden": false,
                                "title": "Simple Text Widget"
                            },
                            "layout": {
                            "x": 0,
                            "y": 0,
                            "w": 32,
                            "h": 32
                            },
                            "id": "pNkA"
                        }
                    ],
                    "options": {
                        "stacking": "vertical",
                        "numberOfColumns": 96,
                        "padding": {
                            "x": 0,
                            "y": 2
                        },
                        "spacing": {
                            "x": 2,
                            "y": 2
                        }
                    }
                }
            }
        ]
    }
}

Message payload defined as an object but the type and tabs fields are not specified

Consider the following example where the message payload returned by the dataSource does not contain a type or tabs field. In this case, no fields from the payload will be merged to the model.

{
    "payload": {
        "options": {
            "tabAlignment": "left"
        },
        "appearance": {
            "type": "floating"
        }
    }
}

Actions

The actions property for the tabs widget can be used to invoke pipelines for specific action hooks.

Action Hooks

In addition to the general widget action hooks, the tabs widget also supports the following action hook:

onActiveTabChanged

The onActiveTabChanged action hook is invoked when:

  • Clicking on an unselected indicator or activation from an action in a pipeline.

  • For fixed tabs, that is non-floating ones, the hook is also invoked immediately after the widget is loaded, since these always have one active tab.

  • For floating tabs, the hook is invoked both when the tab panel is shown or hidden.

Example:

{
    "type": "tabs",
    "name": "Tabs",
    "description": "Empty Tabs",
    "tabs": [
        {
            "id": "tab01",
            "name": "Tab01",
            "indicator": {
                "title": "Tab 01"
            },
            "compilation": {}
        },
        {
            "id": "tab02",
            "name": "Tab02",
            "indicator": {
                "title": "Tab 02"
            },
            "compilation": {}
        }
    ],
    "actions": {
        "onActiveTabChanged": {
            "type": "notify",
            "text": "The active tab changed"
        }
    },
    "id": "tabs01"
}

Changing the selected tab in a pipeline

Modify actions

A tab can be dynamically changed by means of a modify action. Depending on the route the behavior is:

route contains results in

ID of the tabs

refresh of all the tabs

ID of the tabs and the tab

refresh of that particular tab

ID of the tabs, the tab and the widget

refresh of the widget on that particular tab

This pattern for the route continues for nested tabs widgets within tabs.

Examples:

Change the tab indicator alignment:

{
    "type": "modify",
    "id": { // even though we are not modifying anything on the tab instances,
            // they are all refreshed by modifying a property at tabs level!
        "route": [ "tabs01" ]
    },
    "set": [
        {
            "name": "model.options.tabAlignment",
            "value": "left"
        }
    ]
}

Change the indicator color of "tab01". Only the targeted tab will be updated.

{
    "type": "modify",
    "id": { // only tab01 will be refreshed.
        "route": [ "tabs01", "tab01" ]
    },
    "set": [
        {
            "name": "model.indicator.style.color",
            "value": "yellow"
        }
    ]
}

To change the property of a widget inside the compilation of a tab, say the color of a text widget, you might be tempted to do something like this:

{
    "type": "modify",
    "id": {
        "route": [
            "tabs01",
            "tab01"
        ]
    },
    "set": [
        { // Access a widget by its array index in the compilation.
          // This is not very convenient and can lead to unpredictable
          // results if the position of the widget were to change in the array.
            "name": "model.compilation.widgets.0.options.style.color",
            "value": "yellow"
        }
    ]
}

A much better way of achieving the same is by specifying the id of the target widget in the route expression, which automatically interprets any id following the tab id as that of a widget in its compilation. Not only does it make the expression simpler and more robust, but it also ensures that only the targeted widget is updated instead of the whole tab compilation.

{
    "type": "modify",
    "id": {
        "route": [
            "tabs01",
            "tab01",
            "text01"
        ]
    },
    "set": [
        {
            "name": "model.options.style.color",
            "value": "yellow"
        }
    ]
}

Receive messages (Send Topics)

The tabs widget responds to the following message topics in a send action:

addTab

The addTab topic can be used to add a new tab to the tabs array. By using a send action rather than modify, it ensures that only the added instance is refreshed and not the tabs widget as a whole. The model for the new tab must be provided in the payload of the message received by the action. For example:

{
    "type": "send",
    "to": "tabs01",
    "message": {
        "topic": "addTab",
        "payload": {
            "tab": {
                "name": "New Tab",
                "indicator": {
                    "title": "New Tab"
                },
                "compilation": {
                    "version": "1",
                    "widgets": [
                        {
                            "type": "text",
                            "text": "Text",
                            "captionBar": true,
                            "layout": {
                                "x": 1,
                                "w": 7,
                                "h": 4,
                                "y": 1
                            }
                        }
                    ]
                }
            },
            "activateTab": true
        }
    }
}

The payload fields used by the addTab topic are:

Field Description

tab

Compulsory field used to define the tab model.

activateTab

Optional field used to immediately activate the newly added tab. The default value for this field is false.

Note that if the appearance.type of the tabs widget is "docked" and the newly added tab is the first one to be added then the tab gets activated, regardless of the value of activateTab.

Note: The selected tab is exposed in the work model by the state.activeTabId field

setActiveTab

The setActiveTab topic is used to activate a specific tab. This is configured in the message payload usign the following parameters:

Field Description

activate

Optional field used to control which tab should be selected. Options are:

  • first or last

  • next or previous: Activate the tab after/before the currently active one. The order is defined by the position of the tab in the tabs list of the model.

  • nextWithRotate or previousWithRotate: Wrap round if the current active tab is the last or first one in the list.

  • none: Only affects floating tabs, causing the active tab to be deactivated/hidden. An alternative way to hide the active floating tab is to use a dismiss action triggered from a widget inside the floating tab.

id

Causes the tab with the specified id to be activated when used without providing an activate value. When used in conjunction with activate, determines the tab from which the relative position is calculated.

Examples:

Move to the next tab in the tabs array:

{
    "type": "send",
    "to": "tabs01",
    "message": {
        "topic": "setActiveTab",
        "payload": {
            "activate": "next" // move relative to the current active tab
        }
    }
}

Activate a specific tab:

{
    "type": "send",
    "to": "tabs01",
    "message": {
        "topic": "setActiveTab",
        "payload": {
            "id": "tab02" // activate is not provided
        }
    }
}

Creating and editing tab content

While being able to create and display nested compilations in the tabs widget adds a lot of flexibility to WebStudio, it also introduces some complexities that the "root" compilation doesn’t need to contend with. What these are and how we deal with them is the subject of this section.

As you will have seen when adding a new template tabs widget to your compilation, it start with two empty tab instances. The obvious question now arises "How do I configure the compilation content for each tab?", since at present, widgets can only be added interactively at root level. The best answer to this depends on how you intend to use the tabs widget. Let’s consider two scenarios:

Creating a static tab compilation

If the content of each tab will be static, in that it is not dependent on run-time state and should be loaded with the main compilation rather than by the dataSource, the easiest way to get going is to create the content in a new browser tab and then copy the JSON to the tab.compilation property.

Once loaded, the nested widgets can be edited as usual, by clicking on the edit icon {} in the widget caption bar, dragging them to the desired position in the panel or resizing them using the resize handle in the bottom right corner. Changes made in this way are written back to the initial model.

If you need to add additional widgets to the tab at some later stage, you can use the Add Widget button AddWidget in the Floating Developer Tools.

Dynamic tabs

When the content of a tab instance is loaded using a dataSource, the compilation value is typically sourced from the back end where it may be dynamically created or read from an object property. In the latter case, the compilation JSON will often have been created in WebStudio, like any other "root" compilation.

Widgets added at runtime, whether by a dataSource or via a modify action, cannot be interactively edited since they do not reside in the initial model. The widget JSON is still shown when the {} icon is clicked, but the editor will be in "read-only" mode.
Nested widgets, whether they are in the initial model or were dynamically loaded, can be moved and resized provided that the widget caption bar is displayed, but the changes are only persisted for the ones that are in the initial model.

showDevTools

As is the case with the root compilation, the edit widget model tool {} of widgets in a tab panel, can be shown or hidden using the options.showDevTools property.

{
    "type": "tabs",
    "tabs": [
        {
            "id": "tab01",
            "indicator": {
                "title": "Tab 01"
            },
            "compilation": {
                "version": "1",
                "widgets": [],
                "options": {
                    "showDevTools": true
                }
            }
        }
    ],
    "id": "tabs01"
}
If the showDevTools property is set to false on the root compilation, then this property will be inherited by the tabs widget and any widgets in the tab compilation. The showDevTools property cannot be overridden in a tabs compilation in this case.

If the showDevTools property is not specified or is set to true at the root compilation, then, in the context of tab compilations, there are three scenarios to consider:

  • showDevTools is not set: This is the recommended option, since it allows the showDevTools value to be inherited from the parent compilation the tabs widget resides in.

  • showDevTools is true: The developer tools indicator on the widgets in the tabs compilation will be visible.

  • showDevTools is false: The developer tools indicator on the widgets in the tabs compilation will be hidden.

    If showDevTools is set to false for a specific tab, the developer tool indicator will be hidden on all widgets in that tab. However, the tab compilation can still be accessed through the widget model editor of the parent widget if its dev tools are enabled.

If a tabs compilation contains its own tabs widget, then the showDevTools property is pushed down to the nested tabs compilations, unless otherwise specified.

Floating Developer Tools

For the tabs widget, the floating developer tools feature makes adding content and navigation between tabs easier. To access the floating developer tools, ensure that the DevToolsBtn icon has been selected in the WebStudio tool bar and hover over the tabs widget to display the developer tools indicator DevToolsIndicatorBtn.

  • If the caption bar is displayed, this indicator will be shown on the right-hand side of the bar.

  • If the caption bar is hidden, the indicator will appear in the top right-hand corner of the tabs widget.

Hovering over the indicator icon will display the floating developer toolbar for the tabs widget:

Tabs Widget - Floating Developer Tools

The left-aligned icons can be used to edit the selected tab.

Icon Description

AddWidgetBtn

Add widgets to the selected tab.

EditCompilationBtn

Opens the Model Editor for the selected tab.

The right-aligned icons are used to edit the tabs widget.

Icon Description

ChangeTabBtn

Switch to the next tab.

AddWidgetBtn

Opens the Model Editor containing a template for an empty tab which can be added to tabs widget.

{
    "name": "New Tab",
    "compilation": {
        "version": "1",
        "widgets": [],
        "options": {
            "stacking": "vertical",
            "numberOfColumns": 96,
            "padding": {
                "x": 0,
                "y": 2
            },
            "spacing": {
                "x": 2,
                "y": 2
            }
        }
    }
}

DeleteTabBtn

Removes the active tab from the widget and deletes the tab from the work model.

EditCompilationBtn

Opens the Model Editor for the tabs widget.

If multiple developer tool indicators are overlapping, hovering over the innermost indicator will show the floating developer toolbars for each overlapping widget or tab.

Nested Floating Developer Toolbars

If the caption bar of the widget is hidden then the 'drag handle' is added to the floating developer toolbar.

Drag Handle in Floating Developer Toolbar for Tabs Widget