Skip to content

Integrating with the Visual Editor ​

Bagisto Visual includes a live preview editor that allows merchants to build and customize storefront pages interactively, without reloading the page. When a section or block is added, updated, or removed through the editor, its HTML is dynamically re-rendered from the backend and injected into the DOM in place—without triggering a full page reload.

However, any JavaScript behavior associated with sections or blocks (like carousels, modals, or event listeners) is not automatically re-initialized. Additionally, some setting changes—such as text, image URLs, or inline styles—can be updated instantly in the browser, without requiring a backend re-render.

This guide explains how to:

  • Reinitialize JavaScript behavior when sections or blocks are re-rendered
  • Enable instant, client-side updates for simple setting types
  • Ensure blocks are visible and interactable when being edited

By integrating with these editor behaviors, your sections and blocks will feel fast, predictable, and intuitive to customize.

1. Reinitializing JavaScript ​

When a section or block is updated in the editor, its DOM is replaced. Any interactive JavaScript (like sliders or dropdowns) must be reattached.

Bagisto Visual emits events during this lifecycle:

Common Events ​

EventTimingUse case
visual:section:loadAfter section is added or re-renderedRe-run any necessary JavaScript to ensure the section functions and displays correctly, as if the page were freshly loaded. You may also want to restore the section state.
visual:section:unloadBefore section is removed or re-renderedMake sure to clean up event listeners, variables, and anything else to prevent issues when interacting with the page and avoid memory leaks. Also, save the section state.
visual:block:loadAfter block is added or re-renderedRe-run block-specific JavaScript behavior.
visual:block:unloadBefore block is removed or re-renderedClean up block-specific event listeners and state.

Lifecycle Events ​

EventTiming
visual:section:addingBefore section is added
visual:section:addedAfter section is added
visual:section:removingBefore section is removed
visual:section:removedAfter section is removed
visual:section:updatingBefore section is updated
visual:section:updatedAfter section is updated
visual:section:movingBefore section is moved
visual:section:movedAfter section is moved
visual:section:setting:updatedWhen a section setting changes
visual:block:addingBefore block is added
visual:block:addedAfter block is added
visual:block:removingBefore block is removed
visual:block:removedAfter block is removed
visual:block:updatingBefore block is updated
visual:block:updatedAfter block is updated
visual:block:movingBefore block is moved
visual:block:movedAfter block is moved
visual:block:setting:updatedWhen a block setting changes

Each event exposes:

ts
event.detail = {
  sectionId,  // Section ID
  section,    // Section object
  blockId,    // Block ID (when applicable)
  block,      // Block object (when applicable)
};

Detect the Theme Editor ​

Use @visual_design_mode and @end_visual_design_mode directives to scope code that should only run in the Visual Editor live preview.

blade
@visual_design_mode
    <!-- This code only runs in the editor -->
    <div class="editor-notice">You are in design mode</div>

    @pushOnce('scripts')
        <script>
            // JavaScript that only runs in the editor
            console.log('Editor mode active');
        </script>
    @endPushOnce
@end_visual_design_mode

This prevents editor-specific code from running on the live storefront, keeping your production code clean and performant.

Section Example ​

blade
@visual_design_mode
    @pushOnce('scripts')
        <script>
            document.addEventListener('visual:section:unload', (event) => {
                if (event.detail.section.type === '{{ $section->type }}') {
                    // Save scroll position, destroy carousels, etc.
                }
            });

            document.addEventListener('visual:section:load', (event) => {
                if (event.detail.section.type === '{{ $section->type }}') {
                    // Reinitialize interactivity: sliders, modals, listeners
                }
            });
        </script>
    @endPushOnce
@end_visual_design_mode

Block Example ​

blade
@visual_design_mode
    @pushOnce('scripts')
        <script>
            document.addEventListener('visual:block:unload', (event) => {
                if (event.detail.block.type === '{{ $block->type }}') {
                    // Clean up block-specific listeners, state, etc.
                }
            });

            document.addEventListener('visual:block:load', (event) => {
                if (event.detail.block.type === '{{ $block->type }}') {
                    // Reinitialize block-specific JavaScript
                }
            });
        </script>
    @endPushOnce
@end_visual_design_mode

If you are using Alpine.js or Livewire, your state will automatically persist between updates—no additional setup is needed. However, if you rely on vanilla JS or third-party libraries, you should reinitialize them after every update.

