Html client plugins can be used to implement features not supported by origam and to supplement origam modeling where necessary using a piece of custom react code.
There are two kinds of plugins so far, both of which can use javascript/react to create a custom user interface. The section level plugin has access to screen section data and can be used to display them. The screen level plugin can change data fed into section level plugins by modifying the screen parameters.
We will demonstrate how to get started with the plugins on Audit screen from the Audit package which you can find it in the standard origam distribution. The hello world style plugins we will create here won’t do much but you can find a more complex implementation of the Audit view using plugins in the demo project https://github.com/origam/origam-demo
.
Before you start creating a plugin make sure you have origam Root package version 5.1.8 or higher.
Plugin Placement
The plugins can be placed on any lazy loaded screen. To make a screen lazy loaded you have to specify a ListMethod, Method and ListSortSet in MenuItem.
Next you have to place the plugin (or a placeholder rather) on a screen. This is done in screen editor in Architect.
In the widget properties you have to specify the plugin’s SchemaItemName. Name the screen level plugin TestScreenPlugin and the section level plugin TestSectionPlugin. The section level plugin needs a DataMember so be sure to select one here. Also note that exactly one SectionLevelPlugin in the section must have the property AllowNavigation set to true. This is an equivalent of one screen section being a master and others details. So the AllowNavigation property would not be set to true if there were a master screen section here.
Custom Attributes
You can pass custom attributes to your plugin to make it configurable from the model (however we do not need the attributes in this demonstration). To do that you should create a new widget which will inherit from the widget you intended to use originally (ScreenLevelPlugin or SectionLevelPlugin). To create a new widget go to User Interface → Widgets → Basic Controls the right click the Basic Controls folder and choose New → Widget. This is what the TestScreenPlugin would look like if we wanted to create it as a separate widget.
When creating a new plugin widget be sure to set the ControlNamespace to Origam.Gui.Win
, ControlToolBoxVisibility to FormDesigner
and the ControlType to the ControlType of the plugin widget you are inhereting from.
And finally to define the attribute add a new property by right clicking on the Properties folder. You will see the property in the screen editor after you place the new plugin widget on it.
The property name and the value you specify in the form designer will be included among the xml attributes. You can read these attributes in the method initialize
in the react plugin implementation. Note that the component might be rendered before the initialize
method is run so you will probably have to store the attribute value in a state field like in the snippet below.
@observable
customAttribute = "";
initialize(xmlAttributes: { [key: string]: string }): void {
this.customAttribute = xmlAttributes["customAttribute"];
}
Running the Client
To create and debug a plugin you have to compile the html client application. First clone repository GitHub - origam/origam: Enterprise software prototyping and development platform. somewhere on your machine and install latest version of Node.js.
Then open command line in directory origam\frontend-html
and run:
npm install -g yarn
this will install the yarn package manager. Next run
yarn install
this will download and install all dependencies.
We will run the client application on a nodejs development server to make debugging simple. Before we do that we will also need an origam server to connect to and get the data from. You can use any running origam server you have access to: installed in IIS or in docker, local or remote. Just note the address and port the server is running on and write it to an environment variable WDS_PROXY_TARGET.
Now you can run the project with this command
yarn start
a development server should be started at https://localhost:3000/ when you go to that address you should see the client application.
Creating the Plugin
The client is written in typescript and react. You can use any javascript editor to edit the project like Visual Studio Code for example.
We will simplify our demonstration example by creating the plugins directly in the origam client source code. But in practise they should be placed in separate projects and published at npm repository as packages. More on this in later sections.
We will put the plugin files to origam\frontend-html\src\plugins\implementations
starting with TestScreenPlugin.tsx. You can copy the following code into it:
import {
ILocalization,
ILocalizer,
IScreenPluginData,
IPluginDataView,
IScreenPlugin
} from "@origam/plugin-interfaces";
export default class TestScreenPlugin implements IScreenPlugin {
$type_IScreenPlugin: 1 = 1;
id: string = "TestScreenPlugin";
requestSessionRefresh: (() => Promise<any>) | undefined;
setScreenParameters: ((parameters: { [p: string]: string }) => void) | undefined;
getComponent(data: IScreenPluginData, createLocalizer(localizations)): JSX.Element {
return <div>TestPlugin!</div>;
}
onSessionRefreshed() {
}
initialize(xmlAttributes: { [p: string]: string }): void {
}
}
As you can see the plugin implements interface IScreenPlugin
and all its fields and methods.
The field $type_IScreenPlugin
is not explicitly defined in the interface but it is needed to clearly show that the objects created from this class implement the interface.
requestSessionRefresh
is a method you can call to refresh the screen (data)
setScreenParameters
is a method you can use to set screen parameters
getComponent
will be called when rendering the component and what you return here will be used to replace the placeholder widget you added to the screen in the Architect. The input to the method contains data
which is model and database data you can use in your component. And the createLocalizer
function wihich will create a Localizer
more on that later.
onSessionRefreshed
is called after the user pushes the Refresh
button in the origam application. You can reload the data and update your components from here.
initialize
is called after the application starts and the xmlAttributes
parameter contains among other things the properties that you can model in architect and use them to configure the plugin.
Now we can create another file called TestSectionPlugin.tsx and put this into it:
import {
ILocalization,
ILocalizer,
ISectionPluginData,
IPluginDataView,
ISectionPlugin
} from "@origam/plugin-interfaces";
export default class TestSectionPlugin implements ISectionPlugin {
$type_ISectionPlugin: 1 = 1;
id: string = "TestSectionPlugin ";
getScreenParameters: (() => { [p: string]: string }) | undefined;
getComponent(data: ISectionPluginData, , createLocalizer: (localizations: ILocalization[]) => ILocalizer): JSX.Element {
return <div>{data.dataView.properties.map(prop => prop.id+", ")}</div>;
}
onSessionRefreshed() {
}
initialize(xmlAttributes: { [p: string]: string }): void {
}
}
The TestSectionPlugin
implements IsectionPlugin
interface. As you can see this plugin cannot refresh the screen or set screen parameters but it can get the parameters and their values.
Finaly we have to register the plugins in the origam\frontend-html\src\plugins\tools\PluginRegistration.ts
file so that the plugin classes can be recognized as plugins and found by the application:
export function registerPlugins(){
registerPlugin("TestScreenPlugin", () => new TestScreenPlugin());
registerPlugin("TestSectionPlugin", () => new TestSectionPlugin());
}
Now when we open audit view on any page:
We should see this:
Localization
to use localization features you have to create an instance of Localizer
class by calling the createLocalizer
function in the getComponenet
method. The localizations
array which is a parameter in the createLocalizer
should be placed in a separate file next to the plugin file. The localization file for the TestScreenPlugin would be named TestScreenPluginLocalization.ts. The file should contain a constant named localizations
in the following format:
export const localizations = [
{
locale: "de",
translations: {
day: "Tag",
}
},
{
locale: "cs-CZ",
translations: {
day: "Den",
}
},
{
locale: "en-US",
translations: {
day: "Day",
}
},
]
To localize a string inside your plugin call translate(key: string, parameters?: {[key: string]: any})
method of the localizer where the key
is the key from the localization file and parameters
is an object with named parameters. The localization strings are interpreted using library called messageformat. Look here to learn how you can parametrize your strings: Format Guide - messageformat a nice debug tool is also available here: Online ICU Message Editor
Plugin Deployment
In the simple demonstration we just completed we put all the plugin files into the client application source directly. This works as an example but the plugins should really be independent to avoid the need to maintain a fork of the client application and constantly merging changes from origam into it.
Publish Plugin as an npm package
A Good way of separating the plugin code from the client is to publish the plugin as an npm package and include it in the client application during a custom build.
Examples of such plugins can be found here:
There are a couple of origam npm packages you can use when developing a plugin:
@origam/plugin-interfaces
we have already referenced this one in the example above. The package is needed in every plugin because it contains the necessary interfaces.
@origam/js-styles
contains color definitions which you can reference in order to make your plugin compatible with the origam color style.
@origam/js-components
contains simple reusable react components which again will make your plugin look more like the origam application. Especially the SimpleDropdown
could be useful to you because the standard html input looks quite different from the origam dropdowns.
@origam/js-utils
contains various functions useful in origam and potentially in the plugins.
A tutorial on how to create and publish an npm package can be found here: The complete guide to publishing a React package to npm - LogRocket Blog. Note that after you create the plugin package you can locally link it to the origam frontend project with npm link
. This will make furhrer development of the package a lot easier. See details in the link above.
Alternative Approach to npm Packages
You may find that building the plugin package separately, publishing it on npm and including the package in the origam client creates some problems.
We had constant compatibility problems with one package in particular. The package build would work but when including it in the origam client build there were various build errors caused by different versions of the plugin package’s dependencies. Such problems can be solved by including the problematic dependency versions in the resolutions
and peerDependencies
sections of the package.json
. But the problems were just too anoying.
So we decicded to simply clone the plugin repository into origam\frontend-html\src\plugins\implementations
and register it as a git module. This way can both git repositories coexist and changes in either one are not confused with changes in the other one. The dependency problems during the build were also eliminated.
To registed the plugin repository as a git module create file origam/.gitmodules
and put this inside:
[submodule "frontend-html/src/plugins/implementations/XYPlugin"]
path = frontend-html/src/plugins/implementations/XYPlugin
url = https://github.com/XYOrganization/XYPlugin.git
You also have to include the plugin’s delendencies in the origam client’s package.json
and set the correct paths in origam\frontend-html\src\plugins\tools\PluginRegistration.ts
.
Notes
If you think the plugin you are making might be used inside of TabControl, make sure to give the plugin componet’s tag this css style: flex-grow:1;
If you don’t do that the plugin might not be hidden when it’s tab is deactivated leading to several tab contents to be rendered at the same time.