DOM Manipulation
Update the browser's DOM directly from your controllers.
Overview
Gale can send HTML from your server and intelligently update the DOM. You have complete control over:
- What to send — Blade views, fragments, or raw HTML
- Where to update — Target specific elements with CSS selectors
- How to update — Choose from 8 different update modes
Alpine State Preservation
Gale uses Alpine's morphing algorithm by default, which intelligently updates the DOM while preserving Alpine component state, focus, and scroll position.
Rendering Views
Basic View Rendering
Render a Blade view and send it to the browser:
1return gale()->view('partials.user-card', ['user' => $user]);
return gale()->view('partials.user-card', ['user' => $user]);
Targeting Specific Elements
Use a selector to target specific elements:
1return gale()->view('partials.user-card', ['user' => $user], [
2 'selector' => '#user-profile',
3]);
return gale()->view('partials.user-card', ['user' => $user], [
'selector' => '#user-profile',
]);
View Options
1return gale()->view('partials.list-item', ['item' => $item], [
2 'selector' => '#items-list', // CSS selector for target element
3 'mode' => 'append', // How to apply the update
4 'useViewTransition' => true, // Use View Transitions API
5 'settle' => 100, // Delay in ms for CSS transitions
6 'limit' => 5, // Max elements to update
7]);
return gale()->view('partials.list-item', ['item' => $item], [
'selector' => '#items-list', // CSS selector for target element
'mode' => 'append', // How to apply the update
'useViewTransition' => true, // Use View Transitions API
'settle' => 100, // Delay in ms for CSS transitions
'limit' => 5, // Max elements to update
]);
Raw HTML
For simple updates, send raw HTML strings:
1return gale()->html('<span class="badge">New!</span>', [
2 'selector' => '#notification-badge',
3 'mode' => 'replace',
4]);
return gale()->html('<span class="badge">New!</span>', [
'selector' => '#notification-badge',
'mode' => 'replace',
]);
Escape User Input
e() or use Blade views to prevent XSS attacks.
DOM Update Modes
Gale supports 8 different modes for updating the DOM:
| Mode | Behavior | Alpine State |
|---|---|---|
morph (outer) |
Replace element with intelligent diffing | Preserved |
morph_inner (inner) |
Replace children only, keep parent | Preserved |
replace |
Quick replacement, no diffing | Lost |
append |
Add as last child | N/A (new element) |
prepend |
Add as first child | N/A (new element) |
before |
Insert before element | N/A (new element) |
after |
Insert after element | N/A (new element) |
remove |
Delete element from DOM | Removed |
Morph vs Replace
Morph uses Alpine's morphing algorithm to intelligently diff the new HTML against the existing DOM. This preserves:
- Alpine component state (
x-data) - Focus state on inputs
- Scroll position
- CSS transitions in progress
Replace is faster but destroys existing state. Use it for static content or when you want a clean slate.
Default Mode
morph. You rarely need to change it unless you have specific requirements.
Convenience Methods
Gale provides shorthand methods for common operations:
append() / prepend()
1// Add item to end of list
2return gale()->append('#todo-list', view('partials.todo-item', ['todo' => $todo]));
3
4// Add item to beginning of list
5return gale()->prepend('#notifications', '<div class="notification">New message</div>');
// Add item to end of list
return gale()->append('#todo-list', view('partials.todo-item', ['todo' => $todo]));
// Add item to beginning of list
return gale()->prepend('#notifications', '<div class="notification">New message</div>');
before() / after()
1// Insert before an element
2return gale()->before('#item-5', view('partials.item', ['item' => $newItem]));
3
4// Insert after an element
5return gale()->after('.header', '<div class="alert">Important notice</div>');
// Insert before an element
return gale()->before('#item-5', view('partials.item', ['item' => $newItem]));
// Insert after an element
return gale()->after('.header', '<div class="alert">Important notice</div>');
inner() / outer()
1// Replace only the inner content (uses morph_inner mode)
2return gale()->inner('#container', view('partials.content', $data));
3
4// Replace the entire element including itself (uses morph mode)
5return gale()->outer('#old-element', view('partials.new-element', $data));
// Replace only the inner content (uses morph_inner mode)
return gale()->inner('#container', view('partials.content', $data));
// Replace the entire element including itself (uses morph mode)
return gale()->outer('#old-element', view('partials.new-element', $data));
replace() / remove()
1// Quick replacement (no morphing)
2return gale()->replace('#status', '<span class="online">Online</span>');
3
4// Remove element from DOM
5return gale()->remove('#deleted-item-' . $id);
// Quick replacement (no morphing)
return gale()->replace('#status', '<span class="online">Online</span>');
// Remove element from DOM
return gale()->remove('#deleted-item-' . $id);
Multiple Updates
Chain multiple DOM operations in a single response:
1return gale()
2 ->state('loading', false)
3 ->view('partials.user-list', compact('users'), [
4 'selector' => '#user-list',
5 ])
6 ->append('#activity-log', view('partials.log-entry', [
7 'action' => 'Users loaded',
8 ]))
9 ->remove('#loading-spinner');
return gale()
->state('loading', false)
->view('partials.user-list', compact('users'), [
'selector' => '#user-list',
])
->append('#activity-log', view('partials.log-entry', [
'action' => 'Users loaded',
]))
->remove('#loading-spinner');
Each update is sent as a separate SSE event and applied in order.
Practical Examples
Infinite Scroll
1public function loadMore()
2{
3 $page = request()->state('page', 1);
4 $items = Item::paginate(10, ['*'], 'page', $page);
5
6 return gale()
7 ->state('page', $page + 1)
8 ->state('hasMore', $items->hasMorePages())
9 ->append('#items-container', view('partials.items', compact('items')));
10}
public function loadMore()
{
$page = request()->state('page', 1);
$items = Item::paginate(10, ['*'], 'page', $page);
return gale()
->state('page', $page + 1)
->state('hasMore', $items->hasMorePages())
->append('#items-container', view('partials.items', compact('items')));
}
Toast Notification
1public function save()
2{
3 // ... save logic ...
4
5 return gale()
6 ->state('saved', true)
7 ->append('#toast-container', view('partials.toast', [
8 'message' => 'Changes saved successfully!',
9 'type' => 'success',
10 ]));
11}
public function save()
{
// ... save logic ...
return gale()
->state('saved', true)
->append('#toast-container', view('partials.toast', [
'message' => 'Changes saved successfully!',
'type' => 'success',
]));
}
Delete Item
1public function destroy(Item $item)
2{
3 $item->delete();
4
5 return gale()
6 ->state('count', Item::count())
7 ->remove('#item-' . $item->id);
8}
public function destroy(Item $item)
{
$item->delete();
return gale()
->state('count', Item::count())
->remove('#item-' . $item->id);
}
Quick Reference
| Method | Description |
|---|---|
view($view, $data, $options) |
Render Blade view and send to DOM |
html($html, $options) |
Send raw HTML to DOM |
append($selector, $html) |
Append as last child |
prepend($selector, $html) |
Prepend as first child |
before($selector, $html) |
Insert before element |
after($selector, $html) |
Insert after element |
inner($selector, $html) |
Replace inner content (morph_inner) |
outer($selector, $html) |
Replace entire element (morph) |
replace($selector, $html) |
Quick replacement (no morph) |
remove($selector) |
Remove element from DOM |