Refactor Documentation for Backend API Method Naming and Iframe Communication
- Updated iframe documentation to remove token handling from the setup message, clarifying the focus on theme and locale. - Added a new section in MCP documentation detailing HTTP transport configuration for API access, enhancing clarity on authorization. - Revised frontend API documentation to standardize method naming by removing the 'Api' prefix in examples, improving consistency across backend calls. - Enhanced event handling documentation by updating method names in examples, ensuring alignment with the new naming conventions.
This commit is contained in:
parent
aa01ea216c
commit
ebf3dcc870
9 changed files with 100 additions and 82 deletions
|
|
@ -47,8 +47,8 @@ When the iframe loads, CUI sends a `setup` message:
|
||||||
// In your page script
|
// In your page script
|
||||||
window.addEventListener("message", (e) => {
|
window.addEventListener("message", (e) => {
|
||||||
if (e.data.type === "setup") {
|
if (e.data.type === "setup") {
|
||||||
const { theme, locale, token } = e.data.message;
|
const { theme, locale } = e.data.message;
|
||||||
// Apply theme, store token, set locale
|
// Apply theme, set locale
|
||||||
document.documentElement.setAttribute("data-theme", theme);
|
document.documentElement.setAttribute("data-theme", theme);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -165,7 +165,7 @@ window.addEventListener("message", (e) => {
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "setup":
|
case "setup":
|
||||||
// Initial setup with theme, locale, token
|
// Initial setup with theme, locale
|
||||||
break;
|
break;
|
||||||
case "refresh":
|
case "refresh":
|
||||||
// CUI requests page refresh
|
// CUI requests page refresh
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,16 @@ Map Yao Processes directly to MCP tools:
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**HTTP (REST API)**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"transport": "http",
|
||||||
|
"url": "https://mcp.example.com/api",
|
||||||
|
"authorization_token": "$ENV.TOKEN"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
**SSE (Server-Sent Events)**
|
**SSE (Server-Sent Events)**
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,7 @@ const self = this as Component;
|
||||||
|
|
||||||
// Event handler bound to s:on-click="HandleClick"
|
// Event handler bound to s:on-click="HandleClick"
|
||||||
self.HandleClick = async (event: Event, data: EventData) => {
|
self.HandleClick = async (event: Event, data: EventData) => {
|
||||||
const result = await $Backend().Call("ApiGetData", data.id);
|
const result = await $Backend().Call("GetData", data.id);
|
||||||
console.log(result);
|
console.log(result);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -191,7 +191,7 @@ self.HandleSubmit = async (event: Event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const form = event.target as HTMLFormElement;
|
const form = event.target as HTMLFormElement;
|
||||||
const formData = new FormData(form);
|
const formData = new FormData(form);
|
||||||
await $Backend().Call("ApiSubmit", Object.fromEntries(formData));
|
await $Backend().Call("Submit", Object.fromEntries(formData));
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -201,7 +201,7 @@ self.HandleSubmit = async (event: Event) => {
|
||||||
import { $Backend, Yao } from "@yao/sui";
|
import { $Backend, Yao } from "@yao/sui";
|
||||||
|
|
||||||
// Call backend method
|
// Call backend method
|
||||||
const data = await $Backend().Call("ApiMethodName", arg1, arg2);
|
const data = await $Backend().Call("MethodName", arg1, arg2);
|
||||||
|
|
||||||
// Direct API calls
|
// Direct API calls
|
||||||
const yao = new Yao();
|
const yao = new Yao();
|
||||||
|
|
@ -550,7 +550,7 @@ ctx.Send({
|
||||||
import { $Backend, Yao } from "@yao/sui";
|
import { $Backend, Yao } from "@yao/sui";
|
||||||
|
|
||||||
// Call backend method defined in .backend.ts
|
// Call backend method defined in .backend.ts
|
||||||
const data = await $Backend().Call("ApiMethodName", arg1, arg2);
|
const data = await $Backend().Call("MethodName", arg1, arg2);
|
||||||
|
|
||||||
// Direct API calls
|
// Direct API calls
|
||||||
const yao = new Yao();
|
const yao = new Yao();
|
||||||
|
|
|
||||||
|
|
@ -273,7 +273,7 @@ const self = this as Component;
|
||||||
|
|
||||||
// Event handler bound to s:on-click="HandleClick"
|
// Event handler bound to s:on-click="HandleClick"
|
||||||
self.HandleClick = async (event: Event, data: EventData) => {
|
self.HandleClick = async (event: Event, data: EventData) => {
|
||||||
const result = await $Backend().Call("ApiGetData", data.id);
|
const result = await $Backend().Call("GetData", data.id);
|
||||||
console.log(result);
|
console.log(result);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -282,7 +282,7 @@ self.HandleSubmit = async (event: Event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const form = event.target as HTMLFormElement;
|
const form = event.target as HTMLFormElement;
|
||||||
const formData = new FormData(form);
|
const formData = new FormData(form);
|
||||||
await $Backend().Call("ApiSubmit", Object.fromEntries(formData));
|
await $Backend().Call("Submit", Object.fromEntries(formData));
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,20 +58,20 @@ function BeforeRender(request: Request): Record<string, any> {
|
||||||
|
|
||||||
## API Methods
|
## API Methods
|
||||||
|
|
||||||
Functions prefixed with `Api` are exposed as callable endpoints:
|
Functions prefixed with `Api` are exposed as callable endpoints. The backend automatically adds the `Api` prefix, so frontend calls use the method name without the prefix:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// Callable from frontend as: this.backend.ApiGetUsers()
|
// Callable from frontend as: $Backend().Call("GetUsers")
|
||||||
function ApiGetUsers(request: Request): any[] {
|
function ApiGetUsers(request: Request): any[] {
|
||||||
return Process("models.user.Get", {});
|
return Process("models.user.Get", {});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callable from frontend as: this.backend.ApiCreateUser(name, email)
|
// Callable from frontend as: $Backend().Call("CreateUser", name, email)
|
||||||
function ApiCreateUser(name: string, email: string, request: Request): any {
|
function ApiCreateUser(name: string, email: string, request: Request): any {
|
||||||
return Process("models.user.Create", { name, email });
|
return Process("models.user.Create", { name, email });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callable from frontend as: this.backend.ApiDeleteUser(id)
|
// Callable from frontend as: $Backend().Call("DeleteUser", id)
|
||||||
function ApiDeleteUser(id: string, request: Request): boolean {
|
function ApiDeleteUser(id: string, request: Request): boolean {
|
||||||
Process("models.user.Delete", id);
|
Process("models.user.Delete", id);
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -81,19 +81,20 @@ function ApiDeleteUser(id: string, request: Request): boolean {
|
||||||
### Calling from Frontend
|
### Calling from Frontend
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
function Page(component: HTMLElement) {
|
import { $Backend, Component } from "@yao/sui";
|
||||||
this.root = component;
|
|
||||||
|
|
||||||
this.loadUsers = async () => {
|
const self = this as Component;
|
||||||
const users = await this.backend.ApiGetUsers();
|
|
||||||
console.log(users);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.createUser = async () => {
|
self.LoadUsers = async () => {
|
||||||
const user = await this.backend.ApiCreateUser("John", "john@example.com");
|
// Call "ApiGetUsers" in backend script (without "Api" prefix)
|
||||||
console.log("Created:", user);
|
const users = await $Backend().Call("GetUsers");
|
||||||
};
|
console.log(users);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
self.CreateUser = async () => {
|
||||||
|
const user = await $Backend().Call("CreateUser", "John", "john@example.com");
|
||||||
|
console.log("Created:", user);
|
||||||
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
## Constants
|
## Constants
|
||||||
|
|
@ -115,10 +116,12 @@ const __sui_constants = {
|
||||||
Access in frontend:
|
Access in frontend:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
function Page(component: HTMLElement) {
|
import { Component } from "@yao/sui";
|
||||||
console.log(this.constants.API_URL); // "/api/v1"
|
|
||||||
console.log(this.constants.MAX_ITEMS); // 100
|
const self = this as Component;
|
||||||
}
|
|
||||||
|
console.log(self.constants.API_URL); // "/api/v1"
|
||||||
|
console.log(self.constants.MAX_ITEMS); // 100
|
||||||
```
|
```
|
||||||
|
|
||||||
## Helpers
|
## Helpers
|
||||||
|
|
@ -147,11 +150,13 @@ function validateEmail(email: string): boolean {
|
||||||
Access in frontend:
|
Access in frontend:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
function Page(component: HTMLElement) {
|
import { Component } from "@yao/sui";
|
||||||
const formatted = this.helpers.formatDate("2024-01-15");
|
|
||||||
const price = this.helpers.formatCurrency(99.99);
|
const self = this as Component;
|
||||||
const isValid = this.helpers.validateEmail("test@example.com");
|
|
||||||
}
|
const formatted = self.helpers.formatDate("2024-01-15");
|
||||||
|
const price = self.helpers.formatCurrency(99.99);
|
||||||
|
const isValid = self.helpers.validateEmail("test@example.com");
|
||||||
```
|
```
|
||||||
|
|
||||||
## Request Object
|
## Request Object
|
||||||
|
|
|
||||||
|
|
@ -43,11 +43,13 @@ A component is just a page with a single root element:
|
||||||
**`/card/card.ts`**:
|
**`/card/card.ts`**:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
function card(component: HTMLElement) {
|
import { Component } from "@yao/sui";
|
||||||
this.root = component;
|
|
||||||
this.store = new __sui_store(component);
|
const self = this as Component;
|
||||||
this.props = new __sui_props(component);
|
|
||||||
}
|
// self.root - Root element
|
||||||
|
// self.store - Data store
|
||||||
|
// self.props - Props from attributes
|
||||||
```
|
```
|
||||||
|
|
||||||
## Using Components
|
## Using Components
|
||||||
|
|
@ -94,17 +96,16 @@ Props are passed as attributes:
|
||||||
Access props in the component script:
|
Access props in the component script:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
function userCard(component: HTMLElement) {
|
import { Component } from "@yao/sui";
|
||||||
this.root = component;
|
|
||||||
this.props = new __sui_props(component);
|
|
||||||
|
|
||||||
// Get single prop
|
const self = this as Component;
|
||||||
const name = this.props.Get("name");
|
|
||||||
|
|
||||||
// Get all props
|
// Get single prop
|
||||||
const allProps = this.props.List();
|
const name = self.props.Get("name");
|
||||||
// { name: "John", email: "john@example.com", avatar: "...", role: "admin" }
|
|
||||||
}
|
// Get all props
|
||||||
|
const allProps = self.props.List();
|
||||||
|
// { name: "John", email: "john@example.com", avatar: "...", role: "admin" }
|
||||||
```
|
```
|
||||||
|
|
||||||
Access props in backend script:
|
Access props in backend script:
|
||||||
|
|
@ -221,7 +222,7 @@ self.watch = {
|
||||||
|
|
||||||
// Event handlers (bound to s:on-click="HandleClick")
|
// Event handlers (bound to s:on-click="HandleClick")
|
||||||
self.HandleClick = async (event: Event, data: EventData) => {
|
self.HandleClick = async (event: Event, data: EventData) => {
|
||||||
const result = await $Backend().Call("ApiMethod", data.id);
|
const result = await $Backend().Call("Method", data.id);
|
||||||
// Handle result
|
// Handle result
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
@ -339,7 +340,6 @@ Component CSS is automatically scoped using namespace attributes:
|
||||||
## Important Notes
|
## Important Notes
|
||||||
|
|
||||||
1. **Single Root Element**: Components must have exactly one root element
|
1. **Single Root Element**: Components must have exactly one root element
|
||||||
2. **Route as Identifier**: The page route becomes the component name (e.g., `/card` → `card()`)
|
2. **Scoped Styles**: CSS is automatically scoped to prevent conflicts
|
||||||
3. **Scoped Styles**: CSS is automatically scoped to prevent conflicts
|
3. **Recursive Prevention**: SUI detects and prevents recursive component inclusion
|
||||||
4. **Recursive Prevention**: SUI detects and prevents recursive component inclusion
|
4. **Component Pattern**: Use `const self = this as Component` to access component APIs
|
||||||
5. **Script Naming**: Function name is derived from the route path
|
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,7 @@ self.DeleteItem = async (event: Event, data: EventData) => {
|
||||||
const item = data.item; // Object from s:json-item
|
const item = data.item; // Object from s:json-item
|
||||||
|
|
||||||
if (confirm(`Delete ${item.name}?`)) {
|
if (confirm(`Delete ${item.name}?`)) {
|
||||||
await $Backend().Call("ApiDeleteItem", id);
|
await $Backend().Call("DeleteItem", id);
|
||||||
(event.target as HTMLElement).closest(".item")?.remove();
|
(event.target as HTMLElement).closest(".item")?.remove();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -285,7 +285,7 @@ self.HandleSubmit = async (event: Event) => {
|
||||||
const password = formData.get("password");
|
const password = formData.get("password");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await $Backend().Call("ApiLogin", email, password);
|
await $Backend().Call("Login", email, password);
|
||||||
window.location.href = "/dashboard";
|
window.location.href = "/dashboard";
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert("Login failed");
|
alert("Login failed");
|
||||||
|
|
@ -380,7 +380,7 @@ self.AddTodo = async (event: Event) => {
|
||||||
const input = form.querySelector("input") as HTMLInputElement;
|
const input = form.querySelector("input") as HTMLInputElement;
|
||||||
|
|
||||||
if (input.value.trim()) {
|
if (input.value.trim()) {
|
||||||
const todo = await $Backend().Call("ApiAddTodo", input.value);
|
const todo = await $Backend().Call("AddTodo", input.value);
|
||||||
const todos = self.state.Get("todos") || [];
|
const todos = self.state.Get("todos") || [];
|
||||||
self.state.Set("todos", [...todos, todo]);
|
self.state.Set("todos", [...todos, todo]);
|
||||||
input.value = "";
|
input.value = "";
|
||||||
|
|
@ -389,11 +389,11 @@ self.AddTodo = async (event: Event) => {
|
||||||
|
|
||||||
self.ToggleTodo = async (event: Event, data: EventData) => {
|
self.ToggleTodo = async (event: Event, data: EventData) => {
|
||||||
const checkbox = event.target as HTMLInputElement;
|
const checkbox = event.target as HTMLInputElement;
|
||||||
await $Backend().Call("ApiToggleTodo", data.id, checkbox.checked);
|
await $Backend().Call("ToggleTodo", data.id, checkbox.checked);
|
||||||
};
|
};
|
||||||
|
|
||||||
self.DeleteTodo = async (event: Event, data: EventData) => {
|
self.DeleteTodo = async (event: Event, data: EventData) => {
|
||||||
await $Backend().Call("ApiDeleteTodo", data.id);
|
await $Backend().Call("DeleteTodo", data.id);
|
||||||
const todos = self.state.Get("todos").filter((t: any) => t.id !== data.id);
|
const todos = self.state.Get("todos").filter((t: any) => t.id !== data.id);
|
||||||
self.state.Set("todos", todos);
|
self.state.Set("todos", todos);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -40,23 +40,26 @@ const items = component.queryAll(".item"); // Returns NodeList
|
||||||
|
|
||||||
### Via $Backend
|
### Via $Backend
|
||||||
|
|
||||||
|
The backend automatically adds the `Api` prefix to method names, so you call without the prefix:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { $Backend } from "@yao/sui";
|
import { $Backend } from "@yao/sui";
|
||||||
|
|
||||||
// Call backend API methods
|
// Call backend API methods (backend functions are ApiGetUsers, ApiGetUser, ApiCreateUser)
|
||||||
const users = await $Backend().Call("ApiGetUsers");
|
const users = await $Backend().Call("GetUsers");
|
||||||
const user = await $Backend().Call("ApiGetUser", 123);
|
const user = await $Backend().Call("GetUser", 123);
|
||||||
const result = await $Backend().Call("ApiCreateUser", "John", "john@example.com");
|
const result = await $Backend().Call("CreateUser", "John", "john@example.com");
|
||||||
```
|
```
|
||||||
|
|
||||||
### Direct Call
|
### Direct Call
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// __sui_backend_call(route, headers, method, ...args)
|
// __sui_backend_call(route, headers, method, ...args)
|
||||||
|
// Note: method name here also gets Api prefix added automatically
|
||||||
const result = await __sui_backend_call(
|
const result = await __sui_backend_call(
|
||||||
"/users/list", // Page route
|
"/users/list", // Page route
|
||||||
{ "X-Custom-Header": "value" }, // Custom headers
|
{ "X-Custom-Header": "value" }, // Custom headers
|
||||||
"ApiGetUsers", // Method name
|
"GetUsers", // Method name (backend has ApiGetUsers)
|
||||||
{ page: 1, limit: 10 } // Arguments
|
{ page: 1, limit: 10 } // Arguments
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
@ -81,7 +84,7 @@ import { $Backend, Component } from "@yao/sui";
|
||||||
const self = this as Component;
|
const self = this as Component;
|
||||||
|
|
||||||
self.RefreshUsers = async () => {
|
self.RefreshUsers = async () => {
|
||||||
const users = await $Backend().Call("ApiGetUsers");
|
const users = await $Backend().Call("GetUsers");
|
||||||
|
|
||||||
// Render with data
|
// Render with data
|
||||||
await self.render("userList", { users });
|
await self.render("userList", { users });
|
||||||
|
|
@ -352,7 +355,7 @@ async function loadUsers() {
|
||||||
|
|
||||||
// Create user
|
// Create user
|
||||||
self.CreateUser = async (event: Event, data: EventData) => {
|
self.CreateUser = async (event: Event, data: EventData) => {
|
||||||
const response = await $Backend().Call("ApiCreateUser", data.name, data.email);
|
const response = await $Backend().Call("CreateUser", data.name, data.email);
|
||||||
const users = self.state.Get("users");
|
const users = self.state.Get("users");
|
||||||
self.state.Set("users", [...users, response]);
|
self.state.Set("users", [...users, response]);
|
||||||
};
|
};
|
||||||
|
|
@ -500,7 +503,7 @@ init();
|
||||||
// Event handlers
|
// Event handlers
|
||||||
self.HandleSave = async (event: Event, data: EventData) => {
|
self.HandleSave = async (event: Event, data: EventData) => {
|
||||||
try {
|
try {
|
||||||
await $Backend().Call("ApiSave", data);
|
await $Backend().Call("Save", data);
|
||||||
sendAction("notify.success", { message: "Saved successfully!" });
|
sendAction("notify.success", { message: "Saved successfully!" });
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
sendAction("notify.error", { message: error.message });
|
sendAction("notify.error", { message: error.message });
|
||||||
|
|
|
||||||
|
|
@ -112,18 +112,18 @@ Named keys are used internally for translation lookup. The `keys` section in loc
|
||||||
### Scripts
|
### Scripts
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
function Page(component: HTMLElement) {
|
import { Component } from "@yao/sui";
|
||||||
this.root = component;
|
|
||||||
|
|
||||||
this.showMessage = () => {
|
const self = this as Component;
|
||||||
const message = __m("Operation completed");
|
|
||||||
alert(message);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.confirm = () => {
|
self.ShowMessage = () => {
|
||||||
return confirm(__m("Are you sure you want to delete?"));
|
const message = __m("Operation completed");
|
||||||
};
|
alert(message);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
self.Confirm = () => {
|
||||||
|
return confirm(__m("Are you sure you want to delete?"));
|
||||||
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
## Locale Detection
|
## Locale Detection
|
||||||
|
|
@ -223,17 +223,17 @@ This command:
|
||||||
<a href="/contact" s:trans>Contact</a>
|
<a href="/contact" s:trans>Contact</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<button s:on-click="showWelcome" s:trans>Show Welcome</button>
|
<button s:on-click="ShowWelcome" s:trans>Show Welcome</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function home(component) {
|
import { Component } from "@yao/sui";
|
||||||
this.root = component;
|
|
||||||
|
|
||||||
this.showWelcome = () => {
|
const self = this as Component;
|
||||||
alert(__m("Welcome to our site!"));
|
|
||||||
};
|
self.ShowWelcome = () => {
|
||||||
}
|
alert(__m("Welcome to our site!"));
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue