Overview

The components are standard Custom Elements. Any framework that can render a <div> can render a <smart-faceplate>, set its properties and listen to its events. The wrappers add typing, change detection and framework-specific event binding. Every property, method and event listed on a component's API page is available through each wrapper under the same name. That includes the classifiers that are also static: the faceplate's limitStateOf(), the status tile's stateFor() and the resource picker's parse() and isValid() can be called on the element or through a wrapper, and on the class without creating an element.

The request/confirm convention applies in every framework: a component raises a request event, the application writes the value to the plant and sets the property from the value the plant reports. For this reason the wrappers expose the request as an event and the value as an input, rather than as a two-way binding.

Two packages cover the four frameworks. smart-industrial contains the components, the styles and the React and Angular wrappers as separate entry points: smart-industrial/react and smart-industrial/angular for all components, and smart-industrial/react/<name> and smart-industrial/angular/<name> for a single component. Each wrapper imports its element from the package's source/ folder, so nothing else has to be installed. React and Angular are optional peer dependencies. Smart.Blazor.Industrial on NuGet provides the same components for Blazor, with the runtime in its wwwroot.

Every component has the same demo in Angular, React and Vue as in plain HTML. The Overview page of each component has a getting started section per framework that installs the package, loads the styles, imports the wrapper or module and shows the demo source. The framework demos are generated from the HTML demos, so they show the same content; only the wrapper differs.

Angular

npm install smart-industrial

In angular.json, add the theme and the components stylesheet to build.options.styles:

"styles": [
    "node_modules/smart-industrial/source/styles/smart.default.css",
    "node_modules/smart-industrial/source/styles/smart.industrial-elements.css"
]

Each component is its own entry point, an NgModule built with Angular 13. An NgModule application lists the module in its imports; a standalone component, from Angular 14, lists it in its own imports, as here. The component class itself is not standalone and cannot be imported on its own. The faceplate raises its requests only while interactive is set:

import { Component, ViewChild } from '@angular/core';
import { FaceplateComponent, FaceplateModule } from 'smart-industrial/angular/faceplate';

@Component({
    selector: 'app-loop',
    standalone: true,
    imports: [FaceplateModule],
    template: `
        <smart-faceplate #tic101 tag="TIC-101" description="Reactor temperature" unit="°C"
                         [processValue]="pv" [setpoint]="sp" [output]="op" mode="auto" [interactive]="true"
                         (onSetpointChange)="onSetpoint($event)"></smart-faceplate>`
})
export class LoopComponent {
    @ViewChild('tic101', { read: FaceplateComponent, static: false }) faceplate!: FaceplateComponent;
    pv = 72.4; sp = 75; op = 41.2;

    onSetpoint(event: CustomEvent) {
        const requested = event.detail.value;

        // write the request to the plant here; this example confirms it after half a second
        setTimeout(() => this.sp = requested, 500);
    }
}

The two stylesheets are about 1.7 MB, more than the 1 MB "initial" error budget of a new Angular project, so ng build stops until angular.json allows more:

"budgets": [
    { "type": "initial", "maximumWarning": "3mb", "maximumError": "4mb" }
]

The entry points are smart-industrial/angular/<name> for every Industrial component (alarmgrid, trend, statemachine, esignature and so on). smart-industrial/angular exports all of them and a SmartIndustrialModule that imports and exports the 54 modules. Each wrapper carries its own copy of the framework, so SmartIndustrialModule puts the whole library, about 9 MB, into the bundle: import the modules of the components the application uses. Events are exposed as (on<EventName>) outputs. A method that returns a value returns a Promise that resolves once the element has rendered, and has a Sync twin (alarmStateSync() next to alarmState()) that returns the value directly, or null before the element has rendered. The library is compiled in Ivy partial mode for Angular 13 and later.

React

npm install smart-industrial
import { useRef, useState } from 'react';
import 'smart-industrial/source/styles/smart.default.css';
import 'smart-industrial/source/styles/smart.industrial-elements.css';
import { Faceplate } from 'smart-industrial/react/faceplate';

export default function Loop() {
    const ref = useRef<Faceplate | null>(null);
    const [sp, setSp] = useState(75);

    // write the request to the plant here; this example confirms it after half a second
    const onSetpoint = (event: CustomEvent) => {
        const requested: number = event.detail.value;

        setTimeout(() => setSp(requested), 500);
    };

    return (
        <Faceplate ref={ref} tag="TIC-101" description="Reactor temperature" unit="°C"
                   processValue={72.4} setpoint={sp} output={41.2} mode="auto" interactive
                   onSetpointChange={onSetpoint} />
    );
}

The wrappers need React 18 or later: they render through react-dom/client. Every Industrial component has a React component of the same name under smart-industrial/react/<name>, and smart-industrial/react exports all 54 from one bundle of about 2.5 MB minified: import the components the application uses one by one. The components are typed from the same definitions as the web components. Each entry point is an ES module with a UMD build for require(), and the bundles are marked "use client".

Vue

Vue does not need a wrapper: custom elements are supported by Vue's template compiler. Use Vue 3.2.38 or later: earlier versions of Vue 3 never call a listener for a camelCase event such as @setpointChange. Install the package and configure the compiler to treat smart-* tags as custom elements:

