UI Changelog

This file will document notable API changes specific to the Niagara UI. Hard breaking changes will always be documented on their own, but other notable non-breaking changes are worthy of their own description.

Niagara 4.14u1

jQuery Core update

jQuery Core has been updated to version 3.7.1. For more details, visit https://blog.jquery.com/2023/08/28/jquery-3-7-1-released-reliable-table-row-dimensions/.

Further changes to Workbench Mouse Handling

Your BWbShell subclass will no longer receive every mouse event. This led to issues when a BWbShell already had a mousePressed override, but did not call event.consume() on the input event. These mouse events would be double-fired, which inadvertently caused a breaking change.

If you wish to support navigation by mouse events, you can use the (new, currently private) API IMouseNavigableShell in the bajaui module.

Niagara 4.14

Changes to Abstract Manager Framework

#makeCommands() moved from DriverMgr to base Manager and made asynchronous

DriverMgr had a method, makeCommands(), that allowed subclasses to declare what Commands they would make available and where they would appear (action bar, toolbar, etc.). This was a separate method so that DriverMgr could have finer control over the ordering of commands.

As of Niagara 4.14, this functionality has been moved to the base Manager class. It has also been changed from synchronous to asynchronous.

If you were previously overriding DriverMgr#makeCommands(), your override will need to change as shown:

// previously:

makeCommands() {
  return [ ...super.makeCommands(), new MyMgrCommand() ];
}

//  now:

makeCommands() {
  return Promise.resolve(super.makeCommands())
    .then((commands) => [ ...commands, new MyMgrCommand() ]);
}

Also of note: flags have been added to nmodule/webEditors/rc/wb/mgr/commands/MgrCommand. You can now pass these flags to bajaux/commands/Command constructors to control in which bars and context menus your commands will appear. This behavior may be familiar if you have previously used the MgrCommand class to make Java-based Managers, although the flag names have been edited for clarity. Please see the bajaux and MgrCommand documentation pages for information on how to configure Command flags.

MgrCommand methods isShowInActionBar and setShowInActionBar, as well as the use of MgrCommand as a mixin, are deprecated. Please use MgrCommand.flags instead.

For even finer control of right-click context menus in Managers, getMainTableCommands() has been added to Manager, and getLearnTableCommands() has been added to MgrLearn. If you were previously using the private updateMenuCommandGroup function to add commands to right-click menus, you can remove it in favor of these new functions.

Default string formatting for PropertyColumn and PropertyMgrColumn

Previously, you had to specify a context data attribute for PropertyColumn and PropertyMgrColumn to use customized formatting when displaying cell values. Now, the default behavior is to use the slot facets of the Property these columns are looking at. This also allows each row in the table to be formatted according to the specific Complex instance loaded in that row.

If you are using the context data attribute, it will still be used regardless of slot facets. If you need to fall back to synchronous, unformatted toStrings, you can set context to null.

Manager commands using BatchComponentEditor will now call save() on field editors.

Previously in a Manager, save was not called for its common BatchComponentEditor commands like New, Add, Edit, and Match. Starting in Niagara 4.14, Manager based Dialogs started calling save and this includes calling saveToComplex on any BaseEditor subclasses. Note that the New and Add Commands will call this method with an unmounted Complex. You can detect this scenario by checking if this.getComplex().isMounted() is false. Any behavior in saveToComplex that is needed for a mounted Complex should be replicated in your subclass of MgrModel#addInstances.

Changes to spandrel.jsx

Asynchronous content creation

The child elements of JSX nodes can now be asynchronously declared.

Previously, you would have had to generate the content first, and then resolve the JSX. In this example, remember that baja.Boolean#toString, when passed a context object, may perform a Lexicon lookup and will therefore return a Promise:

const BooleanDisplay = spandrel((boolean) => {
  const promise = boolean.toString({ trueText: 'yes', falseText: 'no' });
  return promise
    .then((string) => <span>{ string }</span>);
});

