To consume the UI Modifications (UIM) API, your app needs to import the @forge/jira-bridge
package:
1 2import { uiModificationsApi } from '@forge/jira-bridge';
This API is strictly related to the Jira UI modifications module
1 2onInit(<initCallback>, <registerFieldsCallback>): void
initCallback
is where apps can query field/screen tab data and request modifications. registerFieldsCallback
is used to specify which fields will be changed by initCallback
. The information returned by registerFieldsCallback
is used to display a loading indicator next to the relevant fields until initCallback
finishes. There is no need to register screen tabs in order to modify them.
Both callback functions are called every time:
project
or issueType
(from context) changescreate another issue
checkbox enabledThe initCallback
function will receive one object as an argument. This object contains the following attributes:
api
- the API used to find and modify fields and/or screen tabsuiModifications
- an array of UIM entities registered for a given contextThe registerFieldsCallback
function will receive an object containing:
uiModifications
- an array of UIM entities registered for a given context1 2uiModificationsApi.onInit(({ api, uiModifications }) => { // You can find form fields using the FieldsHookApi const summary = api.getFieldById('summary'); // You can manipulate the fields you found if (summary) { summary.setDescription('New summary description'); } // You can find screen tabs using the ScreenTabsHookApi const tabs = api.getScreenTabs(); // You can manipulate the tabs you found if (Array.isArray(tabs) && tabs.length > 0) { tabs[0].setVisible(true); tabs[0].focus(); } }, ({ uiModifications }) => { // This function should return an array of the IDs of the fields // which are going to be changed in initCallback return ['summary']; })
All modifications requested synchronously using this API will be batched and applied at once.
That means that consecutive modifications applied by the same FieldAPI/ScreenTabAPI
method within the onInit
callback will override the previous ones.
1 2uiModificationsApi.onInit(({ api }) => { const { getFieldById } = api; const summary = getFieldById('summary'); summary.setDescription('First description'); // will be overridden by the next `summary.setDescription` call summary.setDescription('Second description'); // this change will be applied }, ({ uiModifications }) => { return ['summary']; })
If a Promise
is returned from the callback, all of the modifications will be postponed until the Promise
resolves.
This may be useful in a scenario when the app needs to perform an async operation before it requests a change.
UIM blocks any changes requested for fields within initCallback
for any field IDs not in the array returned from registerFieldsCallback
.
If the initCallback
doesn’t return a Promise
which resolves after all field and tab modification requests are made, then field and tab modifications requested asynchronously will be ignored.
1 2onChange(<changeCallback>, <registerFieldsCallback>): void
changeCallback
is where apps can query field/screen tab data and request modifications. registerFieldsCallback
is used to specify which fields will be changed by changeCallback
. The information returned by registerFieldsCallback
is used to display a loading indicator next to the relevant fields until changeCallback
finishes. There is no need to register screen tabs in order to modify them.
Both callback functions are called every time:
blur
field event is triggered by one of the supported text fields, which are summary
and description
change
field event is triggered by any other supported field (for select-type fields when the user picks an option; for Issue view when the user commits a value)The changeCallback
function will receive one object as an argument.
This object contains the following attributes:
api
- the API used to find and modify fields and/or screen tabschange
- an object with the current
attribute containing the object exposing the FieldAPI
of the field which triggered the change eventuiModifications
- an array of UIM entities registered for a given contextThe registerFieldsCallback
function will receive an object containing:
change
- an object with the current
attribute containing the object exposing the FieldAPI
of the field which triggered the change eventuiModifications
- an array of UIM entities registered for a given context1 2uiModificationsApi.onChange(({ api, change, uiModifications }) => { // You can find form fields using the FieldsHookApi // You can also manipulate the fields you found or access the field // that triggered the change const { current } = change; if (current.getId() === 'summary') { // hint: You may want to read the content of uiModifications before // deciding what changes need to be applied here current.setDescription('New summary description'); } // You can find screen tabs using the ScreenTabsHookApi const tabs = api.getScreenTabs(); // You can manipulate the tabs you found if (Array.isArray(tabs) && tabs.length > 0) { tabs[0].setVisible(true); tabs[0].focus(); } }, ({ change, uiModifications }) => { // This function should return an array of the IDs of the fields // which are going to be changed in changeCallback return ['summary']; })
All modifications requested synchronously using this API will be batched and applied at once.
That means that consecutive modifications applied by the same FieldAPI/ScreenTabAPI
method within the onChange
callback will override the previous ones.
1 2uiModificationsApi.onChange(({ api }) => { const { getFieldById } = api; const summary = getFieldById('summary'); summary.setDescription('First description'); // will be overridden by the next `summary.setDescription` call summary.setDescription('Second description'); // this change will be applied }, ({ uiModifications }) => { return ['summary']; })
If a Promise
is returned from the callback
, all of the modifications will be postponed until the Promise
resolves. This may be useful in a scenario when the app needs to perform an async operation before it requests a change.
UIM blocks any changes requested for fields within changeCallback
for any field IDs not in the array returned from registerFieldsCallback
.
If the changeCallback
doesn’t return a Promise
which resolves after all field modification requests are made, then field modifications requested asynchronously will be ignored.
1 2onError(<errorCallback>): void
An errorCallback
is where apps can process information about the errors that happened during the execution of UI modifications. The callback function is called when:
validation error
occurs, which means data passed to one of the FieldAPI or ScreenTabAPI methods was invalidconflict error
occurs, which means multiple apps attempted to call the same FieldAPI or ScreenTabAPI method on the same field or tabThe errorCallback
function will receive one object as an argument.
This object contains the following attributes:
errors
- the array of objects with error data1 2uiModificationsApi.onError(({ errors }) => { // you can process errors' data in the errorCallback for (const error of errors) { if (error.type === 'FIELD_VALIDATION_FAILED') { logFieldValidationError(error); } if (error.type === 'SCREENTABS_VALIDATION_FAILED') { logScreenTabsValidationError(error); } if (error.type === 'MULTIPLE_APPS_CONFLICT') { logConflictError(error); } } })
1 2{ type: 'FIELD_VALIDATION_FAILED', fieldId: string, fieldType: string, method: string, // for example: setRequired message?: string; // additional information about the error }
1 2{ type: 'SCREENTABS_VALIDATION_FAILED', message?: string; // additional information about the error }
1 2{ type: 'MULTIPLE_APPS_CONFLICT', cause: 'FIELD', fieldId: string; fieldType: string; lifecycleHook: 'onInit' | 'onChange'; method: string, // for example: setValue message: string; // additional information about the error }
1 2getFieldById<fieldType>(<fieldId>): FieldAPI | undefined
Uses the generic: fieldType
(string) - the type of the field that the app wants to work on.
Accepts one argument: fieldId
(string) - the ID of the field which the app wants to work on.
Returns the FieldAPI object if the field exists on the supported view.
Returns undefined
if the field doesn't exist or isn’t supported by UIM.
Example of usage with regular field:
1 2uiModificationsApi.onInit(({ api }) => { const { getFieldById } = api; const description = getFieldById('description') // You can perform operations on 'description' // field using the FieldAPI here }, () => ['description'])
Example of usage with custom field:
1 2uiModificationsApi.onInit(({ api }) => { const { getFieldById } = api; const select = getFieldById<'com.atlassian.jira.plugin.system.customfieldtypes:select'>('customfield_10035') // You can perform operations on 'customfield_10035' // single select field using the FieldAPI here }, () => ['customfield_10035'])
1 2getFields(): FieldAPI[]
Returns an array of FieldAPI objects with all supported fields available on a current view form.
Example of usage:
1 2uiModificationsApi.onInit(({ api }) => { const { getFields } = api; const fields = getFields(); // You can iterate over fields // and perform operations on each // field using the FieldAPI here }, () => [])
UIM supports some custom fields types - these are listed in the tables below. However, there is no support for Forge custom fields.
Field / methods | setName getName | setDescription getDescription | setVisible isVisible | setValue getValue | setReadOnly isReadOnly | setRequired isRequired | setOptionsVisibility getOptionsVisibility |
---|---|---|---|---|---|---|---|
affects versions | |||||||
assignee | |||||||
components | |||||||
checkboxes | |||||||
date picker | |||||||
date time picker | |||||||
description | |||||||
due date | |||||||
fix versions | |||||||
issue type | |||||||
labels | |||||||
multi select | |||||||
multi user picker | |||||||
number | |||||||
paragraph | |||||||
parent | |||||||
people | |||||||
priority | |||||||
radio buttons | |||||||
reporter | |||||||
single select | |||||||
summary | |||||||
target start | |||||||
target end | |||||||
text field | |||||||
url | |||||||
user picker |
This section describes a Forge preview feature. Preview features are deemed stable; however, they remain under active development and may be subject to shorter deprecation windows. Preview features are suitable for early adopters in production environments.
We release preview features so partners and developers can study, test, and integrate them prior to General Availability (GA). For more information, see Forge release phases: EAP, Preview, and GA.
Field / methods | setName getName | setDescription getDescription | setVisible isVisible | setValue getValue | setReadOnly isReadOnly | setRequired isRequired | setOptionsVisibility getOptionsVisibility |
---|---|---|---|---|---|---|---|
affects versions | |||||||
assignee | |||||||
components | |||||||
checkboxes | |||||||
date picker | |||||||
date time picker | |||||||
description | |||||||
fix versions | |||||||
labels | |||||||
multi select | |||||||
multi user picker | |||||||
number | |||||||
paragraph | |||||||
parent | |||||||
people | |||||||
priority | |||||||
radio buttons | |||||||
reporter | |||||||
single select | |||||||
status | |||||||
summary | |||||||
text field | |||||||
url | |||||||
user picker |
This section describes a Forge preview feature. Preview features are deemed stable; however, they remain under active development and may be subject to shorter deprecation windows. Preview features are suitable for early adopters in production environments.
We release preview features so partners and developers can study, test, and integrate them prior to General Availability (GA). For more information, see Forge release phases: EAP, Preview, and GA.
Field / methods | setName getName | setDescription getDescription | setVisible isVisible | setValue getValue | setReadOnly isReadOnly | setRequired isRequired | setOptionsVisibility getOptionsVisibility |
---|---|---|---|---|---|---|---|
assignee | |||||||
cascading select | |||||||
checkboxes | |||||||
date picker | |||||||
date time picker | |||||||
description | |||||||
affects versions | |||||||
fix versions | |||||||
issue type | |||||||
labels | |||||||
multi select | |||||||
number | |||||||
paragraph | |||||||
parent | |||||||
priority | |||||||
radio buttons | |||||||
reporter | |||||||
resolution | |||||||
single select | |||||||
text field | |||||||
url | |||||||
user picker | |||||||
original estimate |
The changes requested by setters in this API aren't applied immediately.
They're batched and only applied after the execution of the onInit
or onChange
callback ends.
This means that reading the values using getters will always provide the initial form field state, which is immutable.
1 2getId(): string
Returns the field's ID.
1 2getType(): FieldType
Returns the field's type.
1 2setName(value: string): FieldAPI
Changes the field's name.
Example:
1 2field.setName('New name for the field');
1 2getName(): string
Returns the field's name.
1 2setDescription(value: string): FieldAPI
Changes the field's description.
Example:
1 2field.setDescription('This the description!');
1 2getDescription(): string
Returns the field’s description.
1 2setVisible(value: boolean): FieldAPI
Changes field visibility. The form payload will contain the field’s value, but the field won’t be visible in the UI if this is set to false
.
Example:
1 2field.setVisible(false);
1 2isVisible(): boolean
Returns true
if the field is currently visible. Returns false
otherwise.
1 2setValue(value: unknown): FieldAPI
Set a given field's value. See the specific field value contracts in the Field details section below to make sure the changes requested by this method will be applied.
1 2getValue(): unknown
See the field contracts below to learn the shape of the data returned by each field.
1 2setReadOnly(value: boolean): FieldAPI
Set if a given field’s value is read-only. If true, the form payload will contain the field’s value, but the user won’t be able to modify the value in the UI.
Example:
1 2field.setReadOnly(true);
On the Issue view, fields that don't have a value are automatically hidden when set to read-only. This is the default behavior when the user doesn't have permission to edit.
1 2isReadOnly(): boolean
Returns true
if the field's value currently can't be modified. Returns false
otherwise.
1 2setRequired(value: boolean): FieldAPI
Set a given field as required. Fields required by system configuration can't be set to non-required. Example:
1 2field.setRequired(true);
1 2isRequired(): boolean
Returns true
if the field is currently required. Returns false
otherwise.
1 2setOptionsVisibility(options: string[], isVisible: boolean): FieldAPI
Applies a visibility rule based on a list of option ids
and isVisible
arguments for a given field's dropdown list of options.
Note that the method only allows you to either make some options visible or make some options hidden, not both.
Example:
1 2// Only display the enumerated options, others will be hidden field.setOptionsVisibility(['field-option-id-1', 'field-option-id-2'], true); // Only hide the enumerated options, others will be visible field.setOptionsVisibility(['field-option-id-1', 'field-option-id-2'], false); // Special case, hide all options in the dropdown field.setOptionsVisibility([], true);
Options hidden in the dropdown can still be selected as a field's default value or by using field.setValue
.
In Issue view and Issue transition, the number of options that can be set via the setOptionsVisibility
method can't be greater than 100 per one update.
This means that each field with setOptionsVisibility
support has its own 100 options limit.
1 2getOptionsVisibility(): OptionsVisibility | undefined
Returns an object
containing the isVisible: boolean
and options: string[]
attributes. The options
attribute contains IDs of options with modified visibility.
This method, when called within the onInit
callback, always returns undefined
, meaning the visibility of options wasn't yet modified by setOptionsVisibility
.
type: issuetype
The execution of the setValue
method will initiate a UIM context change in Global issue create (GIC).
Issue type is a part of the UIM invocation context. Changing the issue type with setValue
changes that context.
UI modifications won’t be applied to an issue type field that’s out of the UIM app invocation context.
Note that issue types have different UIM app invocation contexts, so UIM functionality may not be applied the same way, in particular when it comes to setting visibility of options.
1 2setValue(id: string): FieldAPI
1 2getValue(): IssueTypeField
1 2{ id: string, name: string, }
type: priority
1 2setValue(id: string): FieldAPI
1 2getValue(): PriorityField
1 2{ id: string, // '2' name: string, // 'High' iconUrl?: string, }
type: resolution
Use null
to unset the value.
1 2setValue(id: string | null): FieldAPI
1 2getValue(): ResolutionField
1 2{ id: string, value: string, } | null
type: summary
1 2setValue(id: SummaryField): FieldAPI
1 2getValue(): SummaryField
1 2string
type: assignee
An accountId
of "-1"
is used for the "Automatic" assignee value. null
is used for the "Unassigned" value.
For more information on the "Automatic" assignee value, see https://support.atlassian.com/jira-software-cloud/docs/who-does-the-automatic-assignee-option-assign-an-issue-to/
1 2setValue(accountId: string | null): FieldAPI
1 2getValue(): AssigneeField
1 2null | { accountId: string, }
The number of unique accountIds
that can be set via the setValue
method can't be greater than 90 per one batched update.
This means that all calls to setValue
for user-based fields performed in a single onInit
or onChange
callback are counted against this limit.
type: reporter
Use null
to unset the value.
1 2setValue(accountId: string | null): FieldAPI
1 2getValue(): ReporterField
1 2null | { accountId: string }
The number of unique accountIds
that can be set via the setValue
method can't be greater than 90 per one batched update.
This means that all calls to setValue
for user-based fields performed in a single onInit
or onChange
callback are counted against this limit.
type: labels
1 2setValue(ids: string[]): FieldAPI
1 2getValue(): LabelsField
1 2string[]
type: description
The description
field can be configured using either the rich-text "Wiki style renderer" (current default) or plain-text "Default style renderer". Each renderer requires a different value type, and apps will need to match the type they receive from getValue
in any calls to setValue
.
For more information on how to configure field renderers, please refer to https://support.atlassian.com/jira-cloud-administration/docs/configure-renderers/
1 2setValue(value: DescriptionField): FieldAPI
1 2getValue(): DescriptionField
1 2string // Plain-text editor | type ADF = { version: 1, type: 'doc', content: Node[] } // Rich-text editor (ADF format) // https://developer.atlassian.com/cloud/jira/platform/apis/document/structure/
1 2type ADF = { version: 1, type: 'doc', content: Node[] } // Rich-text editor (ADF format) // https://developer.atlassian.com/cloud/jira/platform/apis/document/structure/
type: components
This field is only available in company-managed projects.
Use []
to unset the value.
1 2setValue(ids: string[]): FieldAPI
1 2getValue(): ComponentsField
1 2[ { id: string, name: string, }, { id: string, name: string, }, ... ]
type: fixVersions
Use []
to unset the value.
1 2setValue(ids: string[]): FieldAPI
1 2getValue(): FixVersionsField
1 2[ { id: string, name: string, }, { id: string, name: string, }, ... ]
In Issue view and Issue transition, you can only set up to 100 IDs per update using the setValue
method.
This means that each supported field has its own limit of 100 IDs.
type: versions
This field is only available in company-managed projects.
Use []
to unset the value.
1 2setValue(ids: string[]): FieldAPI
1 2getValue(): VersionsField
1 2[ { id: string, name: string, }, { id: string, name: string, }, ... ]
You can only set up to 100 IDs per update using the setValue
method.
This means that each field of this type has its own limit of 100 IDs.
type: com.atlassian.jira.plugin.system.customfieldtypes:select
Use null
to unset the value.
1 2setValue(id: string): FieldAPI
1 2getValue(): SelectField
1 2null | { id: string, value: string, }
type: com.atlassian.jira.plugin.system.customfieldtypes:multiselect
Use []
to unset the value.
1 2setValue(ids: string[]): FieldAPI
1 2getValue(): MultiSelectField
1 2[ { id: string, value: string, }, { id: string, value: string, }, ... ]
In Issue view and Issue transition, you can only set up to 100 IDs per update using the setValue
method.
This means that each supported field has its own limit of 100 IDs.
Currently, cascading select field is only supported by UI modifications in Issue transition
type: com.atlassian.jira.plugin.system.customfieldtypes:cascadingselect
Use null
to unset the value.
1 2setValue(value: { parentId: string; childId: string | null; } | null): FieldAPI
1 2getValue(): CascadingSelectField
1 2null | { parent: { id: string; value: string }; child: { id: string; value: string } | null; }
type: com.atlassian.jira.plugin.system.customfieldtypes:multicheckboxes
Use []
to unset the value.
1 2setValue(ids: string[]): FieldAPI
1 2getValue(): MultiCheckboxesField
1 2[ { id: string, value: string, }, { id: string, value: string, }, ... ]
In Issue view and Issue transition, you can only set up to 100 IDs per update using the setValue
method.
This means that each supported field has its own limit of 100 IDs.
type: com.atlassian.jira.plugin.system.customfieldtypes:radiobuttons
Use null
to unset the value.
1 2setValue(id: string | null): FieldAPI
1 2getValue(): RadioButtonsField
1 2null | { id: string, value: string, }
type: com.atlassian.jira.plugin.system.customfieldtypes:textarea
1 2setValue(value: ParagraphField): FieldAPI
1 2getValue(): ParagraphField
1 2string // Plain-text editor | type ADF = { version: 1, type: 'doc', content: Node[] } // Rich-text editor (ADF format) // https://developer.atlassian.com/cloud/jira/platform/apis/document/structure/
type: com.atlassian.jira.plugin.system.customfieldtypes:textfield
1 2setValue(value: TextField): FieldAPI
1 2getValue(): TextField
1 2string // Plain-text editor
type: com.atlassian.jira.plugin.system.customfieldtypes:userpicker
Use null
to unset the value.
1 2setValue(accountId: string | null): FieldAPI
1 2getValue(): UserPickerField
1 2null | { accountId: string, }
The number of unique accountIds
that can be set via the setValue
method can't be greater than 90 per one batched update.
This means that all calls to setValue
for user-based fields performed in a single onInit
or onChange
callback are counted against this limit.
type: com.atlassian.jira.plugin.system.customfieldtypes:multiuserpicker
Use []
to unset the value.
1 2setValue(accountId: string[]): FieldAPI
1 2getValue(): MultiUserPickerField
1 2[ { accountId: string, }, { accountId: string, }, ... ]
The number of unique accountIds
that can be set via the setValue
method can't be greater than 90 per one batched update.
This means that all calls to setValue
for user-based fields performed in a single onInit
or onChange
callback are counted against this limit.
type: com.atlassian.jira.plugin.system.customfieldtypes:people
This field is available in a team-managed project only.
The people
field can be configured using either the single or multiple value field. In the case of a single value configuration, only the first value from the array provided via the setValue
will be displayed in the field.
Use []
to unset the value.
1 2setValue(accountId: string[]): FieldAPI
1 2getValue(): PeopleField
1 2[ { accountId: string, }, { accountId: string, }, ... ]
The number of unique accountIds
that can be set via the setValue
method can't be greater than 90 per one batched update.
This means that all calls to setValue
for user-based fields performed in a single onInit
or onChange
callback are counted against this limit.
type: com.atlassian.jira.plugin.system.customfieldtypes:url
1 2setValue(url: string): FieldAPI
1 2getValue(): UrlField
1 2string
type: com.atlassian.jira.plugin.system.customfieldtypes:datepicker
Use null
to unset the value.
The provided string must be in the yyyy-MM-dd
date format.
1 2setValue(date: string | null): FieldAPI
1 2getValue(): DatePickerField
1 2string | null
type: com.atlassian.jira.plugin.system.customfieldtypes:datetime
Use null
to unset the value.
The provided string must be in the YYYY-MM-DDThh:mmTZD
date format.
1 2setValue(value: string | null): FieldAPI
1 2getValue(): DatePickerField
1 2string | null
type: duedate
Use null
to unset the value.
The provided string must be in the yyyy-MM-dd
date format.
1 2setValue(date: string | null): FieldAPI
1 2getValue(): DatePickerField
1 2string | null
type: com.atlassian.jpo:jpo-custom-field-baseline-start
Use null
to unset the value.
The provided string must be in the yyyy-MM-dd
date format.
1 2setValue(date: string | null): FieldAPI
1 2getValue(): DatePickerField
1 2string | null
type: com.atlassian.jpo:jpo-custom-field-baseline-end
Use null
to unset the value.
The provided string must be in the yyyy-MM-dd
date format.
1 2setValue(date: string | null): FieldAPI
1 2getValue(): DatePickerField
1 2string | null
type: com.atlassian.jira.plugin.system.customfieldtypes:float
Use null
to unset the value.
1 2setValue(value: number | null): FieldAPI
1 2getValue(): NumberField
1 2number | null
type: parent
1 2setValue(id: string | null): FieldAPI
1 2getValue(): ParentField
1 2{ id: string, key: string, } | null
type: status
The screen will appear if configured for the used transition ID.
The get value method returns the ID and name of the current status. You should pass the transition ID to the set value method.
1 2setValue(transitionId: string): FieldAPI
1 2getValue(): StatusField | null
1 2{ id: string, name: string, }
type: timeoriginalestimate
Use null
to unset the value.
The input should be an integer greater than 0, representing the estimated time in minutes. See What are time estimations?
1 2setValue(value: number | null): FieldAPI
1 2getValue(): OriginalEstimateField
1 2number | null
1 2getScreenTabById(<tabId>): ScreenTabAPI | undefined
Accepts one argument: tabId
(string) - the identifier of the screen tab which the app wants to work on.
Returns the ScreenTabAPI object if the tab exists on the GIC or Issue View.
Returns undefined
if the screen tab doesn't exist.
Example of usage with a regular screen tab:
1 2uiModificationsApi.onInit(({ api }) => { const { getScreenTabById } = api; const tab = getScreenTabById('<identifier>') // You can perform operations on a specific screen tab // using the ScreenTabAPI here }, () => [''])
1 2getScreenTabs(): ScreenTabAPI[]
Returns an array of ScreenTabAPI objects with all screen tabs mounted on the current view.
Example of usage:
1 2uiModificationsApi.onInit(({ api }) => { const { getScreenTabs } = api; const tabs = getScreenTabs(); // You can iterate over screen tabs // (or access them by index) // and perform operations on each // screen tab using the ScreenTabAPI here. }, () => [])
The changes requested by setters in this API aren't applied immediately.
They’re batched and only applied after the execution of the onInit
or onChange
callback ends.
This means that reading the values using getters will always provide the screen tab’s initial state, which is immutable.
1 2getId(): string
Returns the screen tab identifier.
1 2setVisible(value: boolean): ScreenTabAPI
Changes tab visibility.
Example:
1 2tab.setVisible(false);
Make sure you don't try to hide the tab that's currently in focus. If you do, your UIM won't be applied and the onError
callback will receive a SCREENTABS_VALIDATION_FAILED
error.
1 2isVisible(): boolean
Returns true
if the tab is currently visible. Returns false
otherwise.
1 2focus(): ScreenTabAPI
Switches the focus to a given screen tab. Automatically puts other visible tabs out of focus.
Example:
1 2tab.focus();
Rate this page: