@ea-lab/reactive-json-docs
Version:
Complete documentation for Reactive-JSON - Components, examples and LLM-parsable guides
416 lines (372 loc) • 16.1 kB
YAML
renderView:
- type: Markdown
content: |
# Editable Modal in Row Template
This guide demonstrates a pattern for creating editable modals within row templates, allowing users to edit individual items in a list without complex state management or custom handlers.
## Core Concept
By placing a modal inside a row template (using `Switch`), each row gets its own modal instance with direct access to the row's data via the `~.` notation. This simplifies data flow and eliminates the need for complex index tracking or custom save handlers.
- type: Markdown
content: |
## Key Benefits
1. **Simplified data access**: Modal has direct access to row data via `~.` notation
2. **No index tracking**: No need to find array indices or store file IDs
3. **Automatic isolation**: Each row's modal is isolated from others
4. **Simple save pattern**: Use basic `setData` reactions to copy editable values back to originals
- type: Markdown
content: |
## Prerequisites
Before implementing this pattern, you need:
1. **A Reactive-JSON Modal component**: This pattern requires a Modal component implementation that supports:
- `showBoolPath` property (or equivalent) to control visibility via data path
- `headerTitle` property for the modal header
- `body` property for the modal content
- Integration with Reactive-JSON's action system
You can use a custom Modal component or one from an integration package (like `@ea-lab/reactive-json-bootstrap`), but it must be compatible with Reactive-JSON's component system.
- type: Markdown
content: |
## Pattern Structure
### 1. Modal Inside Row Template
Place the modal component inside your row template so each rendered row has its own modal instance.
- type: SyntaxHighlighter
language: yaml
title: "Modal Inside Row Template"
content: |
templates:
itemRow:
- type: tr
content:
# Row content (cells, buttons, etc.)
- type: button
content: "Edit"
actions:
- what: setData
on: click
path: ~.showModal
value: true
# Modal inside the row
- type: ReactiveJsonSubroot
sharedUpdates: true
rjOptions:
rjBuildUrl: "/components/EditModal.yaml"
dataOverride:
showModal: ~.showModal
# ... other data
- type: Markdown
content: |
### 2. Separate Editable Values
Use separate fields for editable copies and original values.
- type: SyntaxHighlighter
language: yaml
title: "Data Override Structure"
content: |
dataOverride:
# Original values (read-only display)
id: ~.id
name: ~.name
# Editable copies (for form fields)
editable_name: ~.editable.name
editable_description: ~.editable.description
# Original values (for saving back)
name: ~.name
description: ~.description
- type: Markdown
content: |
### 3. Initialize Editable Values
When opening the modal, copy original values to editable fields.
- type: SyntaxHighlighter
language: yaml
title: "Initialize Editable Values"
content: |
- type: button
content: "Edit"
actions:
- what: setData
on: click
path: ~.editable.name
value: ~.name
- what: setData
on: click
path: ~.editable.description
value: ~.description
- what: setData
on: click
path: ~.showModal
value: true
- type: Markdown
content: |
### 4. Save Pattern
Use simple `setData` reactions to copy editable values back to originals.
- type: SyntaxHighlighter
language: yaml
title: "Save Button Actions"
content: |
- type: button
content: "Save"
actions:
- what: setData
on: click
path: ~.name
value: ~.editable_name
- what: setData
on: click
path: ~.description
value: ~.editable_description
- what: setData
on: click
path: ~.showModal
value: false
- type: RjBuildDescriber
title: "Complete Example: Editable Item List"
description: |
A complete example showing a table with editable rows. Each row has an "Edit" button that opens a modal. The modal allows editing the item's name and description, with Save and Cancel buttons.
toDescribe:
renderView:
- type: div
attributes:
class: "p-4"
content:
- type: h2
attributes:
class: "text-2xl font-bold mb-4"
content: "Items List"
- type: table
attributes:
class: "w-full border-collapse border border-gray-300"
content:
- type: thead
content:
- type: tr
content:
- type: th
attributes:
class: "border border-gray-300 p-2 bg-gray-100"
content: "ID"
- type: th
attributes:
class: "border border-gray-300 p-2 bg-gray-100"
content: "Name"
- type: th
attributes:
class: "border border-gray-300 p-2 bg-gray-100"
content: "Description"
- type: th
attributes:
class: "border border-gray-300 p-2 bg-gray-100"
content: "Actions"
- type: tbody
content:
- type: Switch
content: ~~.items
singleOption:
load: itemRow
- type: div
attributes:
class: "mt-4 p-2 bg-gray-100 rounded"
content:
- type: strong
content: "Debug - First item name: "
- type: span
content: ~~.items.0.name
templates:
itemRow:
- type: tr
content:
- type: td
attributes:
class: "border border-gray-300 p-2"
content: ~.id
- type: td
attributes:
class: "border border-gray-300 p-2"
content: ~.name
- type: td
attributes:
class: "border border-gray-300 p-2"
content: ~.description
- type: td
attributes:
class: "border border-gray-300 p-2"
content:
- type: button
attributes:
class: "bg-blue-500 text-white px-3 py-1 rounded hover:bg-blue-600"
content: "Edit"
actions:
- what: setData
on: click
path: ~.editable.name
value: ~.name
- what: setData
on: click
path: ~.editable.description
value: ~.description
- what: setData
on: click
path: ~.showModal
value: true
# Modal inside the row
- type: ReactiveJsonSubroot
sharedUpdates: true
rjOptions:
maybeRawAppRjBuild:
renderView:
- type: Modal
showBoolPath: ~.showModal
headerTitle:
type: h3
attributes:
class: "text-xl font-semibold"
content: "Edit Item"
body:
- type: div
attributes:
class: "space-y-4"
content:
- type: div
content:
- type: label
attributes:
class: "block text-sm font-medium mb-1"
content: "ID (read-only):"
- type: div
attributes:
class: "p-2 bg-gray-100 rounded"
content: ~.id
- type: div
content:
- type: label
attributes:
class: "block text-sm font-medium mb-1"
content: "Name:"
- type: TextField
dataLocation: ~.editable_name
forceWrapper: false
inputAttributes:
class: "w-full p-2 border border-gray-300 rounded"
- type: div
content:
- type: label
attributes:
class: "block text-sm font-medium mb-1"
content: "Description:"
- type: TextAreaField
dataLocation: ~.editable_description
forceWrapper: false
rows: 3
inputAttributes:
class: "w-full p-2 border border-gray-300 rounded"
- type: div
attributes:
class: "flex justify-end gap-2 mt-4 pt-4 border-t border-gray-200 dark:border-gray-700"
content:
- type: button
attributes:
class: "px-4 py-2 bg-gray-300 rounded hover:bg-gray-400"
content: "Cancel"
actions:
- what: setData
on: click
path: ~.showModal
value: false
- type: button
attributes:
class: "px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
content: "Save"
actions:
- what: setData
on: click
path: ~.name
value: ~.editable_name
- what: setData
on: click
path: ~.description
value: ~.editable_description
- what: setData
on: click
path: ~.showModal
value: false
data:
showModal: false
id: ""
name: ""
editable_name: ""
editable_description: ""
description: ""
dataOverride:
showModal: ~.showModal
id: ~.id
name: ~.name
editable_name: ~.editable.name
editable_description: ~.editable.description
description: ~.description
data:
items:
- id: 1
name: "First Item"
description: "This is the first item's description"
showModal: false
editable:
name: ""
description: ""
- id: 2
name: "Second Item"
description: "This is the second item's description"
showModal: false
editable:
name: ""
description: ""
- id: 3
name: "Third Item"
description: "This is the third item's description"
showModal: false
editable:
name: ""
description: ""
- type: Markdown
content: |
## How It Works
1. **Row Context**: Each row rendered by `Switch` has its own template context, accessible via `~.`
2. **Modal Access**: The modal inside the row inherits this context, so `~.name` refers to the current row's name
3. **Editable Copies**: Form fields bind to `~.editable_name` (or `~.editable.name`), keeping edits separate from originals
4. **Save Action**: When saving, `setData` with `path: ~.name` and `value: ~.editable_name` copies the edited value back to the original
5. **Shared Updates**: With `sharedUpdates: true`, changes to `~.name` in the modal propagate back to the row's data
- type: Markdown
content: |
## Advantages Over Alternative Approaches
### ❌ Complex Approach (Not Recommended)
- Store file index globally
- Use custom handlers to find and update array items
- Requires complex path construction with array indices
### ✅ Simple Approach (This Pattern)
- Modal is in row context - direct access via `~.`
- No index tracking needed
- Simple `setData` actions copy values back
- Works seamlessly with `sharedUpdates`
- type: Markdown
content: |
## Best Practices
1. **Use `sharedUpdates: true`**: Enables automatic data synchronization between modal and row
2. **Separate editable fields**: Keep editable copies separate from originals for clean cancel behavior
3. **Initialize on open**: Copy original values to editable fields when opening the modal
4. **Simple save pattern**: Use straightforward `setData` actions to copy editable values back
5. **Row-scoped modals**: Place modals inside row templates to leverage template context
- type: Markdown
content: |
## Use Cases
- **Data tables**: Edit individual rows without affecting others
- **List items**: Modify properties of items in a list
- **Card grids**: Edit cards in a grid layout
- **Nested data**: Edit nested objects within arrays
- type: Markdown
content: |
## Limitations
- Each row renders its own modal instance (may impact performance with very large lists)
- Modal state is tied to row data (if row is removed, modal state is lost)
- Not suitable for modals that need to persist across row re-renders
- type: Markdown
content: |
## Related Patterns
- **ReactiveJsonSubroot**: Component used to embed the modal
- **Switch**: Component used to render rows with templates
- **setData**: Reaction used to save edited values
- **sharedUpdates**: Feature enabling bidirectional data sync