Page skeleton
<!doctype html>
<html>
<head>
<script src="/msm2.bundle.js"></script>
</head>
<body>
<msm:fragment id="header" path="main/components/navbar.html" />
<main>
<msm:if id="isLoggedIn" bean="userServiceImpl" scope="SESSION" test="isLoggedIn">
<msm:template>
<msm-navbar></msm-navbar>
</msm:template>
</msm:if>
</main>
</body>
</html>
const app = new MSM2.App("wss://example.com");
WebSocket JSON endpoints (overview)
/request/json/bean/{INVOKE|UPDATE}/request/json/repository/{INVOKE|UPDATE}/request/json/database
Backend service call
import { callService } from "msm-components/dist/services.js";
const response = await callService(
"searchManagerServiceImpl", "PROTOTYPE", "search",
[{ query: { isPublishedForSale: true }, offset: 0, limit: 10 }]
);
const items = response.result?.data?.content ?? [];
JSON.stringify(request) as its argument.
Repository and bean operations
import { callRepository, updateBean } from "msm-components/dist/services.js";
const categoryResponse = await callRepository("blogCategoryRepository", {
query: { _id: categoryId }, skip: 0, limit: 1,
});
// Updates values on a bean registered with @Component or @Service.
await updateBean(
"dummyBean", "PROTOTYPE",
{ description: "Updated description" }
);
React data loading
import { useEffect, useState } from "react";
const app = new MSM2.App("wss://example.com");
export function ProductList() {
const [items, setItems] = useState([]);
useEffect(() => {
const subscription = app.invokeAndGetJson$(
"searchManagerServiceImpl", "PROTOTYPE", "search",
[{ type: "SellerSKU", query: {}, offset: 0, limit: 10 }], null, "USER"
).subscribe((response) => setItems(response.result?.data?.content ?? []));
return () => subscription.unsubscribe();
}, []);
return <ul>{items.map((item) => <li key={item.id}>{item.name}</li>)}</ul>;
}
SSR and custom events
app.renderPage();
app.renderElement(
[{ elementId: "cart-container", path: "/components/cart", payload: {} }],
{ userId }
);
const subscription = app.registerJsonWebSocket("jobs", (event) => {
console.log("Job event", event);
});
Translations and routing
import { setLocalesPath, translate, loadManyTranslations } from "msm-components/dist/services.js";
import { routeManager } from "msm-components/dist/services.js";
setLocalesPath("/assets/locales/{{lng}}.json");
document.title = await translate("DashboardTitle");
const texts = await loadManyTranslations("SaveButton", "CancelButton");
routeManager.init("My application");
routeManager.addRoute("/", "home", "HomeTitle");
routeManager.addRoute("/products/:id.html", "product", "ProductTitle");
routeManager.loadRouteByCurrentUrl();
routeManager.navigate("product", "42.html");
Web components
<msc-header></msc-header>
<msm-button type="OUTLINE" variant="emphasis" href="/catalog">
Open catalog
</msm-button>
<msm-login-form></msm-login-form>
Authentication
MSM2.App.AuthHelper.authenticate$(
"/api/v1/authenticate", "username=user@example.com&password=secret"
).subscribe({
next: (response) => console.log(response),
error: (error) => console.error("Login failed", error),
});
MSM2.App.AuthHelper.logout$("/api/v1/logout", "role=user")
.subscribe(() => { window.location.href = "/user/login.html"; });
REST request and file upload
const response = await fetch("/api/v1/bean/request", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Site-Context": "my-site",
"Lang-Context": "en",
},
body: JSON.stringify({
beanId: "frameworkOperationServiceImpl",
scope: "PROTOTYPE", functionName: "getLangCode", args: [],
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const formData = new FormData();
formData.append("file", file);
formData.append("payload", JSON.stringify({ destination: `${sellerId}/images` }));
formData.append("command", "UPLOAD_IMAGE");
await fetch("/api/v1/uploadImage", { method: "POST", body: formData });
Form payload helper
import { getPayloadOfForm } from "msm-components/dist/services.js";
const payload = getPayloadOfForm(document.querySelector("form[data-profile]"));
console.log(payload);
Cookie and language-aware URL helpers
import {
setCookie, getCookie, languageAwareUrl,
} from "msm-components/dist/services.js";
setCookie("promo", "spring", null, null, "/");
console.log(getCookie("i18next"));
console.log(await languageAwareUrl("/catalog"));
Custom topic subscription
const subscription = app.registerJsonWebSocket("orders", (event) => {
console.log("Order event", event);
});
subscription.unsubscribe();
Partial HTML render
app.renderElement(
[{ elementId: "profile", path: "/components/profile", payload: {} }],
{ userId }
);
Retry a failed service call
async function callWithRetry(task, attempts = 3) {
let lastError;
for (let attempt = 1; attempt <= attempts; attempt += 1) {
try { return await task(); }
catch (error) {
lastError = error;
await new Promise((resolve) => setTimeout(resolve, attempt * 500));
}
}
throw lastError;
}
const response = await callWithRetry(() => callService(
"frameworkOperationServiceImpl", "PROTOTYPE", "getLangCode", []
));
JSON response handling
const response = await callService(
"frameworkOperationServiceImpl", "PROTOTYPE", "getLangCode", []
);
if (response.result == null) {
throw new Error("The service returned an empty result");
}