Skip to content

Commit 0971de4

Browse files
committed
Add version control api and message passing api for web extensibility
1 parent 8a10569 commit 0971de4

File tree

3 files changed

+409
-0
lines changed

3 files changed

+409
-0
lines changed

content/en/docs/apidocs-mxsdk/apidocs/studio-pro-11/extensibility-api/web/_index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,5 @@ Here is a list of how-tos for you to begin with:
4545
* [How to View User Preferences Using Web API](/apidocs-mxsdk/apidocs/web-extensibility-api-11/preference-api/)
4646
* [How to Show a Modal Dialog Using Web API](/apidocs-mxsdk/apidocs/web-extensibility-api-11/dialog-api/)
4747
* [How to Open Documents Using Web API](/apidocs-mxsdk/apidocs/web-extensibility-api-11/editor-api/)
48+
* [How to Exchange Information Between Active Views Using Web API](/apidocs-mxsdk/apidocs/web-extensibility-api-11/message-passing-api/)
49+
* [How to Show Version Control Information](/apidocs-mxsdk/apidocs/web-extensibility-api-11/version-control-api/)
Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
---
2+
title: "Exchange Information Between Active Views Using Web API"
3+
linktitle: "Communication between views"
4+
url: /apidocs-mxsdk/apidocs/web-extensibility-api-11/message-passing-api/
5+
---
6+
7+
## Introduction
8+
9+
This how-to describes how to pass information between different active views (e.g., tabs, dialogs and panes)
10+
of the same extension.
11+
12+
## Prerequisites
13+
14+
This how-to uses the results of [Get Started with the Web Extensibility API](/apidocs-mxsdk/apidocs/web-extensibility-api-11/getting-started/). Please complete that how-to before starting this one. You should also be familiar with creating menus as described in [Create a Menu Using Web API](/apidocs-mxsdk/apidocs/web-extensibility-api-11/menu-api/), and creating different kinds of views
15+
such as [tabs](/apidocs-mxsdk/apidocs/web-extensibility-api-11/tab-api/)
16+
and [panes](/apidocs-mxsdk/apidocs/web-extensibility-api-11/dockable-pane-api/).
17+
18+
## Communication patterns
19+
20+
To support passing information between different active contexts of an extension (its main entry point and active views),
21+
we have introduced Message Passing API, which can be obtained in `studioPro.ui.messagePassing`, where `studioPro` is
22+
the Studio Pro object obtained with `getStudioProApi` call.
23+
24+
This API supports two communication patterns: request-reply and message broadcasting.
25+
26+
### Request-reply
27+
28+
In request-reply pattern, one endpoint sends a message to another endpoint and expects to receive a reply.
29+
Message Passing API supports this pattern by allowing the sending side to send a message and specify a callback
30+
that will be invoked once a reply has been received.
31+
32+
To implement this behavior, insert this code in `main/index.ts`
33+
34+
```typescript {hl_lines=["16-25"]}
35+
import { IComponent, getStudioProApi } from "@mendix/extensions-api";
36+
37+
export const component: IComponent = {
38+
async loaded(componentContext) {
39+
const studioPro = getStudioProApi(componentContext);
40+
let counter = 0;
41+
// Add a menu item to the Extensions menu
42+
await studioPro.ui.extensionsMenu.add({
43+
menuId: "message-passing.MainMenu",
44+
caption: "Message passing",
45+
subMenus: [
46+
{ menuId: "message-passing.ShowTab", caption: "Show tab" },
47+
],
48+
});
49+
50+
await studioPro.ui.messagePassing.addMessageHandler<{type:string}>(async messageInfo => {
51+
const messageData = messageInfo.message;
52+
if (messageData.type === "incrementCounter") {
53+
counter++;
54+
await studioPro.ui.messagePassing.sendResponse(messageInfo.messageId, {
55+
type: "counterValue",
56+
counter
57+
});
58+
}
59+
});
60+
61+
// Open a tab when the menu item is clicked
62+
studioPro.ui.extensionsMenu.addEventListener(
63+
"menuItemActivated",
64+
async (args) => {
65+
if (args.menuId === "message-passing.ShowTab") {
66+
await studioPro.ui.tabs.open(
67+
{
68+
title: "MyExtension Tab"
69+
},
70+
{
71+
componentName: "extension/message-passing",
72+
uiEntrypoint: "tab",
73+
}
74+
);
75+
}
76+
}
77+
);
78+
}
79+
}
80+
```
81+
82+
Insert the following code into `src/ui/index.tsx`:
83+
84+
```typescript {hl_lines=["14-19"]}
85+
import React, { StrictMode, useCallback, useState } from "react";
86+
import { createRoot } from "react-dom/client";
87+
import { ComponentContext, getStudioProApi, IComponent } from "@mendix/extensions-api";
88+
89+
90+
type MessagePassingAppProps = {
91+
componentContext: ComponentContext
92+
}
93+
94+
function CounterState({ componentContext }: MessagePassingAppProps) {
95+
const studioPro = getStudioProApi(componentContext);
96+
const [counter, setCounter] = useState<number | null>(null);
97+
const incrementCounter = useCallback(async () => {
98+
studioPro.ui.messagePassing.sendMessage(
99+
{ type: "incrementCounter"},
100+
async (response: {type: 'counterValue', counter: number}) => {
101+
setCounter(response.counter);
102+
}
103+
);
104+
}, [componentContext]);
105+
106+
return (
107+
<div>
108+
<button onClick={incrementCounter}> Increment counter </button>
109+
<p> Counter value: {counter ?? "unknown"} </p>
110+
</div>
111+
);
112+
}
113+
114+
export const component: IComponent = {
115+
async loaded(componentContext) {
116+
createRoot(document.getElementById("root")!).render(
117+
<StrictMode>
118+
<CounterState componentContext={componentContext} />
119+
</StrictMode>
120+
);
121+
}
122+
}
123+
```
124+
125+
This extension will increase the value of `counter` in the main context every time the user presses a button in the
126+
active tab. To implement this we send a message from the tab to the main context, expecting that the main context
127+
will reply with the current value of `counter`.
128+
129+
In the highlighted lines we illustrate the behavior of responding to a message. First, in the main context (the file `src/main/index.ts`)
130+
we register a listener which will respond to every message sent from other contexts. Every message has an ID which
131+
we can use if we want to respond to this message. We use this ID to identify the message we are responding to.
132+
133+
When the main context sends a response, it will be picked up the `onResponse` callback registered in
134+
the highlighted lines of the file (`src/ui/index.tsx`). Note that this callback will be invoked at most once,
135+
as each message can have only one response.
136+
137+
### Broadcast
138+
139+
In the broadcast pattern, one context broadcasts messages to all other contexts that are listening to
140+
a message. To implement this pattern, copy the following code to `src/main/index.ts`:
141+
142+
```typescript
143+
import { IComponent, getStudioProApi } from "@mendix/extensions-api";
144+
145+
export const component: IComponent = {
146+
async loaded(componentContext) {
147+
const studioPro = getStudioProApi(componentContext);
148+
let counter = 0;
149+
// Add a menu item to the Extensions menu
150+
await studioPro.ui.extensionsMenu.add({
151+
menuId: "message-passing.MainMenu",
152+
caption: "Message Passing",
153+
subMenus: [
154+
{ menuId: "message-passing.ShowTab", caption: "Show tab" },
155+
{ menuId: "message-passing.ShowPane", caption: "Show pane" },
156+
],
157+
});
158+
159+
const paneHandle = await studioPro.ui.panes.register({
160+
title: 'Message Passing Pane',
161+
initialPosition: 'right',
162+
}, {
163+
componentName: "extension/message-passing",
164+
uiEntrypoint: "pane"
165+
})
166+
167+
// Open a tab when the menu item is clicked
168+
studioPro.ui.extensionsMenu.addEventListener(
169+
"menuItemActivated",
170+
async (args) => {
171+
if (args.menuId === "message-passing.ShowTab") {
172+
await studioPro.ui.tabs.open(
173+
{
174+
title: "MyExtension Tab"
175+
},
176+
{
177+
componentName: "extension/message-passing",
178+
uiEntrypoint: "tab",
179+
}
180+
);
181+
} else if (args.menuId === "message-passing.ShowPane") {
182+
await studioPro.ui.panes.open(paneHandle);
183+
}
184+
}
185+
);
186+
}
187+
}
188+
```
189+
190+
Then rename `src/ui/index.tsx` to `src/ui/tab.tsx` and paste the following code into it:
191+
192+
```typescript {hl_lines=["13-15"]}
193+
import React, { StrictMode, useCallback, useEffect, useState } from "react";
194+
import { createRoot } from "react-dom/client";
195+
import { ComponentContext, getStudioProApi, IComponent } from "@mendix/extensions-api";
196+
197+
198+
type MessagePassingAppProps = {
199+
componentContext: ComponentContext
200+
}
201+
202+
function NameBroadcaster({ componentContext }: MessagePassingAppProps) {
203+
const studioPro = getStudioProApi(componentContext);
204+
const [name, setName] = useState<string>("");
205+
useEffect(() => {
206+
studioPro.ui.messagePassing.sendMessage({ type: "nameChanged", name });
207+
}, [name]);
208+
209+
return (
210+
<div>
211+
Name: <input value={name} onChange={e => setName(e.target.value)} />
212+
</div>
213+
);
214+
}
215+
216+
export const component: IComponent = {
217+
async loaded(componentContext) {
218+
createRoot(document.getElementById("root")!).render(
219+
<StrictMode>
220+
<NameBroadcaster componentContext={componentContext} />
221+
</StrictMode>
222+
);
223+
}
224+
}
225+
```
226+
227+
Then, create a file `src/ui/pane.tsx` and paste the following code into it:
228+
229+
```typescript {hl_lines=["14-19"]}
230+
import React, { StrictMode, useCallback, useEffect, useState } from "react";
231+
import { createRoot } from "react-dom/client";
232+
import { ComponentContext, getStudioProApi, IComponent } from "@mendix/extensions-api";
233+
234+
235+
type MessagePassingAppProps = {
236+
componentContext: ComponentContext
237+
}
238+
239+
function Greeter({ componentContext }: MessagePassingAppProps) {
240+
const studioPro = getStudioProApi(componentContext);
241+
const [name, setName] = useState<string>("unknown");
242+
useEffect(() => {
243+
studioPro.ui.messagePassing.addMessageHandler<{ type: string; name: string }>(async messageInfo => {
244+
const messageData = messageInfo.message;
245+
if (messageData.type === "nameChanged") {
246+
setName(messageData.name);
247+
}
248+
});
249+
}, [componentContext]);
250+
251+
return (
252+
<div>
253+
Hello {name}!
254+
</div>
255+
);
256+
}
257+
258+
export const component: IComponent = {
259+
async loaded(componentContext) {
260+
createRoot(document.getElementById("root")!).render(
261+
<StrictMode>
262+
<Greeter componentContext={componentContext} />
263+
</StrictMode>
264+
);
265+
}
266+
}
267+
```
268+
269+
Add the new views to `build-extension.mjs` by replacing the value of `entryPoints` array by
270+
271+
```javascript
272+
const entryPoints = [
273+
{
274+
in: 'src/main/index.ts',
275+
out: 'main'
276+
}
277+
]
278+
279+
entryPoints.push({
280+
in: 'src/ui/tab.tsx',
281+
out: 'tab'
282+
})
283+
284+
entryPoints.push({
285+
in: 'src/ui/pane.tsx',
286+
out: 'pane'
287+
})
288+
```
289+
290+
Lastly, replace the `manifest.json` contents by
291+
292+
```
293+
{
294+
"mendixComponent": {
295+
"entryPoints": {
296+
"main": "main.js",
297+
"ui": {
298+
"tab": "tab.js",
299+
"pane": "pane.js"
300+
}
301+
}
302+
}
303+
}
304+
```
305+
306+
This code first ensures that both the tab and the pane are registered in `src/main/index.ts`.
307+
Then, the code in the tab will send a message every time the value of `name` is updated.
308+
The pane is listening to all the messages emitted from the tab and reacts by updating its
309+
view any time the new message is received.
310+
311+
## Conclusion
312+
313+
You have mastered two patterns of communication between different active views in Studio Pro.
314+
315+
## Extensibility Feedback
316+
317+
If you would like to provide us with additional feedback, you can complete a small [survey](https://survey.alchemer.eu/s3/90801191/Extensibility-Feedback).
318+
319+
Any feedback is appreciated.

0 commit comments

Comments
 (0)