yao/sui/docs/template-syntax.md
Max 097fb73416 Refactor SUI Command Arguments and Enhance Default Template Handling
- Updated the `build` and `watch` commands to accept a single argument for `<sui>` and made the `<template>` argument optional, improving usability.
- Introduced a default template assignment for the `agent` SUI, ensuring a more intuitive setup for users.
- Enhanced error messages for command usage to provide clearer guidance on expected input format.
- Added new OpenAPI file to the bindata, improving the framework's capabilities for API integration.
2026-01-01 10:28:45 +08:00

292 lines
9.2 KiB
Markdown

# Template Syntax
SUI uses a simple template syntax for data binding, conditional rendering, and list iteration.
## Data Interpolation
Use double curly braces `{{ }}` to output data:
```html
<!-- Variable binding -->
<span>{{ name }}</span>
<span>{{ user.email }}</span>
<span>{{ items[0].title }}</span>
<!-- Default values (null coalescing) -->
<span>{{ title ?? 'Default Title' }}</span>
<span>{{ user.name ?? 'Anonymous' }}</span>
<!-- Expressions -->
<span>{{ price * quantity }}</span>
<span>{{ firstName + ' ' + lastName }}</span>
<span>{{ count > 0 ? 'Has items' : 'Empty' }}</span>
```
## Conditional Rendering
### Basic If
```html
<div s:if="{{ isActive }}">Active</div>
<div s:if="{{ count > 0 }}">Has items</div>
<div s:if="{{ user != null }}">Logged in</div>
```
### If-Elif-Else
```html
<div s:if="{{ status == 'active' }}">Active</div>
<div s:elif="{{ status == 'pending' }}">Pending</div>
<div s:elif="{{ status == 'suspended' }}">Suspended</div>
<div s:else>Unknown</div>
```
### Comparison Operators
| Operator | Description |
| -------- | --------------------- |
| `==` | Equal |
| `!=` | Not equal |
| `>` | Greater than |
| `<` | Less than |
| `>=` | Greater than or equal |
| `<=` | Less than or equal |
| `&&` | Logical AND |
| `\|\|` | Logical OR |
| `!` | Logical NOT |
### Examples
```html
<!-- Multiple conditions -->
<div s:if="{{ isAdmin && isActive }}">Admin Panel</div>
<div s:if="{{ age >= 18 || hasPermission }}">Access Granted</div>
<!-- Negation -->
<div s:if="{{ !isLoading }}">Content loaded</div>
<!-- Null checks -->
<div s:if="{{ user != null && user.verified }}">Verified User</div>
```
## List Rendering
### Basic Loop
```html
<ul>
<li s:for="{{ items }}" s:for-item="item">{{ item.name }}</li>
</ul>
```
### With Index
```html
<ul>
<li s:for="{{ items }}" s:for-item="item" s:for-index="index">
{{ index + 1 }}. {{ item.name }}
</li>
</ul>
```
### Nested Loops
```html
<div s:for="{{ categories }}" s:for-item="category">
<h3>{{ category.name }}</h3>
<ul>
<li s:for="{{ category.items }}" s:for-item="item">{{ item.title }}</li>
</ul>
</div>
```
### Loop with Conditional
```html
<div s:for="{{ users }}" s:for-item="user" s:if="{{ user.active }}">
{{ user.name }}
</div>
```
### Object Iteration
```html
<dl s:for="{{ settings }}" s:for-item="value" s:for-index="key">
<dt>{{ key }}</dt>
<dd>{{ value }}</dd>
</dl>
```
## Variable Assignment
Use `<s:set>` to define variables:
```html
<!-- Simple assignment -->
<s:set name="total" value="{{ price * quantity }}" />
<span>Total: {{ total }}</span>
<!-- Computed values -->
<s:set name="fullName" value="{{ firstName + ' ' + lastName }}" />
<s:set name="isExpensive" value="{{ price > 100 }}" />
<!-- From expressions -->
<s:set name="discountedPrice" value="{{ price * (1 - discount / 100) }}" />
```
## Attribute Binding
### Dynamic Attributes
```html
<input value="{{ formData.email }}" />
<a href="{{ '/user/' + userId }}">Profile</a>
<img src="{{ imageUrl }}" alt="{{ imageAlt }}" />
```
### Conditional Attributes
```html
<!-- Attribute with condition -->
<button s:attr-disabled="{{ !isValid }}">Submit</button>
<input s:attr-readonly="{{ isLocked }}" />
<div s:attr-hidden="{{ !showPanel }}">Panel</div>
<!-- Class binding -->
<div class="base {{ isActive ? 'active' : '' }}">Content</div>
```
### Spread Attributes
```html
<!-- Spread object as attributes -->
<div ...props></div>
<input ...inputAttrs />
```
## Raw HTML Output
By default, output is HTML-escaped. Use `s:raw` for raw HTML:
```html
<!-- Escaped (safe) -->
<div>{{ htmlContent }}</div>
<!-- Raw HTML (use with caution) -->
<div s:raw="true">{{ htmlContent }}</div>
```
## Expression Engine
SUI uses [Expr](https://expr-lang.org/) (v1.17) as the expression engine. Expr provides a powerful expression language with operators, functions, and more.
### SUI Custom Functions
| Function | Description | Example |
| --------------- | ------------------------------ | --------------------------------- |
| `P_(proc, ...)` | Call a Yao process | `{{ P_('models.user.Find', 1) }}` |
| `True(value)` | Check if value is truthy | `{{ True(user) }}` |
| `False(value)` | Check if value is falsy | `{{ False(error) }}` |
| `Empty(value)` | Check if array/object is empty | `{{ Empty(items) }}` |
### Expr Built-in Functions
Expr provides many built-in functions. Here are commonly used ones:
| Function | Description | Example |
| --------------------- | ------------------------------ | --------------------------------- |
| `len(array)` | Get length of array/string/map | `{{ len(items) }}` |
| `all(array, pred)` | Check if all elements match | `{{ all(users, .active) }}` |
| `any(array, pred)` | Check if any element matches | `{{ any(items, .price > 100) }}` |
| `one(array, pred)` | Check if exactly one matches | `{{ one(users, .admin) }}` |
| `none(array, pred)` | Check if no elements match | `{{ none(items, .deleted) }}` |
| `map(array, mapper)` | Transform array elements | `{{ map(users, .name) }}` |
| `filter(array, pred)` | Filter array by predicate | `{{ filter(items, .active) }}` |
| `find(array, pred)` | Find first matching element | `{{ find(users, .id == 1) }}` |
| `count(array, pred)` | Count matching elements | `{{ count(items, .price > 50) }}` |
| `sum(array)` | Sum of array elements | `{{ sum(prices) }}` |
| `mean(array)` | Average of array elements | `{{ mean(scores) }}` |
| `min(array)` | Minimum value | `{{ min(prices) }}` |
| `max(array)` | Maximum value | `{{ max(scores) }}` |
| `first(array)` | First element | `{{ first(items) }}` |
| `last(array)` | Last element | `{{ last(items) }}` |
| `take(array, n)` | Take first n elements | `{{ take(items, 5) }}` |
| `keys(map)` | Get map keys | `{{ keys(settings) }}` |
| `values(map)` | Get map values | `{{ values(settings) }}` |
| `contains(a, b)` | Check if a contains b | `{{ contains(name, 'test') }}` |
| `startsWith(s, pre)` | Check string prefix | `{{ startsWith(url, 'https') }}` |
| `endsWith(s, suf)` | Check string suffix | `{{ endsWith(file, '.pdf') }}` |
| `upper(s)` | Uppercase string | `{{ upper(name) }}` |
| `lower(s)` | Lowercase string | `{{ lower(email) }}` |
| `trim(s)` | Trim whitespace | `{{ trim(input) }}` |
| `split(s, sep)` | Split string | `{{ split(tags, ',') }}` |
| `join(array, sep)` | Join array to string | `{{ join(names, ', ') }}` |
| `int(v)` | Convert to integer | `{{ int(value) }}` |
| `float(v)` | Convert to float | `{{ float(value) }}` |
| `string(v)` | Convert to string | `{{ string(count) }}` |
| `now()` | Current time | `{{ now() }}` |
| `date(s)` | Parse date string | `{{ date('2024-01-01') }}` |
| `duration(s)` | Parse duration string | `{{ duration('1h30m') }}` |
For the complete list of built-in functions and operators, see the [Expr Language Definition](https://expr-lang.org/docs/language-definition).
### Examples
```html
<!-- SUI custom functions -->
<div s:if="{{ Empty(users) }}">No users found</div>
<span>{{ P_('utils.formatDate', createdAt) }}</span>
<!-- Array operations -->
<span>Total: {{ len(items) }} items</span>
<span>Active: {{ count(users, .active) }}</span>
<span>Sum: {{ sum(map(items, .price)) }}</span>
<!-- String operations -->
<span>{{ upper(first(split(name, ' '))) }}</span>
<!-- Filtering -->
<div s:for="{{ filter(items, .price > 100) }}" s:for-item="item">
{{ item.name }}
</div>
```
## String Operations
```html
<!-- Concatenation -->
<span>{{ 'Hello, ' + name + '!' }}</span>
<!-- Template literals (in expressions) -->
<a href="{{ '/users/' + userId + '/edit' }}">Edit</a>
```
## Arithmetic Operations
```html
<!-- Basic math -->
<span>{{ price * quantity }}</span>
<span>{{ total / count }}</span>
<span>{{ value + 10 }}</span>
<span>{{ index - 1 }}</span>
<!-- Percentage -->
<span>{{ (completed / total) * 100 }}%</span>
```
## Comments
HTML comments are preserved in output:
```html
<!-- This comment appears in output -->
```
## Whitespace Control
SUI preserves whitespace by default. For minified output, use build options:
```bash
yao sui build <sui> <template> # Minified (production)
yao sui build <sui> <template> -D # Preserved (development, --debug)
```