From 46ae99948e0a30bfa80b3564ef1721a62311f8eb Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 3 Jan 2026 17:39:28 +0800 Subject: [PATCH] Enhance Backend Scripts Documentation - Added important notes regarding ES Module exports and route parameter access in backend scripts. - Introduced a new section on data binding methods, detailing how to call backend script methods from `.json` configurations. - Included examples demonstrating the correct usage of request parameters and common pitfalls to avoid, improving clarity for developers. --- sui/docs/backend-scripts.md | 78 +++++++++++++ sui/docs/data-binding.md | 38 +++++++ sui/docs/routing.md | 214 ++++++++++++++++++++++++++++++++++++ 3 files changed, 330 insertions(+) create mode 100644 sui/docs/routing.md diff --git a/sui/docs/backend-scripts.md b/sui/docs/backend-scripts.md index eca2fa4f..a915e5dc 100644 --- a/sui/docs/backend-scripts.md +++ b/sui/docs/backend-scripts.md @@ -14,6 +14,12 @@ Backend scripts use the naming convention `.backend.ts` or `.backend └── list.backend.ts # Backend script ``` +## Important Notes + +> **⚠️ No ES Module Exports**: Backend scripts do NOT support ES Module `export` syntax. Simply define functions directly - they will be automatically available based on naming conventions. + +> **⚠️ `$param` Not Available**: Unlike HTML templates, you cannot use `$param.id` directly in backend scripts. Route parameters must be accessed via the `request.params` object passed to your functions. + ## BeforeRender The `BeforeRender` function is called before the page is rendered: @@ -248,6 +254,78 @@ function ApiUpdateUser(id: string, data: any, request: Request): any { } ``` +## Data Binding Methods (Called from `.json`) + +In addition to `Api` prefixed methods (for frontend calls) and `BeforeRender`, you can define methods that are called directly from the page's `.json` configuration using the `@MethodName` syntax. + +### Naming Convention + +| Call Source | Function Name | Example Call | +| ---------------------------- | --------------- | ------------------------------- | +| Frontend `$Backend().Call()` | `ApiMethodName` | `$Backend().Call("MethodName")` | +| `.json` data binding | `MethodName` | `"$data": "@MethodName"` | +| Before render | `BeforeRender` | Automatic | + +### How It Works + +When using `@MethodName` in `.json`, SUI calls the backend function with the **Request object appended as the last argument**: + +```typescript +// In .json: "$record": "@GetRecord" +// SUI internally calls: GetRecord(request) + +function GetRecord(request: Request): any { + // Access route parameters via request.params + const id = request.params.id; + return Process("models.record.Find", id); +} +``` + +### With Additional Arguments + +You can also pass arguments from `.json`: + +```json +{ + "$items": { + "process": "@GetItems", + "args": ["category_a", 10] + } +} +``` + +```typescript +// SUI calls: GetItems("category_a", 10, request) +// Arguments from .json come first, request is appended last + +function GetItems(category: string, limit: number, request: Request): any[] { + return Process("models.item.Get", { + wheres: [{ column: "category", value: category }], + limit: limit, + }); +} +``` + +### Common Pitfall: Accessing Route Parameters + +❌ **Wrong** - `$param` is not available in backend scripts: + +```typescript +function GetRecord(): any { + const id = $param.id; // ReferenceError: $param is not defined + return Process("models.record.Find", id); +} +``` + +✅ **Correct** - Use `request.params`: + +```typescript +function GetRecord(request: Request): any { + const id = request.params.id; // Works! + return Process("models.record.Find", id); +} +``` + ## Complete Example **`/users/profile/profile.backend.ts`**: diff --git a/sui/docs/data-binding.md b/sui/docs/data-binding.md index e64dcd81..7edb0764 100644 --- a/sui/docs/data-binding.md +++ b/sui/docs/data-binding.md @@ -150,6 +150,44 @@ Note: `$header` is only available in JSON configuration, not in HTML templates. } ``` +### Calling Backend Script Methods + +Use the `@MethodName` syntax to call functions defined in the page's `.backend.ts` file: + +```json +{ + "$record": "@GetRecord", + "$items": { + "process": "@GetItems", + "args": ["active", 20] + } +} +``` + +**Important**: The Request object is automatically appended as the **last argument** to the backend function. + +**`page.backend.ts`**: + +```typescript +// Called from .json as: "$record": "@GetRecord" +// Receives: (request) +function GetRecord(request: Request): any { + const id = request.params.id; // Access route params via request + return Process("models.record.Find", id); +} + +// Called from .json as: { "process": "@GetItems", "args": ["active", 20] } +// Receives: ("active", 20, request) +function GetItems(status: string, limit: number, request: Request): any[] { + return Process("models.item.Get", { + wheres: [{ column: "status", value: status }], + limit: limit, + }); +} +``` + +> **⚠️ Common Mistake**: You cannot use `$param.id` directly in backend scripts. The `$param`, `$query`, etc. variables are only available in HTML templates and `.json` configurations. In backend scripts, access these values via the `request` parameter: `request.params.id`, `request.query.search`, etc. + ## Built-in Functions ### P\_() - Process Call diff --git a/sui/docs/routing.md b/sui/docs/routing.md new file mode 100644 index 00000000..5f6826e6 --- /dev/null +++ b/sui/docs/routing.md @@ -0,0 +1,214 @@ +# Routing + +SUI supports file-system based routing with dynamic route parameters and URL rewriting. + +## File-System Routing + +Pages are organized in directories, with each directory containing a page's files: + +``` +/pages/ +├── index/ +│ ├── index.html +│ ├── index.css +│ └── index.ts +├── about/ +│ ├── about.html +│ └── about.css +└── users/ + ├── users.html + └── [id]/ # Dynamic route + ├── [id].html + ├── [id].css + └── [id].ts +``` + +## Dynamic Routes + +Use square brackets `[param]` to create dynamic route segments: + +| Directory Structure | URL Pattern | Example URL | +| ------------------- | ---------------- | -------------------- | +| `/users/[id]/` | `/users/:id` | `/users/123` | +| `/posts/[slug]/` | `/posts/:slug` | `/posts/hello-world` | +| `/[category]/[id]/` | `/:category/:id` | `/electronics/456` | + +### Accessing Route Parameters + +**In HTML templates** - Use `$param`: + +```html +

User ID: {{ $param.id }}

+

Category: {{ $param.category }}

+``` + +**In `.json` configuration**: + +```json +{ + "userId": "$param.id", + "$user": { + "process": "models.user.Find", + "args": ["$param.id"] + } +} +``` + +**In backend scripts** - Via Request object: + +```typescript +function GetRecord(request: Request): any { + const id = request.params.id; + return Process("models.record.Find", id); +} +``` + +> **Note**: `$param` is NOT available as a global variable in backend scripts. You must access route parameters through the `request.params` object. + +## URL Rewriting + +SUI pages require URL rewriting to map clean URLs to `.sui` page files. Configure rewrite rules in `app.yao`: + +```json +{ + "public": { + "rewrite": [ + { "^\\/assets\\/(.*)$": "/assets/$1" }, + { "^\\/users\\/([^\\/]+)$": "/users/[id].sui" }, + { "^\\/(.*)$": "/$1.sui" } + ] + } +} +``` + +### Rewrite Rule Syntax + +Each rule is a JSON object with a regex pattern as the key and the target path as the value: + +```json +{ "REGEX_PATTERN": "TARGET_PATH" } +``` + +- **REGEX_PATTERN**: A regular expression to match the incoming URL +- **TARGET_PATH**: The internal path to route to, can use capture groups (`$1`, `$2`, etc.) + +### Rule Processing Order + +Rules are processed **in order from top to bottom**. The first matching rule wins. Always place more specific rules before general ones. + +### Common Patterns + +#### Static Assets (Passthrough) + +```json +{ "^\\/assets\\/(.*)$": "/assets/$1" } +``` + +Passes asset requests directly without modification. + +#### Simple Dynamic Route + +```json +{ "^\\/users\\/([^\\/]+)$": "/users/[id].sui" } +``` + +Maps `/users/123` to `/users/[id].sui`, making `123` available as `$param.id`. + +#### Nested Dynamic Route + +```json +{ + "^\\/users\\/([^\\/]+)\\/posts\\/([^\\/]+)$": "/users/[id]/posts/[postId].sui" +} +``` + +Maps `/users/123/posts/456` to the nested page, with `$param.id = "123"` and `$param.postId = "456"`. + +#### Catch-All for SUI Pages + +```json +{ "^\\/(.*)$": "/$1.sui" } +``` + +Maps any URL to its corresponding `.sui` file. Place this **last** as a fallback. + +#### Specific Page Override + +```json +{ "^\\/dashboard\\/login(.*)$": "/dashboard/login.sui" }, +{ "^\\/dashboard\\/(.*)$": "/dashboard/[id].sui" } +``` + +The login page is matched first (specific), then other dashboard pages use dynamic routing. + +### Complete Example + +```json +{ + "public": { + "rewrite": [ + // Static assets - passthrough + { "^\\/assets\\/(.*)$": "/assets/$1" }, + { "^\\/images\\/(.*)$": "/images/$1" }, + + // Specific pages (before dynamic routes) + { "^\\/blog\\/new$": "/blog/new.sui" }, + { "^\\/blog\\/([^\\/]+)\\/edit$": "/blog/[id]/edit.sui" }, + + // Dynamic routes + { "^\\/blog\\/([^\\/]+)$": "/blog/[id].sui" }, + { + "^\\/users\\/([^\\/]+)\\/posts\\/([^\\/]+)$": "/users/[id]/posts/[postId].sui" + }, + { "^\\/users\\/([^\\/]+)$": "/users/[id].sui" }, + + // Fallback - must be last + { "^\\/(.*)$": "/$1.sui" } + ] + } +} +``` + +### Regex Tips + +| Pattern | Matches | Description | +| ----------- | ------------------ | ------------------------------------ | +| `([^\\/]+)` | Any segment | Matches characters until next `/` | +| `(.*)` | Everything | Matches any characters including `/` | +| `(\\d+)` | Numbers only | Matches numeric IDs | +| `([a-z-]+)` | Lowercase + hyphen | Matches slugs like `hello-world` | + +### Debugging Rewrite Rules + +1. Check the server logs for route matching information +2. Ensure regex escaping is correct (double backslashes in JSON: `\\/` for `/`) +3. Test specific URLs to verify capture groups work correctly +4. Remember that the `.sui` extension is internal - users access pages without it + +## Route Parameters in Different Contexts + +| Context | Access Method | Example | +| --------------- | ------------------- | ------------------------------- | +| HTML Template | `{{ $param.id }}` | `

{{ $param.id }}

` | +| `.json` Config | `"$param.id"` | `"userId": "$param.id"` | +| Backend Script | `request.params.id` | `const id = request.params.id;` | +| Frontend Script | Read from DOM | `document.body.dataset.id` | + +### Frontend Access Pattern + +Since frontend scripts run in the browser, route params aren't directly available. Pass them via data attributes: + +**HTML**: + +```html +
+ +
+``` + +**Frontend TypeScript**: + +```typescript +const pageEl = document.getElementById("page"); +const id = pageEl?.dataset.id; +```