Kogito Tooling Examples — How to integrate a custom Editor, an existing Editors, and custom Views…

Photograph by Cristina Mittermeier

This is part of a series of blog posts, and this one covers “How to integrate a custom Editor, an existing Editors, and custom Views on a Web App”.

You can navigate through the series by clicking on the following topics:

In this section, we will create a Web App where we put together our custom Base64Png Editor, the Kogito DMN Editor, the Kogito BPMN Editor, our ‘To-do’ List View, and our Ping Pong View.

Starting

Each example of this section will have 3 important files:

  • envelope/<respective Envelope>.html;
  • src/envelope/<respective Envelope initialization>.ts;
  • src/Pages/<respective Editor or View dir>;

The only exception to this file organization is the Kogito BPMN Editor, which uses an Envelope already deployed in GitHub Pages.

Envelope HTML

The HTML Envelope file is a boilerplate. Here we have an example of the Base64Png Editor:

The only difference between the HTML Envelope files across the examples covered in this section is the src property from the script tag. In the above example, the src is base64-editor.js, which is the Envelope initialization path. The file organization on the dist folder is done by your bundler, which will not be covered here. You can check all the code of the Web App here.

Envelope Initialization

The initialization of the Envelope is pretty similar between the examples as well. Here follows the initialization method of the Base64Png Editor:

import { init } from "@kogito-tooling/editor/dist/envelope";
import { CompositeEditorFactory } from "@kogito-tooling/editor/dist/envelope";
import { EnvelopeBusMessage } from "@kogito-tooling/envelope-bus/dist/api";
import { ChannelType, getOperatingSystem } from "@kogito-tooling/channel-common-api";
import { Base64PngEditorFactory } from "base64png-editor";
init({
container: document.getElementById("envelope-app")!,
bus: {
postMessage<D, Type>(message: EnvelopeBusMessage<D, Type>, targetOrigin?: string, _?: any) {
window.parent.postMessage(message, "*", _);
}
},
editorFactory: new CompositeEditorFactory([new Base64PngEditorFactory()]),
editorContext: { channel: ChannelType.EMBEDDED, operatingSystem: getOperatingSystem() }
});
view raw base64-editor.ts hosted with ❤ by GitHub

Kogito Tooling makes the Editor init method available on the @kogito-tooling/editor/dist/envelope package. We’re going to inform the Envelope container (as it is on the HTML Envelope file). The bus which is the method utilized to communicate from the Envelope to the Channel. A factory to create the Editor. Last but not least, the Editor context, providing some useful information to the Envelope.

The only difference between this initialization file and the DMN Envelope initialization is the factory.

import { ChannelType, getOperatingSystem } from "@kogito-tooling/channel-common-api";
import { init } from "@kogito-tooling/editor/dist/envelope";
import { CompositeEditorFactory } from "@kogito-tooling/editor/dist/envelope";
import { EnvelopeBusMessage } from "@kogito-tooling/envelope-bus/dist/api";
import { GwtEditorWrapperFactory } from "@kogito-tooling/kie-bc-editors";
init({
container: document.getElementById("envelope-app")!,
bus: {
postMessage<D, Type>(message: EnvelopeBusMessage<D, Type>, targetOrigin?: string, _?: any) {
window.parent.postMessage(message, "*", _);
}
},
editorFactory: new CompositeEditorFactory([new GwtEditorWrapperFactory()]),
editorContext: { channel: ChannelType.EMBEDDED, operatingSystem: getOperatingSystem() }
});

We call the DMN Factory a GwtEditorWrapperFactory because the Editor was written in Java and compiled to Javascript using GWT.

The Views initialization is a bit simpler. Here is the ‘To-do’ List View initialization:

import { init } from "todo-list-view/dist/envelope";
import { EnvelopeBusMessage } from "@kogito-tooling/envelope-bus/dist/api";
init({
container: document.getElementById("envelope-app")!,
bus: {
postMessage<D, Type>(message: EnvelopeBusMessage<D, Type>, targetOrigin?: string, transfer?: any) {
window.parent.postMessage(message, "*", transfer);
},
},
});
view raw todo-list-view.ts hosted with ❤ by GitHub

Differently from the Base64Png Editor, here we use the ‘To-do’ List View initialization method. Again, the container is the same on the Envelope HTML file, and we pass the bus method to tell the Envelope how to communicate with the Channel. This View doesn’t have a custom factory, so it’s unnecessary to pass any other parameter.

Our Ping Pong View has a custom factory, so the difference between it and the ‘To-do’ List View is a new property called pingPongFactory.

