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",
    "appearance" : {},
    "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.
        }
    ]
}

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

tabAlignment

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

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

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 or icon properties, since these relate to individual tab instances.

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 selected, 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 shown on the tab indicator.
        "icon": {},     // Icon config object.
        "style": {},    // Sets additional style for this indicator. (e.g. Different font or color)
        "selector": {   // Settings relating to the selected tab
            "line": {
                "color": "red",
                "alignment": "bottom",
                "hidden": false
            },
            "style": {
                "color": "yellow"
            }
        }
    }
}
Name Description

title

Title of the tab in the indicator bar. Only relevant at tab level. If provided overrides the name field.

icon

Only relevant at tab level. Used to display an optional icon for the tab.

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

style

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

selector

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

  • line: Configure the color and alignment ("top", "bottom", "left", "right") of a line drawn on an edge of the selected tab. 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.

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

  • style: Provide style settings specific to the selected tab, such as font and background color settings.

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.

Actions

Supported action hooks:

  • onActiveTabChanged: Triggers 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 tab selected.

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

Example opening a link:

{
    "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 widget 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"
            }
        ]
    }

Send Actions

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

  • setActiveTab: Activate a specific tab based on the message payload parameters provided:

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

    Active a specific tab:

    {
        "type": "send",
        "to": "tabs01",
        "message": {
            "topic": "setActiveTab",
            "payload": {
                "id": "tab02" // activate is not provided
            }
        }
    }
  • addTab: Add a new tab to the tabs-array. By using a send rather than a modify action 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 send 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

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 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 either:

  • Manually add the widget JSON to the widgets array in the tab.compilation property:

    • Optionally, copy JSON for the new widget from a template or some other existing widget of the same type.

    • Locate the widgets array in the compilation property of the tab and insert the copied JSON as a new array element. Alternatively create a new blank element and set the type property to the desired value, then use code completion to further configure the model.

    • Update the id for the new widget.

  • Edit the compilation interactively in a new browser window:

    • Copy the JSON of the compilation property into a new blank WebStudio browser tab.

    • Add new widgets interactively

    • Copy the modified JSON back

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 that have their layout.static property set to false, whether they are in the initial model or were dynamically loaded, can be moved and resized, 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 caption bar edit-buttons {} 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"
}

In the context of tab compilations there are two 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 or false: The parent setting is ignored in favour of the locally applied value. If the tab in turn contains its own tabs widgets, this values is pushed down to the nested compilations, where the same logic as described here is applied.