Skip to content

Instantly share code, notes, and snippets.

@Pross
Last active November 16, 2022 19:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Pross/c4c24fc59f543f9284b21881ad9c06c5 to your computer and use it in GitHub Desktop.
Save Pross/c4c24fc59f543f9284b21881ad9c06c5 to your computer and use it in GitHub Desktop.
New iframe UI docs

Beaver Builder IFrame UI

This PR introduces an updated builder interface that uses an iframe to display the layout you are editing for accurate previews at different device sizes. The responsive editing interface has also been updated to allow you to preview the layout at any size you'd like while you're editing.

The iframe UI works by using two "windows." One is the browser window you use to view the builder, also referred to as the parent window. The other is an iframe window used to render the page content for you to edit.

What's in the Parent Window? The parent window contains what is needed for the iframe to load and work correctly. This is also where the iframe renders the builder's UI once it has loaded. That includes the majority of the UI including the toolbar, menu, panels, and settings. Even though the parent window loads everything, the iframe controls most execution.

What's in the IFrame The iframe currently contains the entire page and builder. That could change in the future but for now when the builder loads, if it's in the iframe, it'll render its UI in the parent window instead of the current window that it's loaded in. The only UI that's rendered in the iframe is the overlays.

How the IFrame UI Loads

Loading the UI The iframe UI works by intercepting the template_redirect hook and rendering a custom HTML document with only what's needed to render the iframe when the fl_builder_ui GET variable is present. The iframe loads the exact same page, however, it includes the fl_builder_ui_iframe GET variable instead so the builder knows to load the iframe content instead of the iframe's parent. This allows the builder to send AJAX requests from both the parent and the child because the URL is the same.

Loading Scripts The majority of scripts are loaded within the iframe. This allows the builder to continue working in the legacy UI as we transition to an iframe. The difference is that once loaded, the UI renders itself in the parent window instead of the current window as legacy does.

You will see that some scripts are loaded in the parent window. This is mainly because those scripts act on the current window and can't be adjusted through config to render and work on the parent window. If we loaded them in the iframe (e.g. the media library) they would render in the iframe. In the case of the media library, we need it to render in the parent so it can span the width of the page when viewing smaller device sizes.

See FLBuilderUIIFrame::enqueue_scripts for more on script loading in the parent window. Script loading in the iframe remains the same.

Loading Styles Styles are loaded in both the iframe and parent window. This was done because the Beaver Builder styles needed for the iframe (mainly overlays) and parent window UI (toolbar, settings, etc.) are all combined within a number of files (and plugins if you count Themer). We currently need those stylesheets to apply to elements rendered in both the iframe and parent window, so they are loaded in both places.

It's also very likely that third-party developers will face this issue because we've recommended enqueuing builder assets using wp_enqueue_scripts and checking FLBuilderModel::is_builder_active. In the future, we could provide new recommended hooks for enqueuing assets in either the iframe or parent window and split Beaver Builder styles into different files.

Developing in the IFrame UI

Accessing the Parent Window

// Within the iframe
const win = window.parent

// Within the parent window
const win = window

Since the builder's iframe is loaded on the same domain, we don't have to use postMessage to communicate. Instead, we can access the parent window from the iframe using parent.window. That allows you to execute any scripts attached to the parent window or manipulate its DOM with native functions.

The really cool thing is that parent.window is still set even if we're not in the iframe. In that legacy context, it'll resolve to the only window that's available, the current window, and your logic should still function as expected. For example, if you called parent.window.document.getElementById in your script to access a UI element in the parent window, it would still access that same UI element in the legacy UI.

Accessing the IFrame Window

// Within the iframe
const win = window

// Within the parent window
const win = FLBuilder.UIIFrame.getIFrameWindow()

When executing code within the parent window, you can access the iframe's window object by using the FLBuilder.UIIFrame.getIFrameWindow() helper function. That will return the iframe's window object or the current window object if the legacy UI is enabled.

If you need something on the iframe's window when executing within the iframe, you can simply access the window object or call any globals directly. That will fall back to the current window and work as expected in the legacy UI.

Selecting Elements with jQuery

// Accessing parent UI from within the iframe
$( '.fl-builder-bar', parent.window.document )
$( 'body', parent.window.document ).find( '.fl-builder-bar' )

// Accessing iframe UI from within the parent
var win = FLBuilder.UIIFrame.getIFrameWindow();
$( '.fl-row', win.document )
$( 'body', win.document ).find( '.something' )

Selecting elements with jQuery works the same, however, there are cases when you need to specify which document you are checking in. jQuery's select function has a second parameter that's not often used (shown above) that lets you specify the document you'd like it to check in.

That matters because only calling $( '.fl-builder-bar' ) won't select it if you're selecting from a script within the iframe. That element is rendered in the parent window, so to select it, you need to tell jQuery to look there. Both of the examples above will also fallback to the same window object in the legacy UI.

When the builder is loaded, jQuery's select function is modified at runtime to also check in the parent window. That will help catch most of the time you forget to specify which document to check but it won't catch everything. The rule of thumb going forward is to know what window you're executing in and to specify the document if you're accessing something outside of that window.

Triggering Events with jQuery

// Triggering parent events within the iframe
parent.window.jQuery( '.fl-button' ).trigger( 'click' )

// Triggering iframe events within the parent
var win = FLBuilder.UIIFrame.getIFrameWindow();
win.jQuery( '.fl-row' ).trigger( 'mouseenter' )

Triggering events across windows using jQuery requires you to use that window's copy of jQuery to select the element. That's because jQuery internally stores event data that won't be available if you trigger using the wrong copy. Triggering in the same window requires no changes.

Working with TinyMCE

// When working with settings forms in the iframe
const t = parent.window.tinymce

// When working with inline editing in the iframe
const t = tinymce

TinyMCE is one of the scripts that's loaded in both the iframe and parent window. This is because TinyMCE needs to load in the window that it renders. The parent window's copy of TinyMCE is used to work with settings forms since that's where they render. The iframe's copy is used to work with inline editing since that is rendered in the iframe.

Enqueuing in the Parent Window

add_action( 'fl_builder_ui_enqueue_scripts', function() {
    // Enqueue builder UI assets.
} );

Even though we lock down script loading in the parent window, we still allow scripts to be explicitly enqueued using the fl_builder_ui_enqueue_scripts action. Most scripts should still be enqueued in the FLBuilder class for backwards compatibility with the legacy UI until we decide to sunset that.

Disabling the IFrame UI

add_filter( 'fl_builder_iframe_ui_enabled', '__return_false' );

The iframe UI can be disabled with a filter or by going to Admin > Settings > Beaver Builder > Advanced and toggling the Responsive IFrame UI setting.

Frontend Code Reference

FLBuilder.UIIFrame.isEnabled Returns true if the iFrame UI is enabled. The legacy UI is enabled if this returns false.

FLBuilder.UIIFrame.isUIWindow Returns true if the script is currently executing in the parent window that renders the builder's UI.

FLBuilder.UIIFrame.isIFrameWindow Returns true if the script is currently executing in the iframe window.

FLBuilder.UIIFrame.getIFrameWindow Returns the iframe's window object. Falls back to the current window for the legacy UI.

Backend Code Reference

FLBuilderUIIFrame::is_enabled Returns true if the iFrame UI is enabled. The legacy UI is enabled if this returns false.

FLBuilderUIIFrame::is_ui_request Returns true if the current request is for the iframe's parent window.

FLBuilderUIIFrame::is_iframe_request Returns true if the current request is for what should load in the iframe.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment