Component demo · Alarms and status
Alarm Banner
index.htm as it is written with the smart-industrial package
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<title>Alarm Banner Overview Demo</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" type="text/css" href="node_modules/smart-industrial/source/styles/smart.default.css" />
<link rel="stylesheet" type="text/css" href="node_modules/smart-industrial/source/styles/smart.industrial-elements.css" />
<link rel="stylesheet" type="text/css" href="node_modules/smart-industrial/source/styles/smart.industrial.css" />
<link rel="stylesheet" type="text/css" href="node_modules/smart-industrial/source/styles/smart.industrial-dark.css" />
<link rel="stylesheet" type="text/css" href="styles.css" />
</head>
<body class="viewport" theme="industrial">
<div class="smart-demo-container">
<section id="overview">
<h2>Smart.AlarmBanner keeps the alarm that the operator should deal with next in view.</h2>
<p>
The banner receives the list of active alarms and selects the alarms to show. Unacknowledged alarms come first, then higher priorities, and among equal alarms the oldest one, which has been waiting the longest. Priorities follow ISA-18.2, and an unacknowledged priority 1 alarm blinks until it is acknowledged.
</p>
</section>
<section>
<h2>Live banner</h2>
<smart-alarm-banner id="banner"></smart-alarm-banner>
<div class="demo-actions">
<button type="button" id="raiseCritical">Raise critical</button>
<button type="button" id="raiseWarning">Raise warning</button>
<button type="button" id="raiseAdvisory">Raise advisory</button>
<button type="button" id="ackAll">Acknowledge all</button>
<button type="button" id="clearAll">Clear</button>
</div>
<p class="demo-log" id="log">Events raised by the banner are listed here.</p>
</section>
<section>
<h2>Showing more than one alarm</h2>
<p>
The <code>maxVisible</code> property shows a stack of alarms instead of a single row, for example on a wall display that covers several areas.
</p>
<smart-alarm-banner id="stack" max-visible="3"></smart-alarm-banner>
</section>
<section>
<h2>Read-only banner</h2>
<p>
Turn off the acknowledge button and the timestamps for an informational strip. Both are on by default. Boolean attributes work by presence, so <code>show-acknowledge="false"</code> would turn the button <em>on</em>; set the properties from JavaScript instead.
</p>
<smart-alarm-banner id="readonlyBanner"></smart-alarm-banner>
</section>
<section>
<h2>No active alarms</h2>
<smart-alarm-banner id="emptyBanner" empty-message="All monitored points within limits"></smart-alarm-banner>
</section>
</div>
<!-- scripts -->
<script type="module" src="node_modules/smart-industrial/source/modules/smart.alarmbanner.js"></script>
<script type="module" src="index.js"></script>
<script>
/* Shown once every element has rendered, so nothing moves on the way in (styles/demos.css). */
addEventListener('load', function () {
var all = Array.prototype.slice.call(document.querySelectorAll('*')),
ready = function () { document.body.classList.add('ready'); };
Promise.all(all.map(function (e) { return e.localName.indexOf('smart-') === 0 ? customElements.whenDefined(e.localName) : null; })).then(function () {
return Promise.all(all.filter(function (e) { return typeof e.whenRendered === 'function' && !e.isRendered; }).map(function (e) { return e.whenRendered(); }));
}).then(function () { requestAnimationFrame(function () { requestAnimationFrame(ready); }); }, ready);
});
</script>
</body>
</html>
index.js
/// <reference path="../../../source/typescript/smart.elements.d.ts" />
const CATALOG = {
1: [
{ tag: 'PT-2101', message: 'Header pressure high' },
{ tag: 'TT-2108', message: 'Reactor outlet temperature high-high' },
{ tag: 'VT-2101', message: 'Feed pump A bearing vibration high' }
],
2: [
{ tag: 'LT-2202', message: 'Tank TK-02 level low' },
{ tag: 'CP-2150', message: 'Air compressor discharge temperature high' }
],
3: [
{ tag: 'AI-2130', message: 'Product pH outside band' },
{ tag: 'PT-2140', message: 'Instrument air pressure low' }
]
};
let nextId = 1;
/* An alarm of the priority; ageMinutes puts its timestamp in the past, so the initial
alarms have been waiting for different lengths of time, as real ones have. */
function raise(priority, ageMinutes) {
const options = CATALOG[priority],
pick = options[Math.floor(Math.random() * options.length)];
return {
id: nextId++,
tag: pick.tag,
message: pick.message,
priority: priority,
timestamp: new Date(Date.now() - (ageMinutes || 0) * 60000),
acknowledged: false
};
}
window.onload = function () {
const banner = document.getElementById('banner'),
stack = document.getElementById('stack'),
readonlyBanner = document.getElementById('readonlyBanner'),
emptyBanner = document.getElementById('emptyBanner'),
log = document.getElementById('log');
/* ── Live banner ─────────────────────────────────────────────────── */
let alarms = [raise(2, 14), raise(3, 6)];
/* Treat the list as immutable. Pushing onto the array the banner already
holds mutates its own copy, so the follow-up assignment looks unchanged
and no redraw happens. Build a new array instead - or, if you do mutate
in place, call banner.redraw() yourself. */
function setAlarms(next) {
alarms = next;
banner.alarms = next;
}
setAlarms(alarms);
function note(text) {
log.textContent = text;
}
/* The element never mutates the alarms - it reports the operator's intent
and the host decides what acknowledgement means. */
banner.addEventListener('acknowledge', function (event) {
const alarm = event.detail.alarm;
setAlarms(alarms.map(function (item) {
return item.id === alarm.id ? Object.assign({}, item, { acknowledged: true }) : item;
}));
note('acknowledge → ' + alarm.tag + ' (' + alarm.message + ')');
});
banner.addEventListener('alarmClick', function (event) {
note('alarmClick: ' + event.detail.alarm.tag + '. Open the alarm detail here.');
});
document.getElementById('raiseCritical').addEventListener('click', function () {
setAlarms(alarms.concat(raise(1)));
note('raised a priority 1 alarm');
});
document.getElementById('raiseWarning').addEventListener('click', function () {
setAlarms(alarms.concat(raise(2)));
note('raised a priority 2 alarm');
});
document.getElementById('raiseAdvisory').addEventListener('click', function () {
setAlarms(alarms.concat(raise(3)));
note('raised a priority 3 alarm');
});
document.getElementById('ackAll').addEventListener('click', function () {
banner.acknowledgeAll();
note('acknowledgeAll() raised one acknowledge event per unacknowledged alarm');
});
document.getElementById('clearAll').addEventListener('click', function () {
setAlarms([]);
note('alarm list cleared');
});
/* ── Static examples ─────────────────────────────────────────────── */
stack.alarms = [raise(1, 2), raise(2, 9), raise(3, 27), raise(3, 4)];
readonlyBanner.showAcknowledge = false;
readonlyBanner.showTimestamps = false;
readonlyBanner.alarms = [raise(2, 11)];
emptyBanner.alarms = [];
};
styles.css
.smart-demo-container {
max-width: 980px;
}
section {
margin-bottom: 34px;
}
section h2 {
font-size: 16px;
font-weight: 500;
margin: 0 0 8px;
}
section p {
color: var(--smart-text-secondary-color);
margin: 0 0 14px;
max-width: 74ch;
}
code {
background: var(--smart-ui-state-hover);
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
padding: 1px 5px;
}
.demo-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 14px;
}
.demo-actions button {
background: transparent;
border: 1px solid var(--smart-border);
border-radius: var(--smart-border-radius);
color: var(--smart-text-secondary-color);
cursor: pointer;
font-family: inherit;
font-size: 11px;
letter-spacing: .06em;
padding: 7px 14px;
text-transform: uppercase;
}
.demo-actions button:hover {
background: var(--smart-ui-state-hover);
border-color: var(--smart-ui-state-border-hover);
color: var(--smart-ui-state-color-hover);
}
.demo-log {
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
margin-top: 14px;
color: var(--smart-text-tertiary-color);
}
/* ── Demo chrome, shared by the Industrial demos ─────────────────────────
Headings, prose, buttons and notes are the same on every demo page. This block
is appended by scripts and comes last, so it wins over the rules above. */
.smart-demo-container {
padding: 4px 0 32px;
}
.smart-demo-container section {
margin-bottom: 36px;
}
.smart-demo-container section h2 {
margin: 0 0 8px;
font-size: 17px;
font-weight: 600;
line-height: 1.35;
color: var(--smart-text-color);
}
.smart-demo-container #overview h2 {
margin-bottom: 10px;
font-size: 20px;
max-width: 78ch;
}
.smart-demo-container section > p {
margin: 0 0 14px;
max-width: 84ch;
font-size: 14px;
line-height: 1.55;
color: var(--smart-text-secondary-color);
}
.smart-demo-container code {
padding: 1px 5px;
border-radius: 2px;
background: var(--smart-ui-state-hover);
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
}
.smart-demo-container .demo-actions {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px 10px;
margin-top: 12px;
}
.smart-demo-container .demo-actions label {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 13px;
color: var(--smart-text-color);
}
.smart-demo-container .demo-actions button,
.smart-demo-container .demo-button {
min-height: 30px;
padding: 0 12px;
border: 1px solid var(--smart-border);
border-radius: 3px;
background: var(--smart-industrial-panel-background, var(--smart-surface));
color: var(--smart-text-color);
font: inherit;
font-size: 13px;
font-weight: 500;
letter-spacing: 0;
text-transform: none;
cursor: pointer;
}
.smart-demo-container .demo-actions button:hover,
.smart-demo-container .demo-button:hover {
border-color: var(--smart-ui-state-border-hover);
background: var(--smart-ui-state-hover);
color: var(--smart-ui-state-color-hover);
}
.smart-demo-container .demo-actions button.demo-button-primary,
.smart-demo-container .demo-button-primary {
border-color: var(--smart-primary);
background: var(--smart-primary);
color: var(--smart-primary-color, #fff);
}
.smart-demo-container .demo-actions button[disabled],
.smart-demo-container .demo-button[disabled] {
opacity: 0.5;
cursor: default;
}
.smart-demo-container .demo-actions select,
.smart-demo-container .demo-actions input[type="text"],
.smart-demo-container .demo-actions input[type="number"] {
min-height: 30px;
padding: 2px 6px;
border: 1px solid var(--smart-border);
border-radius: 3px;
background: var(--smart-background);
color: var(--smart-text-color);
font: inherit;
font-size: 13px;
}
.smart-demo-container .hint,
.smart-demo-container .demo-log,
.smart-demo-container .demo-counts {
margin: 10px 0 0;
font-size: 12px;
line-height: 1.5;
color: var(--smart-text-secondary-color);
opacity: 1;
}
.smart-demo-container .demo-log,
.smart-demo-container .demo-counts {
font-family: Consolas, "Courier New", monospace;
}
.smart-demo-container .log {
list-style: none;
margin: 10px 0 0;
padding: 0;
font-size: 12px;
font-variant-numeric: tabular-nums;
color: var(--smart-text-secondary-color);
opacity: 1;
}
.smart-demo-container .log li {
padding: 2px 0;
}
The same demo in Angular, generated from the HTML demo. The frameworks guide has the project setup.
angular/app.component.html
<div class="angular-root" theme="industrial">
<div class="smart-demo-container">
<section id="overview">
<h2>Smart.AlarmBanner keeps the alarm that the operator should deal with next in view.</h2>
<p>
The banner receives the list of active alarms and selects the alarms to show. Unacknowledged alarms come first, then higher priorities, and among equal alarms the oldest one, which has been waiting the longest. Priorities follow ISA-18.2, and an unacknowledged priority 1 alarm blinks until it is acknowledged.
</p>
</section>
<section>
<h2>Live banner</h2>
<smart-alarm-banner id="banner"></smart-alarm-banner>
<div class="demo-actions">
<button type="button" id="raiseCritical">Raise critical</button>
<button type="button" id="raiseWarning">Raise warning</button>
<button type="button" id="raiseAdvisory">Raise advisory</button>
<button type="button" id="ackAll">Acknowledge all</button>
<button type="button" id="clearAll">Clear</button>
</div>
<p class="demo-log" id="log">Events raised by the banner are listed here.</p>
</section>
<section>
<h2>Showing more than one alarm</h2>
<p>
The <code>maxVisible</code> property shows a stack of alarms instead of a single row, for example on a wall display that covers several areas.
</p>
<smart-alarm-banner id="stack" [maxVisible]="3"></smart-alarm-banner>
</section>
<section>
<h2>Read-only banner</h2>
<p>
Turn off the acknowledge button and the timestamps for an informational strip. Both are on by default. Boolean attributes work by presence, so <code>show-acknowledge="false"</code> would turn the button <em>on</em>; set the properties from JavaScript instead.
</p>
<smart-alarm-banner id="readonlyBanner"></smart-alarm-banner>
</section>
<section>
<h2>No active alarms</h2>
<smart-alarm-banner id="emptyBanner" empty-message="All monitored points within limits"></smart-alarm-banner>
</section>
</div>
</div>
angular/app.component.ts
import { Component, AfterViewInit, OnDestroy } from '@angular/core';
import { AlarmBannerModule } from 'smart-industrial/angular/alarmbanner';
import { runDemo } from './demo';
/**
* Smart.UI Industrial - AlarmBanner in Angular.
*
* The template is the HTML demo's markup: the elements are the wrappers, so what is
* written on them is checked against their inputs. demo.ts drives them the way the
* plant's data would, once every element in the view has rendered.
*/
@Component({
selector: 'app-root',
standalone: true,
imports: [AlarmBannerModule],
templateUrl: './app.component.html',
styleUrl: './app.component.css'
})
export class AppComponent implements AfterViewInit, OnDestroy {
private unmounted = false;
ngAfterViewInit(): void {
const root = document.querySelector('.angular-root') as HTMLElement,
pending: Promise<void>[] = [];
root.querySelectorAll('*').forEach((element: any) => {
if (typeof element.whenRendered === 'function' && !element.isRendered) {
pending.push(element.whenRendered());
}
});
Promise.all(pending).then(() => {
if (!this.unmounted) {
runDemo();
}
});
}
ngOnDestroy(): void {
this.unmounted = true;
}
}
angular/demo.ts
// @ts-nocheck
/* The HTML demo's script, as it was written: page code that feeds the elements, not a typed
API. The types are on the wrappers in app.component.ts. */
const CATALOG = {
1: [
{ tag: 'PT-2101', message: 'Header pressure high' },
{ tag: 'TT-2108', message: 'Reactor outlet temperature high-high' },
{ tag: 'VT-2101', message: 'Feed pump A bearing vibration high' }
],
2: [
{ tag: 'LT-2202', message: 'Tank TK-02 level low' },
{ tag: 'CP-2150', message: 'Air compressor discharge temperature high' }
],
3: [
{ tag: 'AI-2130', message: 'Product pH outside band' },
{ tag: 'PT-2140', message: 'Instrument air pressure low' }
]
};
let nextId = 1;
/* An alarm of the priority; ageMinutes puts its timestamp in the past, so the initial
alarms have been waiting for different lengths of time, as real ones have. */
function raise(priority, ageMinutes) {
const options = CATALOG[priority],
pick = options[Math.floor(Math.random() * options.length)];
return {
id: nextId++,
tag: pick.tag,
message: pick.message,
priority: priority,
timestamp: new Date(Date.now() - (ageMinutes || 0) * 60000),
acknowledged: false
};
}
export function runDemo(): void {
const banner = document.getElementById('banner'),
stack = document.getElementById('stack'),
readonlyBanner = document.getElementById('readonlyBanner'),
emptyBanner = document.getElementById('emptyBanner'),
log = document.getElementById('log');
/* ── Live banner ─────────────────────────────────────────────────── */
let alarms = [raise(2, 14), raise(3, 6)];
/* Treat the list as immutable. Pushing onto the array the banner already
holds mutates its own copy, so the follow-up assignment looks unchanged
and no redraw happens. Build a new array instead - or, if you do mutate
in place, call banner.redraw() yourself. */
function setAlarms(next) {
alarms = next;
banner.alarms = next;
}
setAlarms(alarms);
function note(text) {
log.textContent = text;
}
/* The element never mutates the alarms - it reports the operator's intent
and the host decides what acknowledgement means. */
banner.addEventListener('acknowledge', function (event) {
const alarm = event.detail.alarm;
setAlarms(alarms.map(function (item) {
return item.id === alarm.id ? Object.assign({}, item, { acknowledged: true }) : item;
}));
note('acknowledge → ' + alarm.tag + ' (' + alarm.message + ')');
});
banner.addEventListener('alarmClick', function (event) {
note('alarmClick: ' + event.detail.alarm.tag + '. Open the alarm detail here.');
});
document.getElementById('raiseCritical').addEventListener('click', function () {
setAlarms(alarms.concat(raise(1)));
note('raised a priority 1 alarm');
});
document.getElementById('raiseWarning').addEventListener('click', function () {
setAlarms(alarms.concat(raise(2)));
note('raised a priority 2 alarm');
});
document.getElementById('raiseAdvisory').addEventListener('click', function () {
setAlarms(alarms.concat(raise(3)));
note('raised a priority 3 alarm');
});
document.getElementById('ackAll').addEventListener('click', function () {
banner.acknowledgeAll();
note('acknowledgeAll() raised one acknowledge event per unacknowledged alarm');
});
document.getElementById('clearAll').addEventListener('click', function () {
setAlarms([]);
note('alarm list cleared');
});
/* ── Static examples ─────────────────────────────────────────────── */
stack.alarms = [raise(1, 2), raise(2, 9), raise(3, 27), raise(3, 4)];
readonlyBanner.showAcknowledge = false;
readonlyBanner.showTimestamps = false;
readonlyBanner.alarms = [raise(2, 11)];
emptyBanner.alarms = [];
}
angular/app.component.css
.smart-demo-container {
max-width: 980px;
}
section {
margin-bottom: 34px;
}
section h2 {
font-size: 16px;
font-weight: 500;
margin: 0 0 8px;
}
section p {
color: var(--smart-text-secondary-color);
margin: 0 0 14px;
max-width: 74ch;
}
code {
background: var(--smart-ui-state-hover);
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
padding: 1px 5px;
}
.demo-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 14px;
}
.demo-actions button {
background: transparent;
border: 1px solid var(--smart-border);
border-radius: var(--smart-border-radius);
color: var(--smart-text-secondary-color);
cursor: pointer;
font-family: inherit;
font-size: 11px;
letter-spacing: .06em;
padding: 7px 14px;
text-transform: uppercase;
}
.demo-actions button:hover {
background: var(--smart-ui-state-hover);
border-color: var(--smart-ui-state-border-hover);
color: var(--smart-ui-state-color-hover);
}
.demo-log {
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
margin-top: 14px;
color: var(--smart-text-tertiary-color);
}
/* ── Demo chrome, shared by the Industrial demos ─────────────────────────
Headings, prose, buttons and notes are the same on every demo page. This block
is appended by scripts and comes last, so it wins over the rules above. */
.smart-demo-container {
padding: 4px 0 32px;
}
.smart-demo-container section {
margin-bottom: 36px;
}
.smart-demo-container section h2 {
margin: 0 0 8px;
font-size: 17px;
font-weight: 600;
line-height: 1.35;
color: var(--smart-text-color);
}
.smart-demo-container #overview h2 {
margin-bottom: 10px;
font-size: 20px;
max-width: 78ch;
}
.smart-demo-container section > p {
margin: 0 0 14px;
max-width: 84ch;
font-size: 14px;
line-height: 1.55;
color: var(--smart-text-secondary-color);
}
.smart-demo-container code {
padding: 1px 5px;
border-radius: 2px;
background: var(--smart-ui-state-hover);
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
}
.smart-demo-container .demo-actions {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px 10px;
margin-top: 12px;
}
.smart-demo-container .demo-actions label {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 13px;
color: var(--smart-text-color);
}
.smart-demo-container .demo-actions button,
.smart-demo-container .demo-button {
min-height: 30px;
padding: 0 12px;
border: 1px solid var(--smart-border);
border-radius: 3px;
background: var(--smart-industrial-panel-background, var(--smart-surface));
color: var(--smart-text-color);
font: inherit;
font-size: 13px;
font-weight: 500;
letter-spacing: 0;
text-transform: none;
cursor: pointer;
}
.smart-demo-container .demo-actions button:hover,
.smart-demo-container .demo-button:hover {
border-color: var(--smart-ui-state-border-hover);
background: var(--smart-ui-state-hover);
color: var(--smart-ui-state-color-hover);
}
.smart-demo-container .demo-actions button.demo-button-primary,
.smart-demo-container .demo-button-primary {
border-color: var(--smart-primary);
background: var(--smart-primary);
color: var(--smart-primary-color, #fff);
}
.smart-demo-container .demo-actions button[disabled],
.smart-demo-container .demo-button[disabled] {
opacity: 0.5;
cursor: default;
}
.smart-demo-container .demo-actions select,
.smart-demo-container .demo-actions input[type="text"],
.smart-demo-container .demo-actions input[type="number"] {
min-height: 30px;
padding: 2px 6px;
border: 1px solid var(--smart-border);
border-radius: 3px;
background: var(--smart-background);
color: var(--smart-text-color);
font: inherit;
font-size: 13px;
}
.smart-demo-container .hint,
.smart-demo-container .demo-log,
.smart-demo-container .demo-counts {
margin: 10px 0 0;
font-size: 12px;
line-height: 1.5;
color: var(--smart-text-secondary-color);
opacity: 1;
}
.smart-demo-container .demo-log,
.smart-demo-container .demo-counts {
font-family: Consolas, "Courier New", monospace;
}
.smart-demo-container .log {
list-style: none;
margin: 10px 0 0;
padding: 0;
font-size: 12px;
font-variant-numeric: tabular-nums;
color: var(--smart-text-secondary-color);
opacity: 1;
}
.smart-demo-container .log li {
padding: 2px 0;
}
The same demo in React, generated from the HTML demo. The frameworks guide has the project setup.
react/App.jsx
import 'smart-industrial/source/styles/smart.default.css';
import 'smart-industrial/source/styles/smart.industrial-elements.css';
import 'smart-industrial/source/styles/smart.industrial.css';
import 'smart-industrial/source/styles/smart.industrial-dark.css';
import './App.css';
import React from 'react';
import { AlarmBanner } from 'smart-industrial/react/alarmbanner';
/* The demo logic is the HTML demo's: it feeds the elements the way a plant's data would. */
const CATALOG = {
1: [
{ tag: 'PT-2101', message: 'Header pressure high' },
{ tag: 'TT-2108', message: 'Reactor outlet temperature high-high' },
{ tag: 'VT-2101', message: 'Feed pump A bearing vibration high' }
],
2: [
{ tag: 'LT-2202', message: 'Tank TK-02 level low' },
{ tag: 'CP-2150', message: 'Air compressor discharge temperature high' }
],
3: [
{ tag: 'AI-2130', message: 'Product pH outside band' },
{ tag: 'PT-2140', message: 'Instrument air pressure low' }
]
};
let nextId = 1;
/* An alarm of the priority; ageMinutes puts its timestamp in the past, so the initial
alarms have been waiting for different lengths of time, as real ones have. */
function raise(priority, ageMinutes) {
const options = CATALOG[priority],
pick = options[Math.floor(Math.random() * options.length)];
return {
id: nextId++,
tag: pick.tag,
message: pick.message,
priority: priority,
timestamp: new Date(Date.now() - (ageMinutes || 0) * 60000),
acknowledged: false
};
}
const App = () => {
const root = React.useRef(null);
React.useEffect(() => {
/* The elements render after React has mounted them, and the demo's logic - written
for a page's load event - reads their parts, so it waits for every element in the
tree to have rendered first. */
const pending = Array.from(root.current.querySelectorAll('*'))
.filter((element) => typeof element.whenRendered === 'function' && !element.isRendered)
.map((element) => element.whenRendered());
let unmounted = false;
Promise.all(pending).then(() => {
if (unmounted) {
return;
}
const banner = document.getElementById('banner'),
stack = document.getElementById('stack'),
readonlyBanner = document.getElementById('readonlyBanner'),
emptyBanner = document.getElementById('emptyBanner'),
log = document.getElementById('log');
/* ── Live banner ─────────────────────────────────────────────────── */
let alarms = [raise(2, 14), raise(3, 6)];
/* Treat the list as immutable. Pushing onto the array the banner already
holds mutates its own copy, so the follow-up assignment looks unchanged
and no redraw happens. Build a new array instead - or, if you do mutate
in place, call banner.redraw() yourself. */
function setAlarms(next) {
alarms = next;
banner.alarms = next;
}
setAlarms(alarms);
function note(text) {
log.textContent = text;
}
/* The element never mutates the alarms - it reports the operator's intent
and the host decides what acknowledgement means. */
banner.addEventListener('acknowledge', function (event) {
const alarm = event.detail.alarm;
setAlarms(alarms.map(function (item) {
return item.id === alarm.id ? Object.assign({}, item, { acknowledged: true }) : item;
}));
note('acknowledge → ' + alarm.tag + ' (' + alarm.message + ')');
});
banner.addEventListener('alarmClick', function (event) {
note('alarmClick: ' + event.detail.alarm.tag + '. Open the alarm detail here.');
});
document.getElementById('raiseCritical').addEventListener('click', function () {
setAlarms(alarms.concat(raise(1)));
note('raised a priority 1 alarm');
});
document.getElementById('raiseWarning').addEventListener('click', function () {
setAlarms(alarms.concat(raise(2)));
note('raised a priority 2 alarm');
});
document.getElementById('raiseAdvisory').addEventListener('click', function () {
setAlarms(alarms.concat(raise(3)));
note('raised a priority 3 alarm');
});
document.getElementById('ackAll').addEventListener('click', function () {
banner.acknowledgeAll();
note('acknowledgeAll() raised one acknowledge event per unacknowledged alarm');
});
document.getElementById('clearAll').addEventListener('click', function () {
setAlarms([]);
note('alarm list cleared');
});
/* ── Static examples ─────────────────────────────────────────────── */
stack.alarms = [raise(1, 2), raise(2, 9), raise(3, 27), raise(3, 4)];
readonlyBanner.showAcknowledge = false;
readonlyBanner.showTimestamps = false;
readonlyBanner.alarms = [raise(2, 11)];
emptyBanner.alarms = [];
});
return () => {
unmounted = true;
};
}, []);
return (
<div className="react-root" ref={root} theme="industrial">
<div className="smart-demo-container">
<section id="overview">
<h2>Smart.AlarmBanner keeps the alarm that the operator should deal with next in view.</h2>
<p>The banner receives the list of active alarms and selects the alarms to show. Unacknowledged alarms come first, then higher priorities, and among equal alarms the oldest one, which has been waiting the longest. Priorities follow ISA-18.2, and an unacknowledged priority 1 alarm blinks until it is acknowledged.</p>
</section>
<section>
<h2>Live banner</h2>
<AlarmBanner id="banner"></AlarmBanner>
<div className="demo-actions">
<button type="button" id="raiseCritical">Raise critical</button>
<button type="button" id="raiseWarning">Raise warning</button>
<button type="button" id="raiseAdvisory">Raise advisory</button>
<button type="button" id="ackAll">Acknowledge all</button>
<button type="button" id="clearAll">Clear</button>
</div>
<p className="demo-log" id="log">Events raised by the banner are listed here.</p>
</section>
<section>
<h2>Showing more than one alarm</h2>
<p>The <code>maxVisible</code> property shows a stack of alarms instead of a single row, for example on a wall display that covers several areas.</p>
<AlarmBanner id="stack" maxVisible={3}></AlarmBanner>
</section>
<section>
<h2>Read-only banner</h2>
<p>Turn off the acknowledge button and the timestamps for an informational strip. Both are on by default. Boolean attributes work by presence, so <code>show-acknowledge="false"</code> would turn the button <em>on</em>; set the properties from JavaScript instead.</p>
<AlarmBanner id="readonlyBanner"></AlarmBanner>
</section>
<section>
<h2>No active alarms</h2>
<AlarmBanner id="emptyBanner" emptyMessage="All monitored points within limits"></AlarmBanner>
</section>
</div>
</div>
);
};
export default App;
react/App.css
.smart-demo-container {
max-width: 980px;
}
section {
margin-bottom: 34px;
}
section h2 {
font-size: 16px;
font-weight: 500;
margin: 0 0 8px;
}
section p {
color: var(--smart-text-secondary-color);
margin: 0 0 14px;
max-width: 74ch;
}
code {
background: var(--smart-ui-state-hover);
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
padding: 1px 5px;
}
.demo-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 14px;
}
.demo-actions button {
background: transparent;
border: 1px solid var(--smart-border);
border-radius: var(--smart-border-radius);
color: var(--smart-text-secondary-color);
cursor: pointer;
font-family: inherit;
font-size: 11px;
letter-spacing: .06em;
padding: 7px 14px;
text-transform: uppercase;
}
.demo-actions button:hover {
background: var(--smart-ui-state-hover);
border-color: var(--smart-ui-state-border-hover);
color: var(--smart-ui-state-color-hover);
}
.demo-log {
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
margin-top: 14px;
color: var(--smart-text-tertiary-color);
}
/* ── Demo chrome, shared by the Industrial demos ─────────────────────────
Headings, prose, buttons and notes are the same on every demo page. This block
is appended by scripts and comes last, so it wins over the rules above. */
.smart-demo-container {
padding: 4px 0 32px;
}
.smart-demo-container section {
margin-bottom: 36px;
}
.smart-demo-container section h2 {
margin: 0 0 8px;
font-size: 17px;
font-weight: 600;
line-height: 1.35;
color: var(--smart-text-color);
}
.smart-demo-container #overview h2 {
margin-bottom: 10px;
font-size: 20px;
max-width: 78ch;
}
.smart-demo-container section > p {
margin: 0 0 14px;
max-width: 84ch;
font-size: 14px;
line-height: 1.55;
color: var(--smart-text-secondary-color);
}
.smart-demo-container code {
padding: 1px 5px;
border-radius: 2px;
background: var(--smart-ui-state-hover);
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
}
.smart-demo-container .demo-actions {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px 10px;
margin-top: 12px;
}
.smart-demo-container .demo-actions label {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 13px;
color: var(--smart-text-color);
}
.smart-demo-container .demo-actions button,
.smart-demo-container .demo-button {
min-height: 30px;
padding: 0 12px;
border: 1px solid var(--smart-border);
border-radius: 3px;
background: var(--smart-industrial-panel-background, var(--smart-surface));
color: var(--smart-text-color);
font: inherit;
font-size: 13px;
font-weight: 500;
letter-spacing: 0;
text-transform: none;
cursor: pointer;
}
.smart-demo-container .demo-actions button:hover,
.smart-demo-container .demo-button:hover {
border-color: var(--smart-ui-state-border-hover);
background: var(--smart-ui-state-hover);
color: var(--smart-ui-state-color-hover);
}
.smart-demo-container .demo-actions button.demo-button-primary,
.smart-demo-container .demo-button-primary {
border-color: var(--smart-primary);
background: var(--smart-primary);
color: var(--smart-primary-color, #fff);
}
.smart-demo-container .demo-actions button[disabled],
.smart-demo-container .demo-button[disabled] {
opacity: 0.5;
cursor: default;
}
.smart-demo-container .demo-actions select,
.smart-demo-container .demo-actions input[type="text"],
.smart-demo-container .demo-actions input[type="number"] {
min-height: 30px;
padding: 2px 6px;
border: 1px solid var(--smart-border);
border-radius: 3px;
background: var(--smart-background);
color: var(--smart-text-color);
font: inherit;
font-size: 13px;
}
.smart-demo-container .hint,
.smart-demo-container .demo-log,
.smart-demo-container .demo-counts {
margin: 10px 0 0;
font-size: 12px;
line-height: 1.5;
color: var(--smart-text-secondary-color);
opacity: 1;
}
.smart-demo-container .demo-log,
.smart-demo-container .demo-counts {
font-family: Consolas, "Courier New", monospace;
}
.smart-demo-container .log {
list-style: none;
margin: 10px 0 0;
padding: 0;
font-size: 12px;
font-variant-numeric: tabular-nums;
color: var(--smart-text-secondary-color);
opacity: 1;
}
.smart-demo-container .log li {
padding: 2px 0;
}
The same demo in Vue, generated from the HTML demo. The frameworks guide has the project setup.
vue/App.vue
<template>
<div class="vue-root" theme="industrial" v-pre>
<div class="smart-demo-container">
<section id="overview">
<h2>Smart.AlarmBanner keeps the alarm that the operator should deal with next in view.</h2>
<p>
The banner receives the list of active alarms and selects the alarms to show. Unacknowledged alarms come first, then higher priorities, and among equal alarms the oldest one, which has been waiting the longest. Priorities follow ISA-18.2, and an unacknowledged priority 1 alarm blinks until it is acknowledged.
</p>
</section>
<section>
<h2>Live banner</h2>
<smart-alarm-banner id="banner"></smart-alarm-banner>
<div class="demo-actions">
<button type="button" id="raiseCritical">Raise critical</button>
<button type="button" id="raiseWarning">Raise warning</button>
<button type="button" id="raiseAdvisory">Raise advisory</button>
<button type="button" id="ackAll">Acknowledge all</button>
<button type="button" id="clearAll">Clear</button>
</div>
<p class="demo-log" id="log">Events raised by the banner are listed here.</p>
</section>
<section>
<h2>Showing more than one alarm</h2>
<p>
The <code>maxVisible</code> property shows a stack of alarms instead of a single row, for example on a wall display that covers several areas.
</p>
<smart-alarm-banner id="stack" max-visible="3"></smart-alarm-banner>
</section>
<section>
<h2>Read-only banner</h2>
<p>
Turn off the acknowledge button and the timestamps for an informational strip. Both are on by default. Boolean attributes work by presence, so <code>show-acknowledge="false"</code> would turn the button <em>on</em>; set the properties from JavaScript instead.
</p>
<smart-alarm-banner id="readonlyBanner"></smart-alarm-banner>
</section>
<section>
<h2>No active alarms</h2>
<smart-alarm-banner id="emptyBanner" empty-message="All monitored points within limits"></smart-alarm-banner>
</section>
</div>
</div>
</template>
<script>
import { onMounted } from "vue";
import "smart-industrial/source/styles/smart.default.css";
import "smart-industrial/source/styles/smart.industrial-elements.css";
import "smart-industrial/source/styles/smart.industrial.css";
import "smart-industrial/source/styles/smart.industrial-dark.css";
import "smart-industrial/source/modules/smart.alarmbanner.js";
const CATALOG = {
1: [
{ tag: 'PT-2101', message: 'Header pressure high' },
{ tag: 'TT-2108', message: 'Reactor outlet temperature high-high' },
{ tag: 'VT-2101', message: 'Feed pump A bearing vibration high' }
],
2: [
{ tag: 'LT-2202', message: 'Tank TK-02 level low' },
{ tag: 'CP-2150', message: 'Air compressor discharge temperature high' }
],
3: [
{ tag: 'AI-2130', message: 'Product pH outside band' },
{ tag: 'PT-2140', message: 'Instrument air pressure low' }
]
};
let nextId = 1;
/* An alarm of the priority; ageMinutes puts its timestamp in the past, so the initial
alarms have been waiting for different lengths of time, as real ones have. */
function raise(priority, ageMinutes) {
const options = CATALOG[priority],
pick = options[Math.floor(Math.random() * options.length)];
return {
id: nextId++,
tag: pick.tag,
message: pick.message,
priority: priority,
timestamp: new Date(Date.now() - (ageMinutes || 0) * 60000),
acknowledged: false
};
}
export default {
name: "app",
setup() {
onMounted(() => {
/* The elements render after Vue has mounted them, and the demo's logic - written for
a page's load event - reads their parts, so it waits for every element in the tree
to have rendered first. */
const pending = Array.from(document.querySelector(".vue-root").querySelectorAll("*"))
.filter((element) => typeof element.whenRendered === "function" && !element.isRendered)
.map((element) => element.whenRendered());
Promise.all(pending).then(() => {
const banner = document.getElementById('banner'),
stack = document.getElementById('stack'),
readonlyBanner = document.getElementById('readonlyBanner'),
emptyBanner = document.getElementById('emptyBanner'),
log = document.getElementById('log');
/* ── Live banner ─────────────────────────────────────────────────── */
let alarms = [raise(2, 14), raise(3, 6)];
/* Treat the list as immutable. Pushing onto the array the banner already
holds mutates its own copy, so the follow-up assignment looks unchanged
and no redraw happens. Build a new array instead - or, if you do mutate
in place, call banner.redraw() yourself. */
function setAlarms(next) {
alarms = next;
banner.alarms = next;
}
setAlarms(alarms);
function note(text) {
log.textContent = text;
}
/* The element never mutates the alarms - it reports the operator's intent
and the host decides what acknowledgement means. */
banner.addEventListener('acknowledge', function (event) {
const alarm = event.detail.alarm;
setAlarms(alarms.map(function (item) {
return item.id === alarm.id ? Object.assign({}, item, { acknowledged: true }) : item;
}));
note('acknowledge → ' + alarm.tag + ' (' + alarm.message + ')');
});
banner.addEventListener('alarmClick', function (event) {
note('alarmClick: ' + event.detail.alarm.tag + '. Open the alarm detail here.');
});
document.getElementById('raiseCritical').addEventListener('click', function () {
setAlarms(alarms.concat(raise(1)));
note('raised a priority 1 alarm');
});
document.getElementById('raiseWarning').addEventListener('click', function () {
setAlarms(alarms.concat(raise(2)));
note('raised a priority 2 alarm');
});
document.getElementById('raiseAdvisory').addEventListener('click', function () {
setAlarms(alarms.concat(raise(3)));
note('raised a priority 3 alarm');
});
document.getElementById('ackAll').addEventListener('click', function () {
banner.acknowledgeAll();
note('acknowledgeAll() raised one acknowledge event per unacknowledged alarm');
});
document.getElementById('clearAll').addEventListener('click', function () {
setAlarms([]);
note('alarm list cleared');
});
/* ── Static examples ─────────────────────────────────────────────── */
stack.alarms = [raise(1, 2), raise(2, 9), raise(3, 27), raise(3, 4)];
readonlyBanner.showAcknowledge = false;
readonlyBanner.showTimestamps = false;
readonlyBanner.alarms = [raise(2, 11)];
emptyBanner.alarms = [];
});
});
}
};
</script>
<style>
.smart-demo-container {
max-width: 980px;
}
section {
margin-bottom: 34px;
}
section h2 {
font-size: 16px;
font-weight: 500;
margin: 0 0 8px;
}
section p {
color: var(--smart-text-secondary-color);
margin: 0 0 14px;
max-width: 74ch;
}
code {
background: var(--smart-ui-state-hover);
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
padding: 1px 5px;
}
.demo-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 14px;
}
.demo-actions button {
background: transparent;
border: 1px solid var(--smart-border);
border-radius: var(--smart-border-radius);
color: var(--smart-text-secondary-color);
cursor: pointer;
font-family: inherit;
font-size: 11px;
letter-spacing: .06em;
padding: 7px 14px;
text-transform: uppercase;
}
.demo-actions button:hover {
background: var(--smart-ui-state-hover);
border-color: var(--smart-ui-state-border-hover);
color: var(--smart-ui-state-color-hover);
}
.demo-log {
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
margin-top: 14px;
color: var(--smart-text-tertiary-color);
}
/* ── Demo chrome, shared by the Industrial demos ─────────────────────────
Headings, prose, buttons and notes are the same on every demo page. This block
is appended by scripts and comes last, so it wins over the rules above. */
.smart-demo-container {
padding: 4px 0 32px;
}
.smart-demo-container section {
margin-bottom: 36px;
}
.smart-demo-container section h2 {
margin: 0 0 8px;
font-size: 17px;
font-weight: 600;
line-height: 1.35;
color: var(--smart-text-color);
}
.smart-demo-container #overview h2 {
margin-bottom: 10px;
font-size: 20px;
max-width: 78ch;
}
.smart-demo-container section > p {
margin: 0 0 14px;
max-width: 84ch;
font-size: 14px;
line-height: 1.55;
color: var(--smart-text-secondary-color);
}
.smart-demo-container code {
padding: 1px 5px;
border-radius: 2px;
background: var(--smart-ui-state-hover);
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
}
.smart-demo-container .demo-actions {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px 10px;
margin-top: 12px;
}
.smart-demo-container .demo-actions label {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 13px;
color: var(--smart-text-color);
}
.smart-demo-container .demo-actions button,
.smart-demo-container .demo-button {
min-height: 30px;
padding: 0 12px;
border: 1px solid var(--smart-border);
border-radius: 3px;
background: var(--smart-industrial-panel-background, var(--smart-surface));
color: var(--smart-text-color);
font: inherit;
font-size: 13px;
font-weight: 500;
letter-spacing: 0;
text-transform: none;
cursor: pointer;
}
.smart-demo-container .demo-actions button:hover,
.smart-demo-container .demo-button:hover {
border-color: var(--smart-ui-state-border-hover);
background: var(--smart-ui-state-hover);
color: var(--smart-ui-state-color-hover);
}
.smart-demo-container .demo-actions button.demo-button-primary,
.smart-demo-container .demo-button-primary {
border-color: var(--smart-primary);
background: var(--smart-primary);
color: var(--smart-primary-color, #fff);
}
.smart-demo-container .demo-actions button[disabled],
.smart-demo-container .demo-button[disabled] {
opacity: 0.5;
cursor: default;
}
.smart-demo-container .demo-actions select,
.smart-demo-container .demo-actions input[type="text"],
.smart-demo-container .demo-actions input[type="number"] {
min-height: 30px;
padding: 2px 6px;
border: 1px solid var(--smart-border);
border-radius: 3px;
background: var(--smart-background);
color: var(--smart-text-color);
font: inherit;
font-size: 13px;
}
.smart-demo-container .hint,
.smart-demo-container .demo-log,
.smart-demo-container .demo-counts {
margin: 10px 0 0;
font-size: 12px;
line-height: 1.5;
color: var(--smart-text-secondary-color);
opacity: 1;
}
.smart-demo-container .demo-log,
.smart-demo-container .demo-counts {
font-family: Consolas, "Courier New", monospace;
}
.smart-demo-container .log {
list-style: none;
margin: 10px 0 0;
padding: 0;
font-size: 12px;
font-variant-numeric: tabular-nums;
color: var(--smart-text-secondary-color);
opacity: 1;
}
.smart-demo-container .log li {
padding: 2px 0;
}
</style>
What the demo shows
- Live banner
- Showing more than one alarm
- Read-only banner
- No active alarms
On the operator screens
AlarmsAndonBatchKPI WallLocalizationMimicMobile SupervisorPump Station