- Description
- Decentralised Logic
- Execution order of events
- How to use it
- How to write decentralised events
- How to configure project for decentralised events
- How to write centralised events
Description
The Build.One Client Logic API (CLAPI) allows to implement client-logic in javascript, which executes in the context of screens rendered by the Build.One runtime. It provides easy access to the logical structure of the screens as well as backend services. For detailed documentation of CLAPI, you can check the specific documentation (Link)
Most ui components allow users to run custom logic on certain events. (ex. on initialise, on value change, on before save, ...) These customisable events are available directly from the repository, as attributes. The attributes support simple JavaScript syntax:
EventAttribute: '$ app.domain.function(eventSource);'
The attributes require the value to start with '$', denoting that the used syntax will be JavaScript. (at the moment only JavaScript is supported) Most of the time, the events will require access to the context of the event. (the object that is triggering the event, the screen, ...) For that we introduced the following reserved keywords:
- eventSource - the object on which the event is triggered
- eventOrigin - the container from where the current screen was launched
Decentralised Logic
In the cases where centralised functions are used, the functions need to be available in the global context to be able to reference them. This means having to maintain that global context to avoid conflicts and having to reference the events by their full path. Moreover, this also means that all the client logic is loaded in the main bundle.
With the decentralised client logic, the goal was to address all the issues mentioned above to improve performance and allow users to focus on the client logic itself and not its infrastructure. With this approach, users are able to specify and reference custom client logic without including it in the global context and without preloading it. (it will be lazy loaded on screen launch)
A new attribute 'EventNamespace'
has been introduced for all repository objects.
When the attribute is assigned a namespace (ex. 'app.domain'), on screen launch, the corresponding file will be lazy loaded and injected into the specified namespace.
With this approach, the events will be specified exactly as above, there is no change in how the events are referenced, the main change is in how the logic is lazy loaded when required. This means that a basic implementation will remain the same:
EventNamespace: 'app.domain'
EventAttribute: '$ app.domain.function(eventSource);'
This approach still requires users to specify the correct namespace in the events themselves. We have simplified this with the introduction of a new reserved word 'eventNamespace', which will automatically map to the corresponding namespace:
EventNamespace: 'app.domain'
EventAttribute: '$ eventNamespace.function(eventSource);'
The last mentioned issue with the centralised logic was the polluted global scope. To keep the global context clean, the '#' reserved namespace was introduced. By using it, the namespace is loaded into the object at runtime, but is never exposed outside of it:
EventNamespace: '#'
EventAttribute: '$ #.function(eventSource);'
The code implementation for decentralized client logic is covered here: To maintain backwards compatibility with the centralised client logic and to avoid redundant import checks, the decentralised client logic handling is activated only when an event namespace is assigned to the object master. The event namespace needs to be assigned at object master level and, afterwards, it can be referenced within the master itself. (master-level, instance-level, menu-level).
Execution order of events
A usual screen contains various types of controls, each supporting a certain set of events that have different trigger conditions. The following is a table of commonly used events, along with short descriptions
Event | Description |
EventOnInitialize | Triggered when the element is loaded, with a delay of 10ms on objects of type Window and Form |
EventSyncOnInitialize | Triggered when the element is loaded, with no delay, currently only supported for type Window |
EventDataAvailable | Triggered when the SDO reads a record |
EventPreInitialize | Triggered before initialization of the element, on AkController (as opposed to element) level |
EventOnClose | Triggered when the object is closed |
EventOnStateChanged | Triggered whenever the object’s state is changed, with state data saved internally |
This means that, in a screen containing data, the correct order in which events get triggered is:
- sync
- preInit
- onInit
- dataAvailable.
Because of the small explicit delay of EventOnInitialize
, use EventSyncOnInitialize
when the event should be called synchronously.
How to use it
How to write decentralised events
This feature is integrated directly with the designer. Once an object is loaded, the corresponding client logic file can be generated automatically in the workspace by using the events ribbon button. The generated file is treated as any other TypeScript source, the exposed functions will be marked by exporting them.
Once the client logic is implemented, the EventNamespace attribute needs to be filled with either a valid namespace (ex. 'app.events') or with the reserved encapsulation namespace '#'. This is done so that the runtime knows that the object will lazy load its client logic.
- By using a valid namespace, the client logic will be made available in the global context.
- By using the encapsulation namespace, it will hide the logic from the global scope and only make it available to the object at runtime.
It is recommended to use the encapsulation parameter so that the global context is cleaner.
With the namespace set, it is now possible to reference the functions in any event inside the currently selected designer object (the object itself and its instances). If there are any menus used (ex. by ribbons, toolbars, ...), it will behave the same, the namespace references will map to the closes parent.
When using a proper namespace, the events can be referenced either through the global scop (ex. app.events.<objectname>.<function>
) or by using the reserved 'eventNamespace' keyword (ex. eventNamespace.<function>
).
When using the encapsulation namespace, the events can be referenced either by the eventNamespace
keyword or by the namespace itself (ex. #.<function>
).
How to configure project for decentralised events
Decentralised events will be independent of the main application bundle. Each repository object referencing events will have its own bundle, which will be lazy-loaded at runtime.
The following directory structure is recommended:
webui
|-- client-logic
|-- _export
|-- <repository_object>.ts
|-- ...
|-- EventNamespaceDomain.ts
|-- ...
|-- ...
|-- index.ts
The EventNamespaceDomain.ts
file is used by webpack to "know" that the files in the _export
directory should be bundled in separate bundles. And it will be used to register the namespace domain at runtime in index.ts. Afterwards, any object added to the _export directory will be available for lazy-load at runtime.
import EventNamespaceDomain from './client-logic/EventNamespaceDomain';
const eventNamespace = new EventNamespaceManager();
eventNamespace.registerDomain('APP', new EventNamespaceDomain());
export default {};
export default class EventNamespaceDomain {
async LoadEventNamespace(relativeFilePath: string) {
try {
const object = await import(
/* webpackChunkName: "[request]" */
`./_export/${relativeFilePath}`
);
return object;
} catch {
return null;
}
}
}
How to write centralised events
Centralised events require the actual implementation to be part of the main bundle loaded on startup so that the functions are available in the global context.
With this approach, there are no constraints in terms of how the files are structured. It is important that the functions are exported as part of the main index.ts.
export default function helloWorld() {
console.log('Hello world!');
}
import helloWorld from 'client-logic/helloWorld';
export default { helloWorld };
As part of the webui build, webpack will create a bundle which will expose index.ts as a preconfigured namespace. (ex. 'app')
In the designer, when calling into the function, it will be done relative to the configured namespace:
EventAttribute: '$ app.helloWorld();'
Back to Documentation
Back to Home Page