Component demo · Test and measurement
Resource Picker
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>Resource Picker 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.ResourcePicker is the control a test station opens with: which instrument, which channel. It lists the resources the application has found, grouped by interface and named by alias, and reads a typed resource string before it is accepted.</h2>
<p>
It is the VISA resource name and DAQ channel control of a test-and-measurement front panel, with the application in charge of discovery: the refresh button raises <code>refreshRequest</code>, the application searches its buses and assigns the list, and the picker shows it busy in between. GPIB, LAN, USB, serial, PXI and DAQ names are parsed into their parts, so <code>TCPIP0::10.0.0.5::inst0::INSTR</code> is known to be an instrument at an address and <code>Dev1/ai0:3</code> a device and a channel range.
</p>
</section>
<section>
<h2>The bench</h2>
<p>
The instruments a discovery found on this bench: a DMM and a power supply on GPIB, a scope on the LAN, a source measure unit on USB and a serial-port balance, one of them switched off. Open the list, type part of a name or an alias to filter it, and press Enter. <em>Refresh</em> runs a simulated discovery that takes a moment and finds one more instrument. With custom entries allowed, a resource string the list does not have is offered once it parses.
</p>
<div class="bench">
<smart-resource-picker id="instrument" label="Instrument" value="GPIB0::16::INSTR" allow-custom></smart-resource-picker>
<smart-resource-picker id="channel" label="Acquisition channel" value="Dev1/ai0:3"></smart-resource-picker>
<div class="parsed">
<div class="parsed-title">change event</div>
<pre id="parsed" class="parsed-body">Select a resource.</pre>
</div>
</div>
<div class="demo-actions">
<label><input type="checkbox" id="availableOnly" /> Available resources only</label>
<label><input type="checkbox" id="lanOnly" /> LAN and USB only</label>
<label><input type="checkbox" id="custom" checked /> Allow a typed resource</label>
</div>
<p class="hint">Keyboard: the field is a combobox. Type to filter, Down and Up move through the list, Enter selects, Escape closes. The refresh button is a tab stop of its own. The interface of the resource in force is shown as a tag in the field and read with the name.</p>
</section>
<section>
<h2>Reading a resource string</h2>
<p>
<code>ResourcePicker.parse()</code> is a static method, usable without an element: a page that stores a resource string can check it and show its parts. Type a string to see what it reads as.
</p>
<div class="demo-actions">
<label>Resource string <input type="text" id="tryString" value="USB0::0x0957::0x1755::MY12345678::INSTR" size="44" /></label>
<code id="tryResult"></code>
</div>
</section>
</div>
<script type="module" src="node_modules/smart-industrial/source/modules/smart.resourcepicker.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" />
/* Discovery is simulated: a real station asks its VISA runtime or DAQ driver and assigns
the same shape. The seam is the refreshRequest event and the resources property. */
const BENCH = [
{ name: 'GPIB0::16::INSTR', alias: 'DMM', description: 'Keysight 34465A, 6½ digit multimeter', available: true },
{ name: 'GPIB0::5::INSTR', alias: 'PSU', description: 'Keysight E36313A, triple output supply', available: true },
{ name: 'TCPIP0::192.168.10.21::inst0::INSTR', alias: 'Scope', description: 'Tektronix MSO46, 4 channels', available: true },
{ name: 'TCPIP0::192.168.10.30::5025::SOCKET', alias: 'Load', description: 'Electronic load, raw socket', available: false },
{ name: 'USB0::0x0957::0x1755::MY12345678::INSTR', alias: 'SMU', description: 'Keysight B2902A, source measure unit', available: true },
{ name: 'ASRL3::INSTR', alias: 'Balance', description: 'Mettler Toledo MS204, 9600 8N1', available: true }
];
const CHANNELS = [
{ name: 'Dev1/ai0', alias: 'Vin', description: 'Voltage input, ±10 V', available: true },
{ name: 'Dev1/ai0:3', alias: 'Vin bank', description: 'Four voltage inputs, ±10 V', available: true },
{ name: 'Dev2/ai0', alias: 'TC1', description: 'Thermocouple, type K', available: true },
{ name: 'Dev2/ai0:7', alias: 'TC bank', description: 'Eight thermocouples', available: true },
{ name: 'Dev3/ao0', alias: 'Drive', description: 'Analog output, ±10 V', available: true },
{ name: 'Dev1/ctr0', alias: 'Encoder', description: 'Counter input, quadrature', available: false }
];
window.onload = function () {
const instrument = document.getElementById('instrument'),
channel = document.getElementById('channel'),
parsed = document.getElementById('parsed'),
availableOnly = document.getElementById('availableOnly'),
lanOnly = document.getElementById('lanOnly'),
custom = document.getElementById('custom'),
tryString = document.getElementById('tryString'),
tryResult = document.getElementById('tryResult');
instrument.resources = BENCH.slice();
channel.resources = CHANNELS.slice();
channel.refreshable = false;
function report(event) {
parsed.textContent = JSON.stringify({
value: event.detail.value,
previousValue: event.detail.previousValue,
alias: event.detail.resource ? event.detail.resource.alias : null,
parsed: event.detail.parsed
}, null, 2);
}
instrument.addEventListener('change', report);
channel.addEventListener('change', report);
/* Discovery: the picker shows itself busy while the application searches; here the
search takes a second and finds a spectrum analyser that was switched on meanwhile. */
instrument.addEventListener('refreshRequest', function () {
instrument.busy = true;
setTimeout(function () {
const found = BENCH.slice();
found.push({ name: 'TCPIP0::192.168.10.44::inst0::INSTR', alias: 'SA', description: 'Rohde & Schwarz FSV3007, spectrum analyser', available: true });
found[3] = Object.assign({}, found[3], { available: true });
instrument.resources = found;
instrument.busy = false;
parsed.textContent = 'refreshRequest: discovery found ' + found.length + ' resources.';
}, 1200);
});
availableOnly.addEventListener('change', function () {
instrument.availableOnly = availableOnly.checked;
channel.availableOnly = availableOnly.checked;
});
lanOnly.addEventListener('change', function () {
instrument.kinds = lanOnly.checked ? ['tcpip', 'usb'] : [];
});
custom.addEventListener('change', function () {
instrument.allowCustom = custom.checked;
});
/* ── parse() on its own ────────────────────────────────────────────── */
const ResourcePicker = customElements.get('smart-resource-picker');
function tryParse() {
const result = ResourcePicker.parse(tryString.value);
tryResult.textContent = result ? JSON.stringify(result) : 'empty';
}
tryString.addEventListener('input', tryParse);
tryParse();
};
styles.css
.bench {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
gap: 18px 28px;
}
smart-resource-picker {
width: 360px;
}
.parsed {
flex: 1 1 320px;
max-width: 480px;
}
.parsed-title {
margin-bottom: 4px;
font-size: 11px;
font-weight: 600;
letter-spacing: .08em;
text-transform: uppercase;
color: var(--smart-text-secondary-color);
}
.parsed-body {
margin: 0;
padding: 10px 12px;
border: 1px solid var(--smart-border);
border-radius: var(--smart-border-radius);
background: var(--smart-industrial-panel-background, var(--smart-surface));
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
line-height: 1.5;
white-space: pre-wrap;
color: var(--smart-text-color);
}
#tryResult {
display: block;
max-width: 700px;
white-space: pre-wrap;
word-break: break-all;
}
/* ── 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.ResourcePicker is the control a test station opens with: which instrument, which channel. It lists the resources the application has found, grouped by interface and named by alias, and reads a typed resource string before it is accepted.</h2>
<p>
It is the VISA resource name and DAQ channel control of a test-and-measurement front panel, with the application in charge of discovery: the refresh button raises <code>refreshRequest</code>, the application searches its buses and assigns the list, and the picker shows it busy in between. GPIB, LAN, USB, serial, PXI and DAQ names are parsed into their parts, so <code>TCPIP0::10.0.0.5::inst0::INSTR</code> is known to be an instrument at an address and <code>Dev1/ai0:3</code> a device and a channel range.
</p>
</section>
<section>
<h2>The bench</h2>
<p>
The instruments a discovery found on this bench: a DMM and a power supply on GPIB, a scope on the LAN, a source measure unit on USB and a serial-port balance, one of them switched off. Open the list, type part of a name or an alias to filter it, and press Enter. <em>Refresh</em> runs a simulated discovery that takes a moment and finds one more instrument. With custom entries allowed, a resource string the list does not have is offered once it parses.
</p>
<div class="bench">
<smart-resource-picker id="instrument" label="Instrument" value="GPIB0::16::INSTR" [allowCustom]="true"></smart-resource-picker>
<smart-resource-picker id="channel" label="Acquisition channel" value="Dev1/ai0:3"></smart-resource-picker>
<div class="parsed">
<div class="parsed-title">change event</div>
<pre id="parsed" class="parsed-body">Select a resource.</pre>
</div>
</div>
<div class="demo-actions">
<label><input type="checkbox" id="availableOnly" /> Available resources only</label>
<label><input type="checkbox" id="lanOnly" /> LAN and USB only</label>
<label><input type="checkbox" id="custom" checked /> Allow a typed resource</label>
</div>
<p class="hint">Keyboard: the field is a combobox. Type to filter, Down and Up move through the list, Enter selects, Escape closes. The refresh button is a tab stop of its own. The interface of the resource in force is shown as a tag in the field and read with the name.</p>
</section>
<section>
<h2>Reading a resource string</h2>
<p>
<code>ResourcePicker.parse()</code> is a static method, usable without an element: a page that stores a resource string can check it and show its parts. Type a string to see what it reads as.
</p>
<div class="demo-actions">
<label>Resource string <input type="text" id="tryString" value="USB0::0x0957::0x1755::MY12345678::INSTR" size="44" /></label>
<code id="tryResult"></code>
</div>
</section>
</div>
</div>
angular/app.component.ts
import { Component, AfterViewInit, OnDestroy } from '@angular/core';
import { ResourcePickerModule } from 'smart-industrial/angular/resourcepicker';
import { runDemo } from './demo';
/**
* Smart.UI Industrial - ResourcePicker 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: [ResourcePickerModule],
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. */
/* Discovery is simulated: a real station asks its VISA runtime or DAQ driver and assigns
the same shape. The seam is the refreshRequest event and the resources property. */
const BENCH = [
{ name: 'GPIB0::16::INSTR', alias: 'DMM', description: 'Keysight 34465A, 6½ digit multimeter', available: true },
{ name: 'GPIB0::5::INSTR', alias: 'PSU', description: 'Keysight E36313A, triple output supply', available: true },
{ name: 'TCPIP0::192.168.10.21::inst0::INSTR', alias: 'Scope', description: 'Tektronix MSO46, 4 channels', available: true },
{ name: 'TCPIP0::192.168.10.30::5025::SOCKET', alias: 'Load', description: 'Electronic load, raw socket', available: false },
{ name: 'USB0::0x0957::0x1755::MY12345678::INSTR', alias: 'SMU', description: 'Keysight B2902A, source measure unit', available: true },
{ name: 'ASRL3::INSTR', alias: 'Balance', description: 'Mettler Toledo MS204, 9600 8N1', available: true }
];
const CHANNELS = [
{ name: 'Dev1/ai0', alias: 'Vin', description: 'Voltage input, ±10 V', available: true },
{ name: 'Dev1/ai0:3', alias: 'Vin bank', description: 'Four voltage inputs, ±10 V', available: true },
{ name: 'Dev2/ai0', alias: 'TC1', description: 'Thermocouple, type K', available: true },
{ name: 'Dev2/ai0:7', alias: 'TC bank', description: 'Eight thermocouples', available: true },
{ name: 'Dev3/ao0', alias: 'Drive', description: 'Analog output, ±10 V', available: true },
{ name: 'Dev1/ctr0', alias: 'Encoder', description: 'Counter input, quadrature', available: false }
];
export function runDemo(): void {
const instrument = document.getElementById('instrument'),
channel = document.getElementById('channel'),
parsed = document.getElementById('parsed'),
availableOnly = document.getElementById('availableOnly'),
lanOnly = document.getElementById('lanOnly'),
custom = document.getElementById('custom'),
tryString = document.getElementById('tryString'),
tryResult = document.getElementById('tryResult');
instrument.resources = BENCH.slice();
channel.resources = CHANNELS.slice();
channel.refreshable = false;
function report(event) {
parsed.textContent = JSON.stringify({
value: event.detail.value,
previousValue: event.detail.previousValue,
alias: event.detail.resource ? event.detail.resource.alias : null,
parsed: event.detail.parsed
}, null, 2);
}
instrument.addEventListener('change', report);
channel.addEventListener('change', report);
/* Discovery: the picker shows itself busy while the application searches; here the
search takes a second and finds a spectrum analyser that was switched on meanwhile. */
instrument.addEventListener('refreshRequest', function () {
instrument.busy = true;
setTimeout(function () {
const found = BENCH.slice();
found.push({ name: 'TCPIP0::192.168.10.44::inst0::INSTR', alias: 'SA', description: 'Rohde & Schwarz FSV3007, spectrum analyser', available: true });
found[3] = Object.assign({}, found[3], { available: true });
instrument.resources = found;
instrument.busy = false;
parsed.textContent = 'refreshRequest: discovery found ' + found.length + ' resources.';
}, 1200);
});
availableOnly.addEventListener('change', function () {
instrument.availableOnly = availableOnly.checked;
channel.availableOnly = availableOnly.checked;
});
lanOnly.addEventListener('change', function () {
instrument.kinds = lanOnly.checked ? ['tcpip', 'usb'] : [];
});
custom.addEventListener('change', function () {
instrument.allowCustom = custom.checked;
});
/* ── parse() on its own ────────────────────────────────────────────── */
const ResourcePicker = customElements.get('smart-resource-picker');
function tryParse() {
const result = ResourcePicker.parse(tryString.value);
tryResult.textContent = result ? JSON.stringify(result) : 'empty';
}
tryString.addEventListener('input', tryParse);
tryParse();
}
angular/app.component.css
.bench {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
gap: 18px 28px;
}
smart-resource-picker {
width: 360px;
}
.parsed {
flex: 1 1 320px;
max-width: 480px;
}
.parsed-title {
margin-bottom: 4px;
font-size: 11px;
font-weight: 600;
letter-spacing: .08em;
text-transform: uppercase;
color: var(--smart-text-secondary-color);
}
.parsed-body {
margin: 0;
padding: 10px 12px;
border: 1px solid var(--smart-border);
border-radius: var(--smart-border-radius);
background: var(--smart-industrial-panel-background, var(--smart-surface));
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
line-height: 1.5;
white-space: pre-wrap;
color: var(--smart-text-color);
}
#tryResult {
display: block;
max-width: 700px;
white-space: pre-wrap;
word-break: break-all;
}
/* ── 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 { ResourcePicker } from 'smart-industrial/react/resourcepicker';
/* The demo logic is the HTML demo's: it feeds the elements the way a plant's data would. */
/* Discovery is simulated: a real station asks its VISA runtime or DAQ driver and assigns
the same shape. The seam is the refreshRequest event and the resources property. */
const BENCH = [
{ name: 'GPIB0::16::INSTR', alias: 'DMM', description: 'Keysight 34465A, 6½ digit multimeter', available: true },
{ name: 'GPIB0::5::INSTR', alias: 'PSU', description: 'Keysight E36313A, triple output supply', available: true },
{ name: 'TCPIP0::192.168.10.21::inst0::INSTR', alias: 'Scope', description: 'Tektronix MSO46, 4 channels', available: true },
{ name: 'TCPIP0::192.168.10.30::5025::SOCKET', alias: 'Load', description: 'Electronic load, raw socket', available: false },
{ name: 'USB0::0x0957::0x1755::MY12345678::INSTR', alias: 'SMU', description: 'Keysight B2902A, source measure unit', available: true },
{ name: 'ASRL3::INSTR', alias: 'Balance', description: 'Mettler Toledo MS204, 9600 8N1', available: true }
];
const CHANNELS = [
{ name: 'Dev1/ai0', alias: 'Vin', description: 'Voltage input, ±10 V', available: true },
{ name: 'Dev1/ai0:3', alias: 'Vin bank', description: 'Four voltage inputs, ±10 V', available: true },
{ name: 'Dev2/ai0', alias: 'TC1', description: 'Thermocouple, type K', available: true },
{ name: 'Dev2/ai0:7', alias: 'TC bank', description: 'Eight thermocouples', available: true },
{ name: 'Dev3/ao0', alias: 'Drive', description: 'Analog output, ±10 V', available: true },
{ name: 'Dev1/ctr0', alias: 'Encoder', description: 'Counter input, quadrature', available: 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 instrument = document.getElementById('instrument'),
channel = document.getElementById('channel'),
parsed = document.getElementById('parsed'),
availableOnly = document.getElementById('availableOnly'),
lanOnly = document.getElementById('lanOnly'),
custom = document.getElementById('custom'),
tryString = document.getElementById('tryString'),
tryResult = document.getElementById('tryResult');
instrument.resources = BENCH.slice();
channel.resources = CHANNELS.slice();
channel.refreshable = false;
function report(event) {
parsed.textContent = JSON.stringify({
value: event.detail.value,
previousValue: event.detail.previousValue,
alias: event.detail.resource ? event.detail.resource.alias : null,
parsed: event.detail.parsed
}, null, 2);
}
instrument.addEventListener('change', report);
channel.addEventListener('change', report);
/* Discovery: the picker shows itself busy while the application searches; here the
search takes a second and finds a spectrum analyser that was switched on meanwhile. */
instrument.addEventListener('refreshRequest', function () {
instrument.busy = true;
setTimeout(function () {
const found = BENCH.slice();
found.push({ name: 'TCPIP0::192.168.10.44::inst0::INSTR', alias: 'SA', description: 'Rohde & Schwarz FSV3007, spectrum analyser', available: true });
found[3] = Object.assign({}, found[3], { available: true });
instrument.resources = found;
instrument.busy = false;
parsed.textContent = 'refreshRequest: discovery found ' + found.length + ' resources.';
}, 1200);
});
availableOnly.addEventListener('change', function () {
instrument.availableOnly = availableOnly.checked;
channel.availableOnly = availableOnly.checked;
});
lanOnly.addEventListener('change', function () {
instrument.kinds = lanOnly.checked ? ['tcpip', 'usb'] : [];
});
custom.addEventListener('change', function () {
instrument.allowCustom = custom.checked;
});
/* ── parse() on its own ────────────────────────────────────────────── */
const ResourcePicker = customElements.get('smart-resource-picker');
function tryParse() {
const result = ResourcePicker.parse(tryString.value);
tryResult.textContent = result ? JSON.stringify(result) : 'empty';
}
tryString.addEventListener('input', tryParse);
tryParse();
});
return () => {
unmounted = true;
};
}, []);
return (
<div className="react-root" ref={root} theme="industrial">
<div className="smart-demo-container">
<section id="overview">
<h2>Smart.ResourcePicker is the control a test station opens with: which instrument, which channel. It lists the resources the application has found, grouped by interface and named by alias, and reads a typed resource string before it is accepted.</h2>
<p>It is the VISA resource name and DAQ channel control of a test-and-measurement front panel, with the application in charge of discovery: the refresh button raises <code>refreshRequest</code>, the application searches its buses and assigns the list, and the picker shows it busy in between. GPIB, LAN, USB, serial, PXI and DAQ names are parsed into their parts, so <code>TCPIP0::10.0.0.5::inst0::INSTR</code> is known to be an instrument at an address and <code>Dev1/ai0:3</code> a device and a channel range.</p>
</section>
<section>
<h2>The bench</h2>
<p>The instruments a discovery found on this bench: a DMM and a power supply on GPIB, a scope on the LAN, a source measure unit on USB and a serial-port balance, one of them switched off. Open the list, type part of a name or an alias to filter it, and press Enter. <em>Refresh</em> runs a simulated discovery that takes a moment and finds one more instrument. With custom entries allowed, a resource string the list does not have is offered once it parses.</p>
<div className="bench">
<ResourcePicker id="instrument" label="Instrument" value="GPIB0::16::INSTR" allowCustom></ResourcePicker>
<ResourcePicker id="channel" label="Acquisition channel" value="Dev1/ai0:3"></ResourcePicker>
<div className="parsed">
<div className="parsed-title">change event</div>
<pre id="parsed" className="parsed-body">{"Select a resource."}</pre>
</div>
</div>
<div className="demo-actions">
<label><input type="checkbox" id="availableOnly" /> Available resources only</label>
<label><input type="checkbox" id="lanOnly" /> LAN and USB only</label>
<label><input type="checkbox" id="custom" defaultChecked /> Allow a typed resource</label>
</div>
<p className="hint">Keyboard: the field is a combobox. Type to filter, Down and Up move through the list, Enter selects, Escape closes. The refresh button is a tab stop of its own. The interface of the resource in force is shown as a tag in the field and read with the name.</p>
</section>
<section>
<h2>Reading a resource string</h2>
<p><code>ResourcePicker.parse()</code> is a static method, usable without an element: a page that stores a resource string can check it and show its parts. Type a string to see what it reads as.</p>
<div className="demo-actions">
<label>Resource string <input type="text" id="tryString" defaultValue="USB0::0x0957::0x1755::MY12345678::INSTR" size="44" /></label>
<code id="tryResult"></code>
</div>
</section>
</div>
</div>
);
};
export default App;
react/App.css
.bench {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
gap: 18px 28px;
}
smart-resource-picker {
width: 360px;
}
.parsed {
flex: 1 1 320px;
max-width: 480px;
}
.parsed-title {
margin-bottom: 4px;
font-size: 11px;
font-weight: 600;
letter-spacing: .08em;
text-transform: uppercase;
color: var(--smart-text-secondary-color);
}
.parsed-body {
margin: 0;
padding: 10px 12px;
border: 1px solid var(--smart-border);
border-radius: var(--smart-border-radius);
background: var(--smart-industrial-panel-background, var(--smart-surface));
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
line-height: 1.5;
white-space: pre-wrap;
color: var(--smart-text-color);
}
#tryResult {
display: block;
max-width: 700px;
white-space: pre-wrap;
word-break: break-all;
}
/* ── 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.ResourcePicker is the control a test station opens with: which instrument, which channel. It lists the resources the application has found, grouped by interface and named by alias, and reads a typed resource string before it is accepted.</h2>
<p>
It is the VISA resource name and DAQ channel control of a test-and-measurement front panel, with the application in charge of discovery: the refresh button raises <code>refreshRequest</code>, the application searches its buses and assigns the list, and the picker shows it busy in between. GPIB, LAN, USB, serial, PXI and DAQ names are parsed into their parts, so <code>TCPIP0::10.0.0.5::inst0::INSTR</code> is known to be an instrument at an address and <code>Dev1/ai0:3</code> a device and a channel range.
</p>
</section>
<section>
<h2>The bench</h2>
<p>
The instruments a discovery found on this bench: a DMM and a power supply on GPIB, a scope on the LAN, a source measure unit on USB and a serial-port balance, one of them switched off. Open the list, type part of a name or an alias to filter it, and press Enter. <em>Refresh</em> runs a simulated discovery that takes a moment and finds one more instrument. With custom entries allowed, a resource string the list does not have is offered once it parses.
</p>
<div class="bench">
<smart-resource-picker id="instrument" label="Instrument" value="GPIB0::16::INSTR" allow-custom></smart-resource-picker>
<smart-resource-picker id="channel" label="Acquisition channel" value="Dev1/ai0:3"></smart-resource-picker>
<div class="parsed">
<div class="parsed-title">change event</div>
<pre id="parsed" class="parsed-body">Select a resource.</pre>
</div>
</div>
<div class="demo-actions">
<label><input type="checkbox" id="availableOnly" /> Available resources only</label>
<label><input type="checkbox" id="lanOnly" /> LAN and USB only</label>
<label><input type="checkbox" id="custom" checked /> Allow a typed resource</label>
</div>
<p class="hint">Keyboard: the field is a combobox. Type to filter, Down and Up move through the list, Enter selects, Escape closes. The refresh button is a tab stop of its own. The interface of the resource in force is shown as a tag in the field and read with the name.</p>
</section>
<section>
<h2>Reading a resource string</h2>
<p>
<code>ResourcePicker.parse()</code> is a static method, usable without an element: a page that stores a resource string can check it and show its parts. Type a string to see what it reads as.
</p>
<div class="demo-actions">
<label>Resource string <input type="text" id="tryString" value="USB0::0x0957::0x1755::MY12345678::INSTR" size="44" /></label>
<code id="tryResult"></code>
</div>
</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.resourcepicker.js";
/* Discovery is simulated: a real station asks its VISA runtime or DAQ driver and assigns
the same shape. The seam is the refreshRequest event and the resources property. */
const BENCH = [
{ name: 'GPIB0::16::INSTR', alias: 'DMM', description: 'Keysight 34465A, 6½ digit multimeter', available: true },
{ name: 'GPIB0::5::INSTR', alias: 'PSU', description: 'Keysight E36313A, triple output supply', available: true },
{ name: 'TCPIP0::192.168.10.21::inst0::INSTR', alias: 'Scope', description: 'Tektronix MSO46, 4 channels', available: true },
{ name: 'TCPIP0::192.168.10.30::5025::SOCKET', alias: 'Load', description: 'Electronic load, raw socket', available: false },
{ name: 'USB0::0x0957::0x1755::MY12345678::INSTR', alias: 'SMU', description: 'Keysight B2902A, source measure unit', available: true },
{ name: 'ASRL3::INSTR', alias: 'Balance', description: 'Mettler Toledo MS204, 9600 8N1', available: true }
];
const CHANNELS = [
{ name: 'Dev1/ai0', alias: 'Vin', description: 'Voltage input, ±10 V', available: true },
{ name: 'Dev1/ai0:3', alias: 'Vin bank', description: 'Four voltage inputs, ±10 V', available: true },
{ name: 'Dev2/ai0', alias: 'TC1', description: 'Thermocouple, type K', available: true },
{ name: 'Dev2/ai0:7', alias: 'TC bank', description: 'Eight thermocouples', available: true },
{ name: 'Dev3/ao0', alias: 'Drive', description: 'Analog output, ±10 V', available: true },
{ name: 'Dev1/ctr0', alias: 'Encoder', description: 'Counter input, quadrature', available: 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 instrument = document.getElementById('instrument'),
channel = document.getElementById('channel'),
parsed = document.getElementById('parsed'),
availableOnly = document.getElementById('availableOnly'),
lanOnly = document.getElementById('lanOnly'),
custom = document.getElementById('custom'),
tryString = document.getElementById('tryString'),
tryResult = document.getElementById('tryResult');
instrument.resources = BENCH.slice();
channel.resources = CHANNELS.slice();
channel.refreshable = false;
function report(event) {
parsed.textContent = JSON.stringify({
value: event.detail.value,
previousValue: event.detail.previousValue,
alias: event.detail.resource ? event.detail.resource.alias : null,
parsed: event.detail.parsed
}, null, 2);
}
instrument.addEventListener('change', report);
channel.addEventListener('change', report);
/* Discovery: the picker shows itself busy while the application searches; here the
search takes a second and finds a spectrum analyser that was switched on meanwhile. */
instrument.addEventListener('refreshRequest', function () {
instrument.busy = true;
setTimeout(function () {
const found = BENCH.slice();
found.push({ name: 'TCPIP0::192.168.10.44::inst0::INSTR', alias: 'SA', description: 'Rohde & Schwarz FSV3007, spectrum analyser', available: true });
found[3] = Object.assign({}, found[3], { available: true });
instrument.resources = found;
instrument.busy = false;
parsed.textContent = 'refreshRequest: discovery found ' + found.length + ' resources.';
}, 1200);
});
availableOnly.addEventListener('change', function () {
instrument.availableOnly = availableOnly.checked;
channel.availableOnly = availableOnly.checked;
});
lanOnly.addEventListener('change', function () {
instrument.kinds = lanOnly.checked ? ['tcpip', 'usb'] : [];
});
custom.addEventListener('change', function () {
instrument.allowCustom = custom.checked;
});
/* ── parse() on its own ────────────────────────────────────────────── */
const ResourcePicker = customElements.get('smart-resource-picker');
function tryParse() {
const result = ResourcePicker.parse(tryString.value);
tryResult.textContent = result ? JSON.stringify(result) : 'empty';
}
tryString.addEventListener('input', tryParse);
tryParse();
});
});
}
};
</script>
<style>
.bench {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
gap: 18px 28px;
}
smart-resource-picker {
width: 360px;
}
.parsed {
flex: 1 1 320px;
max-width: 480px;
}
.parsed-title {
margin-bottom: 4px;
font-size: 11px;
font-weight: 600;
letter-spacing: .08em;
text-transform: uppercase;
color: var(--smart-text-secondary-color);
}
.parsed-body {
margin: 0;
padding: 10px 12px;
border: 1px solid var(--smart-border);
border-radius: var(--smart-border-radius);
background: var(--smart-industrial-panel-background, var(--smart-surface));
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
line-height: 1.5;
white-space: pre-wrap;
color: var(--smart-text-color);
}
#tryResult {
display: block;
max-width: 700px;
white-space: pre-wrap;
word-break: break-all;
}
/* ── 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
- The bench
- Reading a resource string
On the operator screens
Also in test and measurement
Bode PlotDigital WaveformIntensity GraphOctave BandsPolar PlotWaveform Graph