Skip to content

Commit f1f7522

Browse files
authored
Merge pull request #10122 from SofiBili/development
Add version control api and message passing api for web extensibility
2 parents 38fce4b + 6829143 commit f1f7522

File tree

3 files changed

+412
-12
lines changed

3 files changed

+412
-12
lines changed

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

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,17 @@ For detailed explanation on how to get started with extensions, check out [Get S
3333

3434
## How-tos
3535

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

0 commit comments

Comments
 (0)