Now, child elements of JSX nodes are asynchronously resolved. You can now simply set the contents of an element, or an element itself, to a Promise that resolves to the desired content. This results in less typing:

const BooleanDisplay = spandrel((boolean) => {
  return <span>{ boolean.toString({ trueText: 'yes', falseText: 'no' }) }</span>;
});

Please note that the asynchronous resolution applies to the entire element. Individual element attributes are not asynchronously resolved.

This also means that spandrel.jsx itself no longer synchronously returns spandrel data. It now returns a Thenable that resolves to the actual spandrel data.

Bug Fixes / Behavior Changes

Changes to WebProperty

Legacy encoding deprecated

javax.baja.ui.util.WebProperty#getPropertyChanges accepts a boolean flag indicating whether to encode the WebProperties to send to a legacy client running 4.3 or earlier. Since 4.3 is no longer a supported release, this method is now deprecated. Any usages should be changed to use the getPropertyChanges(BWidget) signature, which will include both property values and metadata in the response.

The sync() method that takes a boolean flag has also been deprecated. These usages should be changed to the sync(BWidget, Iterable<WebProperty>) signature.

BajaScript Simples directly supported

It is no longer necessary to define Niagara Simple properties on bajaux Widgets by setting the value to the string encoding and manually declaring the typeSpec. You can simply set the value to an instance of a BajaScript baja.Simple subclass. Workbench, DashboardPane, web profiles, and other widget containers have been updated to correctly infer the encoding and type spec directly from the value.

Changes to feDialogs

For more details, please see the documentation for feDialogs under the webEditors section of Open Web Technologies in docDeveloper.

props() function added

feDialogs.props() can prompt the user for multiple named values at once without requiring the creation of an intermediate Component to hold the values as Properties.

Inline validation

feDialogs.showFor() and feDialogs.props() both now support a validate function parameter to allow declaring what values you will accept from the user.

Changes to Workbench Mouse Handling*

If you have a BWbShell subclass for a custom Workbench profile, it will now receive every mouse event. This will cause mousePressed, mouseReleased, etc. to be called on your BWbShell.

If you override one of these methods and consume the BMouseEvent it receives by calling event.consume(), that event will no longer be passed to its target BWidget.

Workbench itself uses this. If your mouse has back/forward buttons on it, Workbench will now capture those button presses and navigate as if you had clicked the Back/Forward buttons directly. (If you wish to disable this behavior, you can set the bajaui.captureMouseNavigationEvents system property to false and those mouse events will be dispatched to the content widget as before.)

Back/Forward buttons on mice typically map to buttons 4 and 5. The respective methods have been added to BMouseEvent. getModifiersEx(), if called, will return the same values as always.

*To solve problems with double-firing mouse events, this approach has been changed in 4.14u1. Please see the additional note there.

LocalizableError introduced

LocalizableError provides an easy way for your JavaScript code to throw an error intended to be displayed to the user.

Previously, to inform the user that an operation failed, while actually throwing an error or rejecting a Promise, you would have to manually show a formatted dialog, and throw/reject separately. Now, you can simply throw or reject with a LocalizableError and the framework will handle the details of informing the user.

For more information, see the LocalizableError JSDoc.

Niagara 4.13

instantiated hook parameter respected by WidgetManager

Previously, only a global instantiated hook, installed by calling installHooks(), would be respected when using a WidgetManager (such as with fe.buildFor). Now, a passed hooks.instantiated parameter will also be respected.

BajaScript RPC calls returning Complex now BSON-marshal them

When calling an RPC via BOX (e.g. using baja.rpc()), if the response was a BComplex, the client would just receive a toString of it, which was not useful. Now, the response will be a BSON-encoded representation of that BComplex, which can be decoded in the client using baja.bson.decodeAsync().

spandrel bugs fixed

Widget stricter about initialization complete

Previously, Widget#load would still resolve as long as initialize() had been called, even if it hadn’t completed. This means that code that looks like this:

widget.initialize(dom);
return widget.load('my value'); // initialization isn't finished yet!

may have succeeded sometimes, or failed sometimes. Now, load() will reject if initialize() has not yet completed. If you experience new failures, be sure that initialization is complete before loading a new value:

return widget.initialize(dom)
  .then(() => widget.load('my value'));

In addition, isInitialized() will now return false until the initialize() promise has resolved. In other words, the previous behavior was:

widget.initialize(dom);
widget.isInitialized(); // true

The new behavior is:

const promise = widget.initialize(dom);
widget.isInitialized(); // false
return promise.then(() => {
  widget.isInitialized(); // true
});

Because the doChanged() callback (called when a Widget Property changes) does not run until isInitialized() returns true, this means that doChanged() will also not be called until the initialize() promise fully resolves. An example of the old behavior is:

widget.doChanged = (name, value) => console.log(name + ' is now ' + value);
widget.initialize(dom);
widget.properties().add('a', 'b'); // this fires doChanged and logs to the console

The new behavior requires initialize() to finish:

widget.doChanged = (name, value) => console.log(name + ' is now ' + value);

const promise = widget.initialize(dom);
widget.properties.add('a', 'b'); // does _not_ fire doChanged or log to the console

return promise.then(() => {
  widget.properties().add('c', 'd'); // _now_ doChanged will get fired
});

BIWebResource introduced

BIWebResource solves two problems.

Redundant CSS declarations

JavaScript resources, such as bajaux field editors, often depend on CSS rules to work properly. They can declare their own dependencies on CSS files using the css! RequireJs plugin, but it’s easy to forget to do this. It’s also redundant - usually a Niagara module will only have one CSS file, and manually adding css!nmodule/myModule/rc/myModule as a dependency to every field editor in a module is a chore.

BCssResource, an implementation of BIWebResource, solves this. It’s now possible to declare a BJsBuild (which all your field editors must reference anyway) as having a dependency on a CSS file. This way, the framework will ensure that the proper CSS files are loaded whenever a BIJavaScript that uses that BJsBuild is instantiated in the browser.

Interdependencies between builtfiles

When resolving dependencies of BIJavaScript objects (like bajaux field editors or BajaScript type extensions), we previously resolved them as a flat list of dependencies.

const deps = [ 'bajaux/rc/bajaux.built.min', 'nmodule/webEditors/rc/webEditors.built.min' ];
const requireJsId = 'nmodule/webEditors/rc/fe/baja/StringEditor';

require(deps, function () {
  require([ requireJsId ], function (resource) {
    doSomethingWith(resource);
  });
});

But there is a problem: in this case, webEditors has its own dependency on bajaux, but the two files get downloaded in parallel. It’s a race against time: if the bajaux builtfile isn’t fully downloaded by the time webEditors is downloaded, then webEditors will start pulling its bajaux dependencies too soon. As a result, say, Widget.js might get downloaded, even though it’s totally unnecessary - Widget is defined in bajaux.built.min, it just hasn’t been downloaded yet. We need a way of ensuring that JavaScript builtfiles are downloaded in the correct order to prevent unnecessary network traffic.

BIWebResource solves this problem as well. It solves a graph of dependencies (BJsBuilds and BCssResources) and arranges them into an array of arrays. The dependencies in each array can be require()d concurrently, but each array’s worth must be fully resolved before moving onto the next. This ensures that all dependencies are correctly satisfied.

In practice, this will likely not affect your day-to-day development - all the relevant places in the Niagara Framework have been updated to correctly resolve these dependencies. But if you have any places where you are manually resolving BJsBuild dependencies, BIWebResource#resolve will solve the graph, and toJSON will provide you with a JSON representation of the solved dependencies as RequireJS IDs.

As a minor side effect of this change, webdev behavior for bajaScript has also changed. Previously, turning on webdev for bajaScript would also disable the use of builtfiles for all BIBajaScriptTypeExts. Now, the webdev settings for all type extensions will be respected, regardless of bajaScript’s webdev setting.

