JavaScript (used in Niagara UX modules)
JavaScript is the language of the modern Niagara interface. As Niagara UX moved from browser plug-ins to HTML5, JavaScript became the way custom dashboards, widgets, and interactions get built. Software Pile writes it the way front-end engineers do – maintainable, tested, and tied cleanly to live station data.
JavaScript in the Niagara Context
Niagara UX JavaScript is not generic web work – it lives inside the framework’s module system and talks to the station through BajaScript. Doing it well means knowing both modern JavaScript and how Niagara expects modules to be structured, loaded, and served from a Supervisor or JACE.
What We Build
- Custom widgets and dashboard components beyond the stock library
- Interactive controls with client-side validation and permission-aware actions
- Reusable component libraries shared across sites and projects
- Codebases built with a real pipeline so they can be maintained, not just deployed
Tell us what interface you need and your Niagara version. We build UX JavaScript that your team – or ours – can maintain for years.
How Is JavaScript Used in Niagara?
JavaScript is how modern Niagara interfaces are built. An N4 UX module packages JavaScript, CSS and markup into a signed Niagara module that the station serves to the browser, where it uses BajaScript to connect back to the live station and render data.
This is what allows a Niagara interface to be responsive, work on a tablet, present custom visualizations, and behave like a contemporary web application rather than a control-system screen.
What Is a Niagara UX Module?
A UX module is the packaging unit for browser-side Niagara code. It is built with a Webpack and Babel pipeline, signed like any other Niagara module, installed on the station, and versioned with it.
That packaging discipline matters more than it first appears. Interface code delivered as loose files copied onto a station is code that nobody can version, review, roll back or reliably reproduce after a migration. Delivering it as a proper module is what makes it maintainable by someone other than its author.
When Is Custom JavaScript Worth It Over Px?
Px handles conventional operator graphics efficiently, and for a standard equipment view on a desktop it is usually the better economic choice. Custom JavaScript earns its cost when the requirement genuinely exceeds that:
- Interfaces that must work properly on phones and tablets in the field.
- Visualizations Px does not offer — custom charts, floor plans, aggregated or comparative views.
- Tenant-facing or public dashboards that must look like the organization, not like a BMS.
- Views combining Niagara data with data from another system entirely.
- Guided multi-step workflows rather than single-value adjustment.
If someone proposes a full custom interface for a requirement Px already meets, ask what specifically Px cannot do. Sometimes there is a good answer; often there is not.
What Makes Niagara UX Work Perform Badly?
- Subscribing to every point on load rather than what is displayed, then never unsubscribing on teardown.
- Expensive BQL queries running on a view that refreshes frequently.
- Large uncompressed assets served to devices on site Wi-Fi.
- Testing only against a lab station, so real concurrency and real point counts are never exercised.
- Building against one Niagara version with no plan for the next upgrade.
Related services: Niagara graphics and operator dashboards · custom module development.
What Is Actually Inside a UX Module Jar?
Module names in Niagara carry a part suffix, and the suffix is functional rather than cosmetic. Browser code lives in a module named with a -ux suffix, and it normally ships next to a -rt module holding the server-side types that browser code reads. Install only the -ux part and the view loads until the moment it resolves a custom type; install only the -rt part and there is nothing for the browser to fetch.
The browser side of N4 loads code through an AMD loader, which is what the Webpack and Babel pipeline has to emit. A bundle configured to output ES modules, or a plain immediately invoked function, is fetched successfully and then never registers anything the loader is waiting on. Nothing appears in the station log, because nothing went wrong in the station.
That is why the first diagnostic is the browser network panel rather than the station. A wrong resource path and a bundle in the wrong format produce the same blank view, and only the network panel separates them: the first never arrives, the second arrives and does nothing. Knowing where else to look means knowing what the jar carries besides the bundle.
- module.xml, the manifest. It declares the module name, vendor, version and every module this one depends on, each with a minimum version. A dependency that is missing or too old stops the module loading when the station starts, not when somebody opens the view.
- The rc resource tree, which the station serves under /module/ by module name and then the path inside rc. That URL is what the browser requests, so a file moved during a build reorganization returns a 404 the widget has no way to report.
- Lexicon files, which supply display text by key at render time. A missing key renders as the key itself, which is a useful signal: text that looks wrong while the data looks right is a lexicon problem, not a binding problem.
- The signature. The jar is signed and the certificate has to be trusted by the host, so the signing key must be available wherever the build runs. A build that only works on the one workstation holding the certificate is a build no colleague and no build server can repeat, and depending on the host's configuration an untrusted signature produces either a warning at install or a refusal.
Why Does a Write From the Browser Sometimes Do Nothing?
A BajaScript write is asynchronous, and what comes back tells you the station accepted the request, not that anything changed. The set call returns a promise, and success means the request was valid and permitted. Code that fires the write and ignores the result hands the operator a control that always looks like it worked.
Permissions are enforced in the station, per user and per category, not in the widget. Hiding a button for a read-only user is presentation only: the same ORD can be exercised from the browser console or any other client on the same session, and the station will still refuse the write.
So a write that appears to do nothing splits into two shapes that look identical to the operator: the station refused it, or the station accepted it and the output did not move. The usual causes fall into both groups.
- The signed-in user lacks operator or admin write permission on the point's category, so the station refuses the request, and the widget only learns that if it handles the rejected promise.
- A higher level of the point's sixteen-level priority array is already holding a value, so the write is accepted and nothing visibly changes.
- The point is linked from a control program or a schedule that reasserts its own value on the next execution, so the change survives only until the next cycle.
- The write reached the station but the field device rejected or overwrote it, which shows up as the proxy point's read value diverging from the value written while the station reports success.
What Does Installing a UX Module Interrupt?
Installing a module is a platform operation rather than a station one. You connect to the host's platform, which has its own credentials separate from the station's user accounts, and the software manager is where jars are added, updated or removed.
The jar goes onto the host through the platform's software manager, and the station restarts before it registers a new or changed module. On a JACE that restart stops the station, which means it stops the control logic running on it, so a UX update on a controller carrying live sequences belongs in a maintenance window rather than in the middle of an afternoon. On a Supervisor the cost usually lands on open browser sessions instead of on control, which is a different conversation with the customer.
Across an estate, the same version has to exist on every host an operator browses to directly. If some users reach a JACE by its own address rather than through the Supervisor, the module is installed twice, and drift between the two copies stays invisible until a view that works from one address comes up blank from the other.
Where Should Data From Another System Be Joined?
There are two joins available and they fail differently. The browser can fetch the outside data itself alongside its BajaScript subscription, which means the endpoint has to be reachable from every device that opens the view, including phones on cellular, and the cross-origin and credential handling lands in the browser. Or the data comes into the station first, through a driver or a web service call, and BajaScript sees a single dataset.
Data brought into the station falls under Niagara's permission model, it can be alarmed and trended like anything else, and it is still there when the browser is closed. The cost is that every value becomes a component the station scans, stores and carries through every backup and migration.
Update rate and the unreachable case are what usually decide it. A feed that changes every few seconds and is only ever watched live is cheap in the browser and expensive as station points. A value that should raise an alarm when it goes stale has to be in the station, because a browser-side fetch that fails leaves a gap in one tile while the Niagara data around it keeps updating, and the view as a whole still looks healthy.
Which Niagara Version Should the Build Target?
Compatibility runs in one direction. A module built and declared against a newer Niagara version is refused by an older station, because the dependency minimum in module.xml is checked when the module loads, so the build environment has to track the oldest station in scope rather than the newest Workbench available.
The browser side carries its own version exposure. Framework versions ship their own front-end libraries, and a widget that reaches for the framework's copy instead of bundling its own inherits whatever that release happens to include. Bundling the dependency makes the jar larger and the upgrade quieter.
Signing requirements have also tightened across N4 releases, which is a constraint on the build rather than on the code. A jar signed under an older pipeline can need resigning before it will install on a newer host without a trust warning, even when nothing in the JavaScript has changed.
Frequently Asked Questions
Is a UX module the same thing as a web application that talks to Niagara?
No. A UX module is served by the station itself, so it shares the station's login session, its permission model and its update path, and it is versioned as part of the station's software. A separate web application hosted elsewhere reaches the station over an API and carries its own hosting, its own authentication, its own certificate handling and its own release process.
What has to exist before UX JavaScript can be written?
A station with the points and components the interface will display, on a Niagara version that is fixed and known, plus platform access to the hosts the module will be installed on and a code-signing certificate for the jar. The point naming matters as much as the design, because widgets bind to ORDs and a renaming exercise part-way through breaks those bindings. It also helps to know which user categories the view will be used under, since permissions decide what it can show and what it can write.
Does a UX module run on a JACE or does it need a Supervisor?
Either can serve it. The difference is that a JACE has limited memory and processor headroom and is also executing control logic, so a view with a large subscription set or a BQL query on a controller competes with the sequences it is displaying. A Supervisor is the usual host for anything estate-wide, because the aggregated data is already there and browsers hit one machine instead of every controller.
How does an operator actually reach a custom view?
Views are addressed by ORD, so a custom UX view can be listed in a nav file to appear in the tree, set as a user's default view, or embedded inside an existing Px page as one widget among stock ones. Embedding is usually the least disruptive, because operators keep the navigation they already know. On the version side, the module version in module.xml is what the software manager reports, so incrementing it on each deploy is what makes a redeploy visible and a rollback possible.
What does the customer have to provide or decide?
The Niagara version and the list of hosts, an account at the permission level the view will run under so it can be tested honestly, and the point list or naming convention the widgets bind to. The decisions that cannot be made for them are who the view is for, what it should do on a device with no network, and whether outside data is joined in the browser or brought into the station first. For tenant-facing or public work, someone also has to own the branding decisions and sign them off.
Can custom views and existing Px graphics coexist?
Yes, and mixing them is the normal case. Both are served by the same station under the same session and the same permissions, a Px page can host a custom widget, and a custom view can link back into Px pages by ORD. There is no requirement to convert an existing graphics package before adding one custom view.