Add skills documentation and misc updates
Add form value persistence across cache revalidation re-renders 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
254
docs/skills/modals/SKILL.md
Executable file
254
docs/skills/modals/SKILL.md
Executable file
@@ -0,0 +1,254 @@
|
||||
---
|
||||
name: modals
|
||||
description: Creating modal dialogs in RSX including alerts, confirms, prompts, selects, and form modals. Use when implementing Modal.alert, Modal.confirm, Modal.prompt, Modal.form, creating modal classes extending Modal_Abstract, or building dialog-based user interactions.
|
||||
---
|
||||
|
||||
# RSX Modal System
|
||||
|
||||
## Built-in Dialog Types
|
||||
|
||||
All modal methods are async and return appropriate values:
|
||||
|
||||
| Method | Returns | Description |
|
||||
|--------|---------|-------------|
|
||||
| `Modal.alert(body)` | `void` | Simple notification |
|
||||
| `Modal.alert(title, body, buttonLabel?)` | `void` | Alert with title |
|
||||
| `Modal.confirm(body)` | `boolean` | Yes/no confirmation |
|
||||
| `Modal.confirm(title, body, confirmLabel?, cancelLabel?)` | `boolean` | Confirmation with labels |
|
||||
| `Modal.prompt(body)` | `string\|false` | Text input |
|
||||
| `Modal.prompt(title, body, default?, multiline?)` | `string\|false` | Prompt with options |
|
||||
| `Modal.select(body, options)` | `string\|false` | Dropdown selection |
|
||||
| `Modal.select(title, body, options, default?, placeholder?)` | `string\|false` | Select with options |
|
||||
| `Modal.error(error, title?)` | `void` | Error with red styling |
|
||||
| `Modal.unclosable(title, body)` | `void` | Modal user cannot close |
|
||||
|
||||
## Basic Usage Examples
|
||||
|
||||
```javascript
|
||||
// Simple alert
|
||||
await Modal.alert("File saved successfully");
|
||||
|
||||
// Alert with title
|
||||
await Modal.alert("Success", "Your changes have been saved.");
|
||||
|
||||
// Confirmation
|
||||
if (await Modal.confirm("Are you sure you want to delete this item?")) {
|
||||
await Controller.delete(id);
|
||||
}
|
||||
|
||||
// Confirmation with custom labels
|
||||
const confirmed = await Modal.confirm(
|
||||
"Delete Project",
|
||||
"This will permanently delete the project.\n\nThis action cannot be undone.",
|
||||
"Delete", // confirm button label
|
||||
"Keep Project" // cancel button label
|
||||
);
|
||||
|
||||
// Text prompt
|
||||
const name = await Modal.prompt("Enter your name:");
|
||||
if (name) {
|
||||
// User entered something
|
||||
}
|
||||
|
||||
// Multiline prompt
|
||||
const notes = await Modal.prompt("Notes", "Enter description:", "", true);
|
||||
|
||||
// Selection dropdown
|
||||
const choice = await Modal.select("Choose an option:", [
|
||||
{value: 'a', label: 'Option A'},
|
||||
{value: 'b', label: 'Option B'}
|
||||
]);
|
||||
|
||||
// Unclosable modal (for critical operations)
|
||||
Modal.unclosable("Processing", "Please wait...");
|
||||
await long_operation();
|
||||
await Modal.close(); // Must close programmatically
|
||||
```
|
||||
|
||||
**Text formatting**: Use `\n\n` for paragraph breaks in modal body text.
|
||||
|
||||
---
|
||||
|
||||
## Form Modals
|
||||
|
||||
For complex data entry, use `Modal.form()`:
|
||||
|
||||
```javascript
|
||||
const result = await Modal.form({
|
||||
title: "Edit User",
|
||||
component: "User_Form", // Component name (must implement vals())
|
||||
component_args: {data: user}, // Args passed to component
|
||||
max_width: 800, // Width in pixels (default: 800)
|
||||
on_submit: async (form) => {
|
||||
const response = await User_Controller.save(form.vals());
|
||||
if (response.errors) {
|
||||
Form_Utils.apply_form_errors(form.$, response.errors);
|
||||
return false; // Keep modal open
|
||||
}
|
||||
return response.data; // Close modal and return data
|
||||
}
|
||||
});
|
||||
|
||||
if (result) {
|
||||
// Modal closed with data
|
||||
console.log(result.id);
|
||||
}
|
||||
```
|
||||
|
||||
### Form Component Requirements
|
||||
|
||||
The component used in `Modal.form()` must:
|
||||
1. Implement `vals()` method (get/set form values)
|
||||
2. Include `<div $sid="error_container"></div>` for validation errors
|
||||
|
||||
```javascript
|
||||
class User_Form extends Component {
|
||||
vals(values) {
|
||||
if (values) {
|
||||
this.$sid('name').val(values.name || '');
|
||||
this.$sid('email').val(values.email || '');
|
||||
return null;
|
||||
} else {
|
||||
return {
|
||||
name: this.$sid('name').val(),
|
||||
email: this.$sid('email').val()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Modal Classes (Reusable Modals)
|
||||
|
||||
For complex or frequently-used modals, create dedicated classes:
|
||||
|
||||
```javascript
|
||||
class Add_User_Modal extends Modal_Abstract {
|
||||
static async show(initial_data = {}) {
|
||||
return await Modal.form({
|
||||
title: 'Add User',
|
||||
component: 'User_Form',
|
||||
component_args: {data: initial_data},
|
||||
on_submit: async (form) => {
|
||||
const response = await User_Controller.create(form.vals());
|
||||
if (response.errors) {
|
||||
Form_Utils.apply_form_errors(form.$, response.errors);
|
||||
return false;
|
||||
}
|
||||
return response.data;
|
||||
}
|
||||
}) || false;
|
||||
}
|
||||
}
|
||||
|
||||
class Edit_User_Modal extends Modal_Abstract {
|
||||
static async show(user_id) {
|
||||
// Load data first
|
||||
const user = await User_Model.fetch(user_id);
|
||||
return await Modal.form({
|
||||
title: 'Edit User',
|
||||
component: 'User_Form',
|
||||
component_args: {data: user},
|
||||
on_submit: async (form) => {
|
||||
const response = await User_Controller.update(form.vals());
|
||||
if (response.errors) {
|
||||
Form_Utils.apply_form_errors(form.$, response.errors);
|
||||
return false;
|
||||
}
|
||||
return response.data;
|
||||
}
|
||||
}) || false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Usage Pattern
|
||||
|
||||
```javascript
|
||||
// Create new user
|
||||
const new_user = await Add_User_Modal.show();
|
||||
if (new_user) {
|
||||
grid.reload();
|
||||
}
|
||||
|
||||
// Edit existing user
|
||||
const updated_user = await Edit_User_Modal.show(user_id);
|
||||
if (updated_user) {
|
||||
// Refresh display
|
||||
}
|
||||
|
||||
// Chain modals (page JS orchestrates, not modals)
|
||||
const user = await Add_User_Modal.show();
|
||||
if (user) {
|
||||
await Assign_Role_Modal.show(user.id);
|
||||
}
|
||||
```
|
||||
|
||||
**Pattern**: Extend `Modal_Abstract`, implement static `show()`, return data or `false`.
|
||||
|
||||
---
|
||||
|
||||
## Modal Options
|
||||
|
||||
Options for `Modal.form()`:
|
||||
|
||||
```javascript
|
||||
await Modal.form({
|
||||
title: "Form Title",
|
||||
component: "Form_Component",
|
||||
component_args: {},
|
||||
max_width: 800, // Width in pixels (default: 800)
|
||||
closable: true, // Allow ESC/backdrop/X to close (default: true)
|
||||
submit_label: "Save", // Submit button text
|
||||
cancel_label: "Cancel", // Cancel button text
|
||||
on_submit: async (form) => { /* ... */ }
|
||||
});
|
||||
```
|
||||
|
||||
Options for `Modal.show()` (custom modals):
|
||||
|
||||
```javascript
|
||||
await Modal.show({
|
||||
title: "Choose Action",
|
||||
body: "What would you like to do?", // String, HTML, or jQuery element
|
||||
max_width: 500, // Width in pixels
|
||||
closable: true,
|
||||
buttons: [
|
||||
{label: "Cancel", value: false, class: "btn-secondary"},
|
||||
{label: "Continue", value: true, class: "btn-primary", default: true}
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Modal Queuing
|
||||
|
||||
Multiple simultaneous modal requests are queued and shown sequentially:
|
||||
|
||||
```javascript
|
||||
// All three modals queued and shown one after another
|
||||
const p1 = Modal.alert("First");
|
||||
const p2 = Modal.alert("Second");
|
||||
const p3 = Modal.alert("Third");
|
||||
|
||||
await Promise.all([p1, p2, p3]);
|
||||
```
|
||||
|
||||
Backdrop persists across queued modals with 500ms delay between.
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use appropriate type**: `alert()` for info, `confirm()` for decisions, `form()` for complex input
|
||||
2. **Handle cancellations**: Always check for `false` return value
|
||||
3. **Modal classes don't chain**: Page JS orchestrates sequences, not modal classes
|
||||
4. **No UI updates in modals**: Page JS handles post-modal UI updates
|
||||
5. **Loading states**: Use `Modal.unclosable()` + `Modal.close()` for long operations
|
||||
|
||||
## More Information
|
||||
|
||||
Details: `php artisan rsx:man modals`
|
||||
Reference in New Issue
Block a user