In a unit test context, your Type Extensions may now try to load builtfiles they weren’t loading before. If this occurs, simply add a line to your srcTest/rc/browserMain.js, before running the actual specs, that tells RequireJS to skip loading that builtfile:

define('nmodule/myModule/rc/myModult.built.min');

Breaking change

If you have your own implementation of BIBajaScriptTypeExt that overrides encodeToJson(), it must be updated to encode an array-of-arrays instead of a flat array, as before. Please see the breaking change notification in the 4.13 documentation.

Niagara 4.12

Javascript playground examples have been updated

The JavaScript Playground examples in the docDeveloper module have been brought up to date. Additionally, spandrel examples have been added.

Bajaux Command change to getDisplayNameFormat() and getDescriptionFormat()

Starting in Niagara 4.12, Command.getDisplayNameFormat() and Command.getDescriptionFormat() will no longer return the resolved lexicon value when the lex parameter is provided in the constructor. They will instead return the BFormat lexicon entry used to resolve that value. This should make it easier to identify a Command by calling its getDisplayFormat() method. You can still obtain the resolved lexicon values by calling Command.toDisplayName() or Command.toDescription().

var command = new Command({
  module: "bajaux", 
  lex: "commands.undo"
});

console.log(command.getDisplayNameFormat()); //"%lexicon(bajaux:commands.undo.displayName)%"
command.toDisplayName().then((result) => console.log(result)); //"Undo"

console.log(command.getDescriptionFormat()); //"%lexicon(bajaux:commands.undo.description)%"
command.toDescription().then((result) => console.log(result)); //"undo the last command invocation"

New Table events

Starting in Niagara 4.12, nmodule/webEditors/rc/wb/table/Table will trigger new events in response to user interaction.

const CELL_ACTIVATED_EVENT = Table.CELL_ACTIVATED_EVENT;
const ROW_SELECTION_CHANGED_EVENT = Table.ROW_SELECTION_CHANGED_EVENT;

// given a DOM element in which a Table has been initialized:
dom.on(CELL_ACTIVATED_EVENT, function (e, table, row, column) {
  const selectedRowValue = row.getSubject();
  const selectedCellValue = column.getValueFor(row);
});

dom.on(ROW_SELECTION_CHANGED_EVENT, function (e, table) {
  const newSelectedRows = table.getSelectedRows();
});

New spandrel features for 4.12

The following spandrel features have been added for 4.12:

Please refer to the spandrel documentation and examples provided in the javascript playground which discuss the above spandrel features in further detail.

spandrel bugs fixed

baja.AbsTime.DEFAULT UTC offset changed

The default UTC offset for baja.AbsTime.DEFAULT is now the UTC timezone.

Changes to dialogs.js params

A text parameter can now be passed in to dialogs.js similar to how content was in the past. The text passed in will be escaped whereas the content passed in will not. text and content cannot both be specified and doing so will result in an error.

Example with content:

dialogs.showOk({ 
  content: '<script> alert("uh oh"); </script>' // This will not be escaped and will open an alert dialog
});

Example with text:

dialogs.showOk({ 
  text: '<script> alert("uh oh"); </script>' // This will be escaped and will not open an alert dialog
});

Global Promise disabled in eslint

‘Promise’ must now be imported explicitly in RequireJS in order for ESLint to pass. This ensures that the correct ‘Promise’ object is being used whether it be from bluebird, native promises, etc.

Example:

define([ 'Promise' ], function (Promise) {
    'use strict';
    
    function doSomethingWithAPromise() {
        return Promise.resolve();
    };
});

Command error handling

Commands should now either show their own error or reject. If a command does both, it will result in there being more than one error shown.

Additionally, Command#defaultNotifyUser may be overridden to specify a custom way to show a command’s error.

Niagara 4.11

BajaScript

baja.Ord.make()

child is now an optional argument - if base is present but child is omitted, base will be used.

// previously, if child might sometimes not be present:
if (child) {
  return baja.Ord.make({ base, child });
} else {
  return baja.Ord.make(base);
}

// now:
return baja.Ord.make({ base, child }); // always works

jQuery Core update

jQuery Core has been updated to version 3.5.1 and comes with a breaking change to make the HTML construction safer. For more details, visit https://jquery.com/upgrade-guide/3.5/.

To avoid significant efforts to replace code, a shim has been added to restore the legacy behavior. This shim is turned on by default but there is a system property to turn it off.

// When set to true, jQuery will perform strict pre-filtering of HTML.
// Apply caution, since turning this to true may also break some of your existing UX views.
niagara.requirejs.useJQueryStrictHtmlPrefilter=true

Unit Testing - jQuery module reference updated

The introduction of this shim also calls for some changes to your Niagara modules. Source files are largely unaffected at this moment, however, the unit test package in your module(s) may need an update to the browserMain.js file. An updated browserMain.js file with the shim looks like this now.

require.config({
  baseUrl: '/base',
  paths: {
    //... paths
    jquery: '/module/js/rc/jquery/jquery.min',
    // ...more paths
  },
  map: {
    '*': {
      'jquery': 'nmodule/js/rc/shims/jquery/jquery'
    },
    'nmodule/js/rc/shims/jquery/jquery': {
      'jquery': 'jquery'
    }
  }
});

Undoable Commands

There is now support for making commands undoable. This enables the user to undo / redo actions for a given command.

These commands will be undoable in the HTML5HxProfile on the web only and not in other hx profiles or workbench. The undoable history will only last during the current session, so the user will not be able to undo / redo invoked commands after browser refresh.

Commands can add this functionality by overriding the undoable method in the child command or passing an undoable to the base Command class. This undoable will specify:

An undoable implementation could look something like this:

new Command({
  undoable: {
    redo: () => console.log('implement redo logic here'),
    undo: () => console.log('implement undo logic here'),
    redoText: () => console.log('get the text displayed for a redo'),
    undoText: () => console.log('get the text displayed for a undo'),
    canRedo: () => console.log('determine if the redo can happen'),
    canUndo: () => console.log('determine if the undo can happen')
  } 
});

For more details, see the documentation for the Command class.

BIRequireJsConfig now respects webdev settings for its containing module

Previously, the JavaScript written to implement a BIRequireJsConfig would receive a webdev parameter that was set based on whether webdev was turned on for the js module. Now, that webdev parameter is set based on whether webdev is turned on for the buildId of the BIRequireJsConfig itself. (If your BIRequireJsConfig does not specify a buildId, then that webdev parameter will fall back to the js module as it was before.)

Please see the help section “Building JavaScript Applications” in Doc Developer for more info about webdev and build IDs.

Niagara 4.10u10

jQuery Core update

jQuery Core has been updated to version 3.7.1 and comes with a breaking change to make the HTML construction safer. For more details, visit https://jquery.com/upgrade-guide/3.5/.

To avoid significant efforts to replace code, a shim has been added to restore the legacy behavior. This shim is turned on by default but there is a system property to turn it off.

// When set to true, jQuery will perform strict pre-filtering of HTML.
// Apply caution, since turning this to true may also break some of your existing UX views.
niagara.requirejs.useJQueryStrictHtmlPrefilter=true

For more details on the upgrade to jQuery 3.7.1, visit https://blog.jquery.com/2023/08/28/jquery-3-7-1-released-reliable-table-row-dimensions/.

Unit Testing - jQuery module reference updated

The introduction of this shim also calls for some changes to your Niagara modules. Source files are largely unaffected at this moment, however, the unit test package in your module(s) may need an update to the browserMain.js file. An updated browserMain.js file with the shim looks like this now.

