Visual Editing Setup
Visual editing allows authorized users to click on frontend content and edit it inline. This guide shows you how to set it up for your sections.
Prerequisites
Section titled “Prerequisites”-
Install the visual editing package:
Terminal window composer require franc014/ve-filament-cms-livewire -
Run the installer:
Terminal window php artisan ve-filament-cms-livewire:install
How Visual Editing Works
Section titled “How Visual Editing Works”graph TD
A[User clicks Edit button] -->|toggle| B[EditingMode activates]
B -->|page reload| C[DOM instruments data-cms-source]
C -->|user clicks element| D[VisualEditor opens modal]
D -->|loads| E[InlineEditForm]
E -->|saves| F[Database updates]
F -->|emits| G[contentUpdated event]
G -->|refreshes| H[Livewire Component]
Step 1: Add the Trait and Implement resolveFieldValue()
Section titled “Step 1: Add the Trait and Implement resolveFieldValue()”Add InteractsWithVisualEditing from ve-filament-cms-livewire and implement resolveFieldValue():
namespace App\Livewire;
use JFA\FilamentCMSCore\CMS\SectionContent;use JFA\FilamentCMSLivewire\Livewire\SectionComponent;use JFA\VeFilamentCMSLivewire\Concerns\InteractsWithVisualEditing;
class Hero extends SectionComponent{ use InteractsWithVisualEditing;
public string $label = ''; public string $headline = '';
protected function hydrateFromContent(SectionContent $content): void { $this->label = $content->hero->label ?? ''; $this->headline = $content->hero->headline ?? ''; }
protected function resolveFieldValue(string $field): mixed { return match ($field) { 'label' => $this->label, 'headline' => $this->headline, default => null, }; }
public function render(): \Illuminate\Contracts\View\View { return view('livewire.components.hero'); }}Note: Without this trait,
renderField()is unavailable. Non-visual-editing components simply don’t need it — use{{ $label }}directly.
Step 2: Add Source Map Initialization
Section titled “Step 2: Add Source Map Initialization”To enable inline editing, populate $cmsSourceMap during initialization:
namespace App\Livewire;
use JFA\FilamentCMSCore\CMS\SectionContent;use JFA\FilamentCMSLivewire\Livewire\SectionComponent;use JFA\VeFilamentCMSLivewire\Concerns\InteractsWithVisualEditing;
class Hero extends SectionComponent{ use InteractsWithVisualEditing;
public string $label = ''; public string $headline = '';
public array $cmsSourceMap = [];
protected function hydrateFromContent(SectionContent $content): void { $this->label = $content->hero->label ?? ''; $this->headline = $content->hero->headline ?? ''; }
protected function initializeVisualEditing(): void { $sourceMap = $this->sectionContent->hero->getSourceMap(); if ($sourceMap !== null) { $this->cmsSourceMap = $sourceMap; } }
protected function resolveFieldValue(string $field): mixed { return match ($field) { 'label' => $this->label, 'headline' => $this->headline, default => null, }; }
protected function getCmsSourceMap(): array { return $this->cmsSourceMap; }
public function render(): \Illuminate\Contracts\View\View { return view('livewire.components.hero'); }}The initializeVisualEditing() hook is called automatically by SectionComponent::mount() after hydrateFromContent().
Step 3: Implement HasFieldLabels (Optional)
Section titled “Step 3: Implement HasFieldLabels (Optional)”For localized field labels in the editor:
namespace App\Livewire;
use JFA\FilamentCMSCore\CMS\SectionContent;use JFA\FilamentCMSLivewire\Livewire\SectionComponent;use JFA\VeFilamentCMSLivewire\Concerns\InteractsWithVisualEditing;use JFA\VeFilamentCMSLivewire\Contracts\HasFieldLabels;
class Hero extends SectionComponent implements HasFieldLabels{ use InteractsWithVisualEditing;
public string $label = ''; public string $headline = '';
public array $cmsSourceMap = [];
protected function hydrateFromContent(SectionContent $content): void { $this->label = $content->hero->label ?? ''; $this->headline = $content->hero->headline ?? ''; }
protected function initializeVisualEditing(): void { $sourceMap = $this->sectionContent->hero->getSourceMap(); if ($sourceMap !== null) { $this->cmsSourceMap = $sourceMap; } }
protected function resolveFieldValue(string $field): mixed { return match ($field) { 'label' => $this->label, 'headline' => $this->headline, default => null, }; }
protected function getCmsSourceMap(): array { return $this->cmsSourceMap; }
public function fieldLabels(): array { return [ 'label' => __('cms.hero.fields.label'), 'headline' => __('cms.hero.fields.headline'), 'primary_cta_text' => __('cms.hero.fields.primary_cta_text'), ]; }
public function render(): \Illuminate\Contracts\View\View { return view('livewire.components.hero'); }}Create translations in lang/es/cms.php:
return [ 'hero' => [ 'fields' => [ 'label' => 'Etiqueta', 'headline' => 'Titular', 'primary_cta_text' => 'Texto del CTA', ], ],];Step 4: Use renderField() in Blade
Section titled “Step 4: Use renderField() in Blade”renderField() is provided by the InteractsWithVisualEditing trait. When visual editing is active, it injects data-cms-source attributes automatically:
{{-- Before --}}<span>{{ $label }}</span><h1>{{ $headline }}</h1>
{{-- After --}}<span>{!! $this->renderField('label', 'text') !!}</span><h1>{!! $this->renderField('headline', 'text') !!}</h1>Field Types
Section titled “Field Types”Pass the appropriate field type to render the correct editor:
| Type | Editor | Use For |
|---|---|---|
text | TextInput | Short text, headlines |
textarea | Textarea | Medium text, descriptions |
rich_text | RichEditor | Formatted content |
image | FileUpload | Images |
repeater | Repeater | Lists, grids |
Image Fields
Section titled “Image Fields”<img src="{{ $imageUrl }}" alt="{{ $imageAlt }}" data-cms-source="{{ $this->renderImageField('image_url') }}">Repeater Containers
Section titled “Repeater Containers”<div data-cms-source="{{ $this->renderRepeaterContainer('members') }}"> @foreach($members as $member) <div>{{ $member['name'] }}</div> @endforeach</div>Step 5: Include VisualEditor in Layout
Section titled “Step 5: Include VisualEditor in Layout”Add the visual editor component to your main layout:
{{-- resources/views/layouts/app.blade.php --}}<body> {{ $slot }}
@auth @can('EditFrontendContent') <livewire:visual-editor /> @endcan @endauth</body>Or include it unconditionally (the component checks authorization internally):
<livewire:visual-editor />Step 6: Test Visual Editing
Section titled “Step 6: Test Visual Editing”- Log in as a user with
EditFrontendContentpermission - Visit a page with your section
- Click the floating “Edit” button
- Click on editable content
- Edit and save
- Verify changes appear immediately
How It Works Internally
Section titled “How It Works Internally”EditingMode
Section titled “EditingMode”Session-backed singleton:
$editingMode = app(\JFA\VeFilamentCMSLivewire\EditingMode::class);
$editingMode->activate(); // Enable editing$editingMode->deactivate(); // Disable editing$editingMode->isActive(); // Check stateInitializeEditingMode Middleware
Section titled “InitializeEditingMode Middleware”Auto-registered by VeFilamentCMSLivewireServiceProvider on the web group:
// Ensures EditingMode is resolved earlypublic function handle(Request $request, Closure $next): Response{ $this->editingMode->isActive(); return $next($request);}contentUpdated Event
Section titled “contentUpdated Event”When content is saved:
// InlineEditForm dispatchesdispatch('contentUpdated', sectionId: $sectionId);
// SectionComponent listens (via InteractsWithVisualEditing trait)public function refreshFromCMS(int $sectionId): void{ if (! $this->isVisualEditingActive()) { return; }
if (($this->getCmsSourceMap()['section_id'] ?? null) !== $sectionId) { return; }
$section = \JFA\FilamentCMSCore\Models\Section::find($sectionId); $resolver = new \JFA\FilamentCMSCore\CMS\ContentResolver($section); $content = $resolver->resolve();
$this->hydrateFromContent($content);}Disabling Visual Editing
Section titled “Disabling Visual Editing”You can disable visual editing via config without uninstalling the package:
'visual_editing' => [ 'enabled' => false,],Or via environment variable:
VISUAL_EDITING_ENABLED=falseWhen disabled, renderField() outputs plain values and the contentUpdated listener is a no-op.
Troubleshooting
Section titled “Troubleshooting”| Problem | Cause | Solution |
|---|---|---|
| Edit button not showing | Missing permission | Assign EditFrontendContent role |
| Elements not clickable | Missing renderField() | Use {!! $this->renderField() !!} |
| Wrong field type shown | Missing field_type | Pass second parameter to renderField() |
| Changes not reflecting | Missing source map | Initialize $this->cmsSourceMap |
| Labels showing as keys | Missing translations | Add to lang/{locale}/cms.php |
| Modal not opening | Missing wire:key | Add dynamic wire:key on inline-edit-form |