2. Enabling Instant Setting Updates ​

For simple updates (text, image URLs, classes), you can avoid full re-renders and apply changes directly in the DOM to provide instant preview without delay.

Bagisto Visual supports this via:


Option 1: liveUpdate() Blade Directives ​

Use $section->liveUpdate() or $block->liveUpdate() to bind settings to elements.

These helpers inject metadata to enable the editor to update the live preview without requiring a server-side re-render.

->text(string $settingId) ​

Updates the element's textContent whenever the specified setting changes.

blade
<h1 {{ $section->liveUpdate()->text('heading') }}>
  {{ $section->settings->heading }}
</h1>

->html(string $settingId) ​

Updates the element's innerHTML with the new setting value.

blade
<div {{ $section->liveUpdate()->html('html_content') }}>
  {!! $section->settings->html_content !!}
</div>

->outerHtml(string $settingId) ​

Replaces the entire element (outerHTML) with the setting value.

blade
<div {{ $section->liveUpdate()->outerHtml('html_block') }}>
  {!! $section->settings->html_block !!}
</div>

->attr(string $settingId, string $attributeName) ​

Updates the specified HTML attribute (e.g. src, href, alt) with the setting value.

blade
<img
  src="{{ $section->settings->image }}"
  {{ $section->liveUpdate()->attr('image', 'src') }}>

->style(string $settingId, string $property) ​

Updates a specific CSS style property on the element using the setting value.

blade
<div
  style="width: {{ $section->settings->width }}"
  {{ $section->liveUpdate()->style('width', 'width') }}>
</div>

🔹 Multiple Bindings on a Single Element ​

You can bind multiple settings fluently to different parts of the same element:

blade
<a
  href="{{ $section->settings->link_url }}"
  {{ $section->liveUpdate()
      ->text('link_text')
      ->attr('link_url', 'href') }}>
  {{ $section->settings->link_text }}
</a>

🔹 Using with Blocks ​

The liveUpdate() method works with both sections and blocks:

In a section:

blade
<h1 {{ $section->liveUpdate()->text('heading') }}>
  {{ $section->settings->heading }}
</h1>

In a block:

blade
<p {{ $block->liveUpdate()->text('text') }}>
  {{ $block->settings->text }}
</p>

Option 2: JavaScript API (Visual.handleLiveUpdate()) ​

For more complex cases (e.g. multiple targets, transform logic, or styling), use Visual.handleLiveUpdate():

blade
@visual_design_mode
    @pushOnce('scripts')
        <script>
            document.addEventListener('visual:editor:init', () => {
                window.Visual.handleLiveUpdate('{{ $section->type }}', {
                    // handle update of section level settings
                    section: {
                        title: { target: 'h2', text: true },
                        image: { target: 'img', attr: 'src' }
                    },
                    blocks: {
                        // handle update of block settings
                        announcement: {
                            text: { target: 'p', text: true }
                        }
                    }
                });
            });
        </script>
    @endPushOnce
@end_visual_design_mode

API Reference: handleLiveUpdate ​

ts
handleLiveUpdate(
  sectionType: string,
  mappings: {
    section?: Record<string, LiveUpdateOptions>;
    blocks?: Record<string, Record<string, LiveUpdateOptions>>;
  }
)

LiveUpdateOptions ​

OptionDescription
targetCSS selector within the section
textReplace text content
htmlReplace inner HTML
attrSet a DOM attribute (e.g. src, href)
styleSet a CSS style property
handlerCustom JS function (el, value) => {}
transformModify the value before applying it

3. Keep Edited Blocks Visible ​

When a merchant is editing a block, that block should remain visible — even if it's part of a carousel, tab, or other dynamic view.

Best Practice:

  • When rendering blocks dynamically (e.g. in a slider), ensure the currently edited block is active or in view.
  • This enhances clarity and ensures live changes are reflected immediately.

You can detect which block is being edited using the visual:section:updated event and event.detail.

No JavaScript is strictly required, but your UI logic should accommodate visibility for active blocks.

4. Summary ​

  • Use @visual_design_mode to scope editor-specific behavior
  • Use liveUpdate() for simple instant updates
  • Use handleLiveUpdate() for advanced DOM control
  • Reinitialize JavaScript using visual:section:load and visual:block:load
  • Make edited blocks clearly visible in the preview

These patterns help ensure your sections and blocks behave consistently and responsively within the live editor environment.

Released under the MIT License.