require.config({
  baseUrl: '/base',
  paths: {
    //... paths
    jquery: '/module/js/rc/jquery/jquery.min',
    // ...more paths
  },
  map: {
    '*': {
      'jquery': 'nmodule/js/rc/shims/jquery/jquery'
    },
    'nmodule/js/rc/shims/jquery/jquery': {
      'jquery': 'jquery'
    }
  }
});

Niagara 4.10u1

Moment / Pikaday changes

Moment now has a dependency on bajaScript. This provides moment with certain locale information from the user’s language settings. To avoid the bajaScript dependency, moment can be required directly using the full module path: require nmodule/js/rc/moment/moment.min.

Due to this change, pikaday must be required after moment to ensure it is able to use moment for formatting. Code similar to:

define([ 'moment', 'pikaday' ], function () {
    // Make use of pikaday / moment
});

would need to transition to:

define([ 'moment' ], function () {
    require([ 'pikaday' ], function () {
        // Make use of pikaday / moment
    });
});

This will ensure that pikaday can detect and make use of moment.

If your unit tests incorporate tests for different languages like German ‘de’, you may want to update your browserMain.js with the new location of the localized moment ‘/module/js/rc/shims/moment/moment’ so that any calls to moment.startOf(‘week’) return the correct day of the week based on the current locale.

Niagara 4.10

BajaScript

Implicit batching introduced

When BajaScript performs multiple network calls in quick succession, those network calls will automatically batch into a single WebSocket message. See the documentation for baja.comm.Batch for full details.

Formatting support added to Complex#getDisplay

A context object can now be passed as the second argument to getDisplay(). The context will be used to format the display string, if possible. Only Simple types that have Type Extensions implemented will perform formatting; other types will use the unformatted display string sent from the server, as before. When a context is passed, getDisplay() will return a Promise.

The main use case for this change is baja.Format, which will now do a better job of respecting slot facets during formatting - most commonly for animating Widget properties in Px pages.

bajaux

spandrel introduced

spandrel is a new API introduced to ease the process of constructing Widgets that contain nested child Widgets. It also allows the construction of bajaux widgets using JSX.

It does not replace bajaux - it is built on top of it. There is no requirement that you use it, but we think you may find it useful. Check the bajaux documentation for full details, including a tutorial.

Widget constructor receives an object literal

The Widget constructor may now receive an object literal instead of three strings. The object literal specifies the Widget’s starting configuration. The old-style constructor is still supported by the framework for now, but it is strongly recommended that you switch to the new style. Please see the bajaux documentation for full details.

// old style.
// using this style, the initial set of properties passed to the constructor
// may not be respected because the add() call would overwrite them.
class MyWidget {
  constructor() {
    super(...arguments);
    this.properties().add('myProp', 'defaultValue');
  }
}
const w = new MyWidget({ properties: { myProp: 'myPropValue' } });
w.properties().getValue('myProp'); // defaultValue

// new style.
// using this style, the initial set of properties passed to the constructor
// will be respected, while still allowing for defaults.
class MyWidget {
  constructor(params) {
    super({
      params,
      defaults: { properties: { myProp: 'defaultValue' } }
    });
  }
}
const w = new MyWidget({ properties: { myProp: 'myPropValue' } });
w.properties().getValue('myProp'); // myPropValue

Widget#resolve receives ViewQueries

Previously, view: query parameters were stripped off before being passed to Widget#resolve. They are now included.

uxBuilder

UxMedia introduced

UxMedia is a new method of rendering Px graphics purely in the browser. See this overview of how it works.

webEditors

fe.buildFor() no longer accepts value as a Promise

If your value is a Promise, it must be resolved first.

// no longer works:
return fe.buildFor({ value: getValueAsync() });

// must now be:
return getValueAsync()
  .then((value) => fe.buildFor({ value }));

fe.buildFor() no longer rejects if a Widget already exists

If a Widget already exists in the DOM element you pass to fe.buildFor(), it will no longer reject - it will simply destroy the existing Widget before building a new one in its place.