npm install smart-industrial
// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
    plugins: [vue({ template: { compilerOptions: { isCustomElement: (tag) => tag.startsWith('smart-') } } })]
});
<script setup>
import { ref } from 'vue';
import 'smart-industrial/source/styles/smart.default.css';
import 'smart-industrial/source/styles/smart.industrial-elements.css';
import 'smart-industrial/source/modules/smart.faceplate.js';

const sp = ref(75);

// write the request to the plant here; this example confirms it after half a second
const onSetpoint = (event) => {
    const requested = event.detail.value;

    setTimeout(() => sp.value = requested, 500);
};
</script>

<template>
    <smart-faceplate tag="TIC-101" description="Reactor temperature" unit="°C"
                     :process-value="72.4" :setpoint="sp" :output="41.2" mode="auto" :interactive="true"
                     @setpointChange="onSetpoint" />
</template>

Use the .prop modifier (:alarms.prop="alarms") for a property that takes an array or an object, so that Vue sets the property instead of converting the value to an attribute string. The demo page of each Industrial component has the same demo as a Vue single-file component.

Blazor

Smart.Blazor.Industrial is the NuGet package. It contains the 54 components as Razor components with the same names, parameters in PascalCase, events as EventCallbacks named On<EventName>, and a typed <Component><Event>EventDetail class for the data of each event. Its wwwroot contains the Smart.Industrial runtime, so the package does not depend on Smart.Blazor and can be installed next to it. All types are in the Smart.Blazor.Industrial namespace, and an application with both packages calls AddSmart() and AddSmartIndustrial().

dotnet add package Smart.Blazor.Industrial

Add the styles and the runtime to the application page (App.razor, _Host.cshtml or index.html):

<link rel="stylesheet" href="_content/Smart.Blazor.Industrial/css/smart.default.css" />
<link rel="stylesheet" href="_content/Smart.Blazor.Industrial/css/smart.industrial-elements.css" />
<link rel="stylesheet" href="_content/Smart.Blazor.Industrial/css/smart.industrial.css" />

<script src="_content/Smart.Blazor.Industrial/js/smart.blazor.js"></script>
<script src="_content/Smart.Blazor.Industrial/js/smart.industrial.js"></script>

The third stylesheet is the ISA-101 light theme; it takes effect when <body> has theme="industrial" (smart.industrial-dark.css and theme="industrial-dark" for the dark one), and can be left out for the default theme.

Register the services in Program.cs and add the namespace to _Imports.razor:

using Smart.Blazor.Industrial;

builder.Services.AddSmartIndustrial();
@using Smart.Blazor.Industrial

Use the components with the request/confirm round trip in C#. In a Blazor Web App (.NET 8 and later) the page needs an interactive render mode, as here, or one for the whole app on <Routes @rendermode="InteractiveServer" /> in App.razor: a statically rendered page shows the components as empty boxes, and their parameters and events never arrive. On .NET 6 and 7 leave the @rendermode line out.

@page "/loop"
@rendermode InteractiveServer

<Faceplate Tag="TIC-101" Description="Reactor temperature" Unit="°C" Interactive="true"
           ProcessValue="@pv" Setpoint="@sp" Output="@op" Mode="FaceplateMode.Auto"
           OnSetpointChange="OnSetpoint" />

@code {
    object pv = 72.4, sp = 75, op = 41.2;

    void OnSetpoint(Event ev)
    {
        // Event is a Dictionary<string, dynamic>; "Detail" is the typed detail of the event.
        FaceplateSetpointChangeEventDetail detail = ev["Detail"];

        // write detail.Value to the plant here, then set the setpoint from the value the plant reports
        sp = detail.Value;
    }
}

Readings that an application may leave unset (ProcessValue, Setpoint, Output) are of type object. Steps, limits, positions and speeds are double, counts and indexes are int, and modes and states are enums (FaceplateMode.Auto, StatusTileState.Ok). Lists such as Alarms, Pens and Entries are object and accept any serialisable collection. Each event is also available as a C# event in the past tense (SetpointChanged next to OnSetpointChange). Where a property and such an event would have the same name, the property has an Is prefix (for example IsOpened next to the Opened event, raised with OnOpen, of the e-signature), and a method that would have the same name as its class has a Get prefix (GetSpectrum()). A few parameters and method arguments are typed int where the value can be fractional or larger than an int, among them Faceplate.RequestSetpoint(), the Setpoint of the thermometer and the panel meter, and the time arguments of Trend.SetWindow(), Zoom() and Pan(); set those values from JavaScript where a whole number does not fit.

As in the other frameworks, requests are refused on a component whose Interactive parameter is not set: a read-only faceplate does not raise OnSetpointChange. When both packages are installed and both namespaces are imported, the Event and License types exist in both and have to be qualified.

Shared type definitions

All four frameworks are typed from the same source. smart.industrial.d.ts, the Angular and React wrappers and the Blazor parameters are generated from one description per component, which is also the source of the API pages. A property renamed in the description is renamed in all of them.