import { EnvelopeBusMessage } from "@kogito-tooling/envelope-bus/dist/api";
import { init } from "ping-pong-view/dist/envelope";
import { PingPongReactImplFactory } from "ping-pong-view-react";
init({
container: document.getElementById("envelope-app")!,
bus: {
postMessage<D, Type>(message: EnvelopeBusMessage<D, Type>, targetOrigin?: string, transfer?: any) {
window.parent.postMessage(message, "*", transfer);
},
},
pingPongViewFactory: new PingPongReactImplFactory(),
});

And of course, the init method is now from the Ping Pong View.

Embedding an Editor

In this section, we will show how to embed our custom Editors and the Kogito BPMN/DMN Editors on our Web App.

Base64Png Editor

We’re going to create a Base64PngPage React component responsible for rendering our component and a custom gallery, where you’ll be able to choose a file to be opened.

Starting, we need to create a file state, which will store the current file. It’s essential to take notice that we’re using the File definition from Kogito Tooling. We start the state with an empty file and the correct file extension.

import { File } from "@kogito-tooling/editor/dist/embedded";
export function Base64PngPage() {
const [file, setFile] = useState<File>({
fileName: "new-file",
fileExtension: "base64png",
getFileContents: () => Promise.resolve(""),
isReadOnly: false,
});
// …
}
view raw Base64PngPage.tsx hosted with ❤ by GitHub

Now, we create a memoized value with an EditorEnvelopeLocator object. This object specifies who is going to send messages to this Envelope (targetOrigin). Also, it maps a file extension (base64png) to an Envelope location (the Base64Png Envelope). In this case, we’re using a local Envelope, and its location is determined by your bundler. Still, further in the BPMN Editor example, we’re going to see how to embed already deployed Envelopes.

import { EditorEnvelopeLocator } from "@kogito-tooling/editor/dist/api";
export function Base64PngPage() {
// …
const editorEnvelopeLocator: EditorEnvelopeLocator = useMemo(() => {
return {
targetOrigin: window.location.origin,
mapping: new Map([
[
"base64png",
{
resourcesPathPrefix: `envelope/`,
envelopePath: `envelope/base64-editor.html`,
},
],
]),
};
}, []);
// …
}
view raw Base64PngPage.tsx hosted with ❤ by GitHub

Finishing, we utilize the Kogito Tooling EmbeddedEditor component to render our custom Editor. Simple like that, we have our Editor up running!

import { EditorEnvelopeLocator } from "@kogito-tooling/editor/dist/embedded";
export function Base64PngPage() {
// …
return (
<Page>
<div className={"webapp–page-main-div"}>
<Base64PngGallery setFile={setFile} />
<EmbeddedEditor
file={file}
editorEnvelopeLocator={editorEnvelopeLocator}
channelType={ChannelType.EMBEDDED}
locale={"en"}
/>
</div>
</Page>
);
}
view raw Base64PngPage.tsx hosted with ❤ by GitHub

The Base64PngGallery is a custom component made for this example, and it’s located on the Channel. Its function is to set a base64png file on the file state. So we can see an interaction between the Channel and the Envelope. You can check a running example here!

Kogito DMN Editor

Like in the example Base64PngPage, we’re going to create a DmnPage React component. Also, we’ll be creating a file state, used to store the current file.

import { File } from "@kogito-tooling/editor/dist/embedded";
export function DmnPage() {
const [file, setFile] = useState<File>({
fileName: "new-file",
fileExtension: "dmn",
getFileContents: () => Promise.resolve(""),
isReadOnly: false,
});
}
view raw DmnPage.tsx hosted with ❤ by GitHub

Again, we’ll be setting up the editorEnvelopeLocator with a memoized value. Both Editors (DMN/BPMN) are available on the @kogito-tooling/kie-bc-editors-unpacked package, and here the DMN Editor was copied from node_modules/@kogito-tooling/kie-bc-editors-unpacked/dmn to the dist/gwt-editors/dmn.

import { EditorEnvelopeLocator } from "@kogito-tooling/editor/dist/api";
export function DmnPage() {
// …
const editorEnvelopeLocator: EditorEnvelopeLocator = useMemo(() => {
return {
targetOrigin: window.location.origin,
mapping: new Map([
[
"dmn",
{
resourcesPathPrefix: "../gwt-editors/dmn/",
envelopePath: "envelope/gwt-editors.html",
},
],
]),
};
}, []);
// …
}
view raw DmnPage.tsx hosted with ❤ by GitHub

To have imperative access to the EmbeddedEditor, it’s necessary to use the useEditorRef. It’ll provide an object with the reference to be used on the EmbeddedEditor component (ref) and a way to access its methods (editor). This Hook creates a reference callback, so it’s possible to use its methods as dependencies for other React hooks, such as the useDirtyState on the Sidebar component, which informs if the Editor is dirty or not.

import { EmbeddedEditor, useEditorRef } from "@kogito-tooling/editor/dist/embedded";
export function DmnPage() {
//
const { editor, ref } = useEditorRef();
return (
<Page>
<div className={"webapp–page-main-div"}>
<Sidebar ... />
<EmbeddedEditor
ref={ref}
file={file}
editorEnvelopeLocator={editorEnvelopeLocator}
channelType={ChannelType.EMBEDDED}
locale={"en"}
/>
</div>
</Page>
);
}
view raw DmnPage.tsx hosted with ❤ by GitHub

The Sidebar component is used to handle new files, opening samples or files, etc. Check up the example here!

Kogito BPMN Editor

Here we’re going to use another approach to set up the EditorEnvelopeLocator. We already have a BPMN Envelope deployed for our Business Modeler Preview, and you’re going to use it. So we simply pass the URL of the Envelope and its resources, and voilá. It’s not necessary to copy any file using your bundler.

const editorEnvelopeLocator: EditorEnvelopeLocator = useMemo(() => {
return {
targetOrigin: window.location.origin,
mapping: new Map([
[
"bpmn",
{
resourcesPathPrefix: "https://kiegroup.github.io/kogito-online/editors/0.7.0/bpmn",
envelopePath: "https://kiegroup.github.io/kogito-online/editors/0.7.0/envelope",
},
],
[
"bpmn2",
{
resourcesPathPrefix: "https://kiegroup.github.io/kogito-online/editors/0.7.0/bpmn2",
envelopePath: "https://kiegroup.github.io/kogito-online/editors/0.7.0/envelope",
},
],
]),
};
}, []);
view raw BpmnPage.tsx hosted with ❤ by GitHub

The editorEnvelopeLocator is the only difference between the DMN example.

And here is the running example of the BPMN Editor.

Embedding a View

In this section, we’ll show how to embed a View on your Web App.

‘To-do’ List View

On the ‘To-do’ List View section, we created an EmbeddedTodoList component, and we’re going to use this component here. We simply passed the ‘To-do’ List Envelope location and implemented a todoList_ItemRemoved method. The ActionSidebar is utilized to add new items to our todo list, showing the interaction between our Channel and Envelope.

export function TodoListViewPage() {
const todoListViewRef = useRef<TodoListApi>(null);
return (
<Page>
<div className={"webapp–page-main-div"}>
<ActionsSidebar todoListViewRef={todoListViewRef} />
<PageSection>
<EmbeddedTodoList
ref={todoListViewRef}
targetOrigin={window.location.origin}
envelopePath={"envelope/todo-list-view.html"}
todoList__itemRemoved={(item) => window.alert(`Item '${item}' removed successfully!`)}
/>
</PageSection>
</div>
</Page>
);
}

Here is the finalized example, or you can use a running example here.

Ping Pong View

The idea of this View is to be used with other Ping Pong Views to show the interaction between them. To start, we will create two variables, pings and pongs, so the Channel can keep count of it. We need to create two states that will handle which View was the last to send the ping message, and the last to reply pong. Also, we implemented the PingPongChannelApi, which updates the states and values.

import { PingPongChannelApi } from "ping-pong-view/dist/api";
let pings = 0;
let pongs = 0;
export function PingPongViewsPage() {
const [lastPing, setLastPing] = useState<string>("-");
const [lastPong, setLastPong] = useState<string>("-");
const api: PingPongChannelApi = useMemo(() => {
return {
pingPongView__ping(source: string) {
pings++;
setLastPing(source);
},
pingPongView__pong(source: string, replyingTo: string) {
pongs++;
setLastPong(source);
},
};
}, [pings, pongs]);
// …
}

We use the EmbeddedPingPong component that we created on the Ping Pong implementation and simply started it with the Envelope location and the Channel API.

<EmbeddedPingPong
{…api}
name={"React 1"}
targetOrigin={window.location.origin}
mapping={{ title: "Ping-Pong Page in React", envelopePath: "envelope/ping-pong-view-react-impl.html" }}
/>

This is the final look with 3 Ping Pong Views and a Sidebar to show the information. You can use it by accessing here.

This screenshot was taken after a Ping request from React 1, React 2, and React 3 (respectively).

Wrapping up

This, for sure, was a long ride! We covered a lot of topics and almost everything that Kogito Tooling can do for you! We hope that this post could help you to implement your own custom Editor and/or View.

Thanks for reading, and stay tuned for more Kogito updates!

Stay safe,

Luiz

This post was original published on here.
0 0 vote
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments