Files
herolib/aiprompts/unpolly/unpolly_core.md
2025-10-25 09:44:19 +04:00

647 lines
12 KiB
Markdown

# Unpoly Quick Reference for AI Agents
## Installation
Include Unpoly from CDN in your HTML `<head>`:
```html
<script src="https://unpoly.com/unpoly.min.js"></script>
<link rel="stylesheet" href="https://unpoly.com/unpoly.min.css">
```
## Core Concept
Unpoly updates page fragments without full page reloads. Users click links/submit forms → server responds with HTML → Unpoly extracts and swaps matching fragments.
---
## 1. Following Links (Fragment Updates)
### Basic Link Following
```html
<a href="/users/5" up-follow>View User</a>
```
Updates the `<main>` element (or `<body>` if no main exists) with content from `/users/5`.
### Target Specific Fragment
```html
<a href="/users/5" up-target=".user-details">View User</a>
<div class="user-details">
<!-- Content replaced here -->
</div>
```
### Multiple Fragments
```html
<a href="/users/5" up-target=".profile, .activity">View User</a>
```
Updates both `.profile` and `.activity` from single response.
### Append/Prepend Content
```html
<!-- Append to list -->
<a href="/items?page=2" up-target=".items:after">Load More</a>
<!-- Prepend to list -->
<a href="/latest" up-target=".items:before">Show Latest</a>
```
### Handle All Links Automatically
```js
up.link.config.followSelectors.push('a[href]')
```
Now all links update fragments by default.
---
## 2. Submitting Forms
### Basic Form Submission
```html
<form action="/users" method="post" up-submit>
<input name="email">
<button type="submit">Create</button>
</form>
```
Submits via AJAX and updates `<main>` with response.
### Target Specific Fragment
```html
<form action="/search" up-submit up-target=".results">
<input name="query">
<button>Search</button>
</form>
<div class="results">
<!-- Search results appear here -->
</div>
```
### Handle Success vs. Error Responses
```html
<form action="/users" method="post" up-submit
up-target="#success"
up-fail-target="form">
<input name="email">
<button>Create</button>
</form>
<div id="success">Success message here</div>
```
- **Success (2xx status)**: Updates `#success`
- **Error (4xx/5xx status)**: Re-renders `form` with validation errors
**Server must return HTTP 422** (or similar error code) for validation failures.
---
## 3. Opening Overlays (Modal, Drawer, Popup)
### Modal Dialog
```html
<a href="/details" up-layer="new">Open Modal</a>
```
Opens `/details` in a modal overlay.
### Drawer (Sidebar)
```html
<a href="/menu" up-layer="new drawer">Open Drawer</a>
```
### Popup (Anchored to Link)
```html
<a href="/help" up-layer="new popup">Help</a>
```
### Close Overlay When Condition Met
```html
<a href="/users/new"
up-layer="new"
up-accept-location="/users/$id"
up-on-accepted="console.log('Created user:', value.id)">
New User
</a>
```
Overlay auto-closes when URL matches `/users/123`, passes `{ id: 123 }` to callback.
### Local Content (No Server Request)
```html
<a up-layer="new popup" up-content="<p>Help text here</p>">Help</a>
```
---
## 4. Validation
### Validate on Field Change
```html
<form action="/users" method="post">
<input name="email" up-validate>
<input name="password" up-validate>
<button type="submit">Register</button>
</form>
```
When field loses focus → submits form with `X-Up-Validate: email` header → server re-renders form → Unpoly updates the field's parent `<fieldset>` (or closest form group).
**Server must return HTTP 422** for validation errors.
### Validate While Typing
```html
<input name="email" up-validate
up-watch-event="input"
up-watch-delay="300">
```
Validates 300ms after user stops typing.
---
## 5. Lazy Loading & Polling
### Load When Element Appears in DOM
```html
<div id="menu" up-defer up-href="/menu">
Loading menu...
</div>
```
Immediately loads `/menu` when placeholder renders.
### Load When Scrolled Into View
```html
<div id="comments" up-defer="reveal" up-href="/comments">
Loading comments...
</div>
```
Loads when element scrolls into viewport.
### Auto-Refresh (Polling)
```html
<div class="status" up-poll up-interval="5000">
Current status
</div>
```
Reloads fragment every 5 seconds from original URL.
---
## 6. Caching & Revalidation
### Enable Caching
```html
<a href="/users" up-cache="true">Users</a>
```
Caches response, instantly shows cached content, then revalidates with server.
### Disable Caching
```html
<a href="/stock" up-cache="false">Live Prices</a>
```
### Conditional Requests (Server-Side)
Server sends:
```http
HTTP/1.1 200 OK
ETag: "abc123"
<div class="data">Content</div>
```
Next reload, Unpoly sends:
```http
GET /path
If-None-Match: "abc123"
```
Server responds `304 Not Modified` if unchanged → saves bandwidth.
---
## 7. Navigation Bar (Current Link Highlighting)
```html
<nav>
<a href="/home">Home</a>
<a href="/about">About</a>
</nav>
```
Current page link gets `.up-current` class automatically.
**Style it:**
```css
.up-current {
font-weight: bold;
color: blue;
}
```
---
## 8. Loading State
### Feedback Classes
Automatically applied:
- `.up-active` on clicked link/button
- `.up-loading` on targeted fragment
**Style them:**
```css
.up-active { opacity: 0.6; }
.up-loading { opacity: 0.8; }
```
### Disable Form While Submitting
```html
<form up-submit up-disable>
<input name="email">
<button>Submit</button>
</form>
```
All fields disabled during submission.
### Show Placeholder While Loading
```html
<a href="/data" up-target=".data"
up-placeholder="<p>Loading...</p>">
Load Data
</a>
```
---
## 9. Preloading
### Preload on Hover
```html
<a href="/users/5" up-preload>User Profile</a>
```
Starts loading when user hovers (90ms delay by default).
### Preload Immediately
```html
<a href="/menu" up-preload="insert">Menu</a>
```
Loads as soon as link appears in DOM.
---
## 10. Templates (Client-Side HTML)
### Define Template
```html
<template id="user-card">
<div class="card">
<h3>{{name}}</h3>
<p>{{email}}</p>
</div>
</template>
```
### Use Template
```html
<a up-fragment="#user-card"
up-use-data="{ name: 'Alice', email: 'alice@example.com' }">
Show User
</a>
```
**Process variables with compiler:**
```js
up.compiler('.card', function(element, data) {
element.innerHTML = element.innerHTML
.replace(/{{name}}/g, data.name)
.replace(/{{email}}/g, data.email)
})
```
---
## 11. JavaScript API
### Render Fragment
```js
up.render({
url: '/users/5',
target: '.user-details'
})
```
### Navigate (Updates History)
```js
up.navigate({
url: '/users',
target: 'main'
})
```
### Submit Form
```js
let form = document.querySelector('form')
up.submit(form)
```
### Open Overlay
```js
up.layer.open({
url: '/users/new',
onAccepted: (event) => {
console.log('User created:', event.value)
}
})
```
### Close Overlay with Value
```js
up.layer.accept({ id: 123, name: 'Alice' })
```
### Reload Fragment
```js
up.reload('.status')
```
---
## 12. Request Headers (Server Protocol)
Unpoly sends these headers with requests:
| Header | Value | Purpose |
| --------------- | -------- | ------------------------------- |
| `X-Up-Version` | `1.0.0` | Identifies Unpoly request |
| `X-Up-Target` | `.users` | Fragment selector being updated |
| `X-Up-Mode` | `modal` | Current layer mode |
| `X-Up-Validate` | `email` | Field being validated |
**Server can respond with:**
| Header | Effect |
| ------------------------ | ------------------------ |
| `X-Up-Target: .other` | Changes target selector |
| `X-Up-Accept-Layer: {}` | Closes overlay (success) |
| `X-Up-Dismiss-Layer: {}` | Closes overlay (cancel) |
---
## 13. Common Patterns
### Infinite Scrolling
```html
<div id="items">
<div>Item 1</div>
<div>Item 2</div>
</div>
<a id="next" href="/items?page=2"
up-defer="reveal"
up-target="#items:after, #next">
Load More
</a>
```
### Dependent Form Fields
```html
<form action="/order">
<!-- Changing country updates city select -->
<select name="country" up-validate="#city">
<option>USA</option>
<option>Canada</option>
</select>
<select name="city" id="city">
<option>New York</option>
</select>
</form>
```
### Confirm Before Action
```html
<a href="/delete" up-method="delete"
up-confirm="Really delete?">
Delete
</a>
```
### Auto-Submit on Change
```html
<form action="/search" up-autosubmit>
<input name="query">
</form>
```
Submits form when any field changes.
---
## 14. Error Handling
### Handle Network Errors
```js
up.on('up:fragment:offline', function(event) {
if (confirm('You are offline. Retry?')) {
event.retry()
}
})
```
### Handle Failed Responses
```js
try {
await up.render({ url: '/path', target: '.data' })
} catch (error) {
if (error instanceof up.RenderResult) {
console.log('Server error:', error)
}
}
```
---
## 15. Compilers (Enhance Elements)
### Basic Compiler
```js
up.compiler('.current-time', function(element) {
element.textContent = new Date().toString()
})
```
Runs when `.current-time` is inserted (initial load OR fragment update).
### Compiler with Cleanup
```js
up.compiler('.auto-refresh', function(element) {
let timer = setInterval(() => {
element.textContent = new Date().toString()
}, 1000)
// Return destructor function
return () => clearInterval(timer)
})
```
Destructor called when element is removed from DOM.
---
## Quick Reference Table
| Task | HTML | JavaScript |
| --------------- | ---------------------------- | -------------------------- |
| Follow link | `<a href="/path" up-follow>` | `up.follow(link)` |
| Submit form | `<form up-submit>` | `up.submit(form)` |
| Target fragment | `up-target=".foo"` | `{ target: '.foo' }` |
| Open modal | `up-layer="new"` | `up.layer.open({ url })` |
| Validate field | `up-validate` | `up.validate(field)` |
| Lazy load | `up-defer` | — |
| Poll fragment | `up-poll` | — |
| Preload link | `up-preload` | `up.link.preload(link)` |
| Local content | `up-content="<p>Hi</p>"` | `{ content: '<p>Hi</p>' }` |
| Append content | `up-target=".list:after"` | — |
| Confirm action | `up-confirm="Sure?"` | `{ confirm: 'Sure?' }` |
---
## Key Defaults
- **Target**: Updates `<main>` (or `<body>`) if no `up-target` specified
- **Caching**: Auto-enabled for GET requests during navigation
- **History**: Auto-updated when rendering `<main>` or major fragments
- **Scrolling**: Auto-scrolls to top when updating `<main>`
- **Focus**: Auto-focuses new fragment
- **Validation**: Targets field's parent `<fieldset>` or form group
---
## Best Practices for AI Agents
1. **Always provide HTTP error codes**: Return 422 for validation errors, 404 for not found, etc.
2. **Send full HTML responses**: Include entire page structure; Unpoly extracts needed fragments
3. **Use semantic HTML**: `<main>`, `<nav>`, `<form>` elements work best
4. **Set IDs on fragments**: Makes targeting easier (e.g., `<div id="user-123">`)
5. **Return consistent selectors**: If request targets `.users`, response must contain `.users`
---
## Common Mistakes to Avoid
**Don't**: Return only partial HTML without wrapper
```html
<h1>Title</h1>
<p>Content</p>
```
**Do**: Wrap in target selector
```html
<div class="content">
<h1>Title</h1>
<p>Content</p>
</div>
```
**Don't**: Return 200 OK for validation errors
**Do**: Return 422 Unprocessable Entity
**Don't**: Use `onclick="up.follow(this)"`
**Do**: Use `up-follow` attribute (handles keyboard, accessibility)
---
## Server Response Examples
### Successful Form Submission
```http
HTTP/1.1 200 OK
<div id="success">
User created successfully!
</div>
```
### Validation Error
```http
HTTP/1.1 422 Unprocessable Entity
<form action="/users" method="post" up-submit>
<input name="email" value="invalid">
<div class="error">Email is invalid</div>
<button>Submit</button>
</form>
```
### Partial Response (Optimized)
```http
HTTP/1.1 200 OK
Vary: X-Up-Target
<div class="user-details">
<!-- Only the targeted fragment -->
</div>
```