Use this service to surface banners at the top of the screen. The expectation is that the banner will used an
<EuiCallOut />
to render, but that is not a requirement. See the EUI docs for
more information on banners and their role within the UI.
Banners should be considered with respect to their lifecycle. Most banners are best served by using the add
and
remove
functions.
import { banners } from 'ui/notify';
There are three methods defined to manipulate the list of banners: add
, set
, and remove
. A fourth method,
onChange
exists to listen to changes made via add
, set
, and remove
.
This is the preferred way to add banners because it implies the best usage of the banner: added once during a page's lifecycle. For other usages, consider not using a banner.
const bannerId = banners.add({
// required:
component,
// optional:
priority,
});
Field | Type | Description |
---|---|---|
component |
Any | The value displayed as the banner. |
priority |
Number | Optional priority, which defaults to 0 used to place the banner. |
To add a banner, you only need to define the component
field.
The priority
sorts in descending order. Items sharing the same priority are sorted from oldest to newest. For example:
const banner1 = banners.add({ component: <EuiCallOut title="fake1" /> });
const banner2 = banners.add({ component: <EuiCallOut title="fake2" />, priority: 0 });
const banner3 = banners.add({ component: <EuiCallOut title="fake3" />, priority: 1 });
That would be displayed as:
[ fake3 ]
[ fake1 ]
[ fake2 ]
Type | Description |
---|---|
String | A newly generated ID. |
This example includes buttons that allow the user to remove the banner. In some cases, you may not want any buttons and in other cases you will want an action to proceed the banner's removal (e.g., apply an Advanced Setting).
This makes the most sense to use when a banner is added at the beginning of the page life cycle and not expected to be touched, except by its own buttons triggering an action or navigating away.
const bannerId = banners.add({
component: (
<EuiCallOut
iconType="iInCircle"
title="In order to visualize and explore data in Kibana, you'll need to create an index pattern to retrieve data from Elasticsearch."
>
<EuiFlexGroup
gutterSize="s"
alignItems="center"
>
<EuiFlexItem
grow={false}
>
<EuiButton
size="s"
fill
onClick={() => banners.remove(bannerId)}
>
Dismiss
</EuiButton>
</EuiFlexItem>
<EuiFlexItem
grow={false}
>
<EuiButton
size="s"
onClick={() => window.alert('Do Something Else')}
>
Do Something Else
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</EuiCallOut>
),
});
Unlike toast notifications, banners stick around until they are explicitly removed. Using the add
example above,you can remove it by calling remove
.
Note: They will stick around as long as the scope is remembered by whatever set it; navigating away won't remove it unless the scope is forgotten (e.g., when the "app" changes)!
const removed = banners.remove(bannerId);
Field | Type | Description |
---|---|---|
id |
String | ID of a banner. |
Type | Description |
---|---|
Boolean | true if the ID was recognized and the banner was removed. false otherwise. |
To remove a banner, you need to pass the id
of the banner.
if (banners.remove(bannerId)) {
// removed; otherwise it didn't exist (maybe it was already removed)
}
Like toast notifications do automatically, you can have a banner automatically removed after a set of time, by setting a timer:
setTimeout(() => banners.remove(bannerId), 15000);
Note: It is safe to remove a banner more than once as unknown IDs will be ignored.
Banners can be replaced once added by supplying their id
. If one is supplied, then the ID will be used to replace
any banner with the same ID and a new id
will be returned.
You should only consider using set
when the banner is manipulated frequently in the lifecycle of the page, where
maintaining the banner's id
can be a burden. It is easier to allow banners
to create the ID for you in most
situations where a banner is useful (e.g., set once), which safely avoids any chance to have an ID-based collision,
which happens automatically with add
.
Usage of set
can imply that your use case is abusing the banner system.
Note: set
will only trigger the callback once for both the implicit remove and add operation.
const id = banners.set({
// required:
component,
// optional:
id,
priority,
});
Field | Type | Description |
---|---|---|
component |
Any | The value displayed as the banner. |
id |
String | Optional ID used to remove an existing banner. |
priority |
Number | Optional priority, which defaults to 0 used to place the banner. |
The id
is optional because it follows the same semantics as the remove
method: unknown IDs are ignored. This
is useful when first creating a banner so that you do not have to call add
instead.
Type | Description |
---|---|
String | A newly generated ID. |
This example does not include any way for the user to clear the banner directly. Instead, it is cleared based on time. Related to it being cleared by time, it can also reappear within the same page life cycle by navigating between different paths that need it displayed. Instead of adding a new banner for every navigation, you should replace any existing banner.
let bannerId;
let timeoutId;
function displayBanner() {
clearTimeout(timeoutId);
bannerId = banners.set({
id: bannerId, // the first time it will be undefined, but reused as long as this is in the same lifecycle
component: (
<EuiCallOut
color="warning"
iconType="iInCircle"
title={
`In order to visualize and explore data in Kibana,
you'll need to create an index pattern to retrieve data from Elasticsearch.`
}
/>
)
});
// hide the message after the user has had a chance to acknowledge it -- so it doesn't permanently stick around
banner.timeoutId = setTimeout(() => {
banners.remove(bannerId);
timeoutId = undefined;
}, 6000);
}
For React components that intend to display the banners, it is not enough to simply render
the banners.list
values. Because they can change after being rendered, the React component that renders the list must be alerted
to changes to the list.
// inside your React component
banners.onChange(() => this.forceUpdate());
Field | Type | Description |
---|---|---|
callback |
Function | The function to invoke whenever the internal banner list is changed. |
Every new callback
replaces the previous callback. So calling this with null
or undefined
will unset the
callback.
Nothing.
This can be used inside of a React component to trigger a re-render
of the banners.
import { GlobalBannerList } from 'ui/notify';
<GlobalBannerList
banners={banners.list}
subscribe={banners.onChange}
/>
For React components that intend to display the banners, it is not enough to simply render
the banners.list
values. Because they can change after being rendered, the React component that renders the list must be alerted
to changes to the list.
<GlobalBannerList
banners={banners.list}
subscribe={banners.onChange}
/>
Type | Description |
---|---|
Array | The array of banner objects. |
Banner objects are sorted in descending order based on their priority
, in the form:
{
id: 'banner-123',
component: <EuiCallOut />,
priority: 12,
}
Field | Type | Description |
---|---|---|
component |
Any | The value displayed as the banner. |
id |
String | The ID of the banner, which can be used as a React "key". |
priority |
Number | The priority of the banner. |
This can be used to supply the banners to the GlobalBannerList
React component (which is done for you).
import { GlobalBannerList } from 'ui/notify';
<GlobalBannerList
banners={banners.list}
subscribe={banners.onChange}
/>
Functional tests are commonly used to verify that an action yielded a successful outcome. You can place a
data-test-subj
attribute on the banner and use it to check if the banner exists inside of your functional test.
This acts as a proxy for verifying the successful outcome. Any unrecognized field will be added as a property of the
containing element.
banners.add({
component: (
<EuiCallOut
title="Look at me!"
/>
),
data-test-subj: 'my-tested-banner',
});
This will apply the data-test-subj
to the element containing the component
, so the inner HTML of that element
will exclusively be the specified component
.
Given that component
is expected to be a React component, you could also add the data-test-subj
directly to it:
banners.add({
component: (
<EuiCallOut
title="Look at me!"
data-test-subj="my-tested-banner"
/>
),
});