Live Widget Previews: Widget Management in the Customizer in WordPress 3.9

The WordPress 3.9 release includes widgetWidget A WordPress Widget is a small block that performs a specific function. You can add these widgets in sidebars also known as widget-ready areas on your web page. WordPress widgets were originally created to provide a simple and easy-to-use way of giving design and structure control of the WordPress theme to the user. management in the CustomizerCustomizer Tool built into WordPress core that hooks into most modern themes. You can use it to preview and modify many of your site’s appearance settings.: previewing changes to widget areas before publishing them live for the world to see. This feature was birthed out of the Widget Customizer pluginPlugin A plugin is a piece of software containing a group of functions that can be added to a WordPress website. They can extend functionality or add new features to your WordPress websites. WordPress plugins are written in the PHP programming language and integrate seamlessly with WordPress. These can be free in the WordPress.org Plugin Directory https://wordpress.org/plugins/ or can be cost-based plugin from a third-party which started development in the 3.8 releaseRelease A release is the distribution of the final version of an application. A software release may be either public or private and generally constitutes the initial or new generation of a new or upgraded application. A release is preceded by the distribution of alpha and then beta versions of the software. cycle, it was formally proposed for merging into coreCore Core is the set of software required to run WordPress. The Core Development Team builds WordPress. at the start of the 3.9 release cycle, and then it was merged and followed by many changes. I wanted to follow-up here to talk through the changes to Widget Customizer since it was initially proposed (read this post for a full overview of the feature). Here is what has changed since then:

No more opt-in for theme support

The Widget Customizer plugin implemented a mechanism for doing fast live widget previews. By default, all changes made to settings in the customizer cause the preview window to do a full page refresh. This causes a lag between when you makemake A collection of P2 blogs at make.wordpress.org, which are the home to a number of contributor groups, including core development (make/core, formerly "wpdevel"), the UI working group (make/ui), translators (make/polyglots), the theme reviewers (make/themes), resources for plugin authors (make/plugins), and the accessibility working group (make/accessibility). a change and when the change appears. The customizer also supports an alternate non-refresh mechanism for previewing changes in the customizer (the postMessage transport) which you often see themes implement for live-previewing the blogblog (versus network, site) name or the headerHeader The header of your site is typically the first thing people will experience. The masthead or header art located across the top of your page is part of the look and feel of your website. It can influence a visitor’s opinion about your content and you/ your organization’s brand. It may also look different on different screen sizes. color.

Implementing such immediate live-previews requires all of the logic for previewing the change to be implemented in JavaScriptJavaScript JavaScript or JS is an object-oriented computer programming language commonly used to create interactive effects within web browsers. WordPress makes extensive use of JS for a better user experience. While PHP is executed on the server, JS executes within a user’s browser. https://www.javascript.com/., in addition to the underlying PHPPHP The web scripting language in which WordPress is primarily architected. WordPress requires PHP 5.6.20 logic for rendering the change when the setting is saved. This is not possible for widgets, since they all use PHP to handle the sanitization of the form inputs and for rendering arbitrary content that a widget may contain. (You’ll notice that when you make a change to a widget in the customizer, it actually does an Ajax request to submit the widget form for sanitization every time.) Well, to get around having the customizer preview refresh for every widget change, we implemented an Ajax renderer for the widgets, and then used the postMessage transport to initiate this widget render request, and then the rendered widget response would get swapped out in the DOM.

Long story short, we decided to remove this feature to use postMessage for faster live previews. We have created #27355 to re-add this feature, but to generalize it so that any customizer control can take advantage of doing these partial preview refreshes. So for now, you’ll have to wait a moment for the preview to refresh when you make a change to a widget area, but now you don’t change your themes or widgets to add explicit support for Widget Customizer.

No more Update button (usually)

Widget forms on the adminadmin (and super admin) page have a familiar Save button. In previous versions of Widget Customizer, we adapted the Save button to be an Update button, since it wouldn’t actually save the widget but would just update the preview with the widget’s latest state. Having to click this Update button to see the preview update, however, was a poor user experience and was unnecessary. Therefore, we implemented a means of submitting the form to update the widget’s state in the preview whenever a field in the control is updated: the Update button was hidden and the spinner appears when a change is being synced to the preview.

The logic which does this live submission of the widget form as you interact with it, depends on the fields in the widget form to be the same fields which get returned when the widget form is sanitized. This is so that the fields can be aligned for being updated with their sanitized values. We couldn’t go the route of the widget forms on the widgets admin page, which actually get fully replaced with the newly-sanitized form, because this would interfere with the user quickly inputting into these fields.

So if a widget form dynamically adds or changes which input fields are included, the live-as-you-type-them updates will stop and the Update button will re-appear. When you click this button, the form will replace itself with the sanitized version just like on the widgets admin page. We also introduced new jQuery events to help handle these form changes…

New widget-added and widget-updated jQuery events

In the widgets admin page, when you add a new widget to a widget area, a widget template element gets cloned and then inserted into the selected widget area. This widget template is cloned in a way that any event handlers initially added to the widget template do not persist on any newly-added widgets. Any widgets previously-added to the widget areas would have these event handlers attached, as well as any dynamically-created fields (e.g. jQuery Chosen), but again, newly-added widgets fail to retain these. The same problem exists, however, whenever you update a widget: since the newly-sanitized widget form replaces the old one, any dynamic elements get lost. (This is why event delegation is often required.)

The same challenges here for the widgets admin page are also challenges for widgets in the customizer. And so in #19675 and #27491, we’ve introduced widget-added and widget-updated jQuery events which pass along the widget container as the second argument for the event handler. So to initialize and re-initialize a widget’s dynamic UIUI User interface, you can now use these events in something like this:

jQuery( function ( $ ) {

	function init( widget_el, is_cloned ) {

		// Clean up from the clone which lost all events and data
		if ( is_cloned ) {
			widget_el.find( '.chosen-container' ).remove();
		}

		widget_el.find( 'select.dynamic-field-chosen-select' ).chosen( { width: '100%' } );
	}

	/**
	 * @param {jQuery.Event} e
	 * @param {jQuery} widget_el
	 */
	function on_form_update( e, widget_el ) {
		if ( 'dynamic_fields_test' === widget_el.find( 'input[name="id_base"]' ).val() ) {
			init( widget_el, 'widget-added' === e.type );
		}
	}

	$( document ).on( 'widget-updated', on_form_update );
	$( document ).on( 'widget-added', on_form_update );

	$( '.widget:has(.dynamic-field-chosen-select)' ).each( function () {
		init( $( this ) );
	} );

} );

This is admittedly still not ideal, but it is much better than hacking the jQuery Ajax events to detect when a form update happens. In the future, I’m keen to see the widgets be revamped to use Backbone extensively.

Configuring widgets during a theme switch

We had a surprise late in the release (#27767) when it was reported that when you activate a new theme via the customizer, any changes made to the widget areas get lost when the theme is activated. This issue has been fixed, so now you can preview a theme and fully configure all of the widget areas before going live.

Beware of widgets and caching

We had another challenge in #27538 where widget changes previewed in the customizer would leak outside the preview and on the live site if the widget cached the output for performance. The core widgets Recent Posts and Recent Comments both cache their outputs in the object cache, and if a persistent object cache plugin is enabled (e.g. Memcached or APC) then the cache would get poisoned with the previewed widget, even though the widget’s changes weren’t saved. The same behavior would be experienced for widgets that use transients to cache the rendered output.

To help widgets do the right thing in regards to caching, we introduced a WP_Widget::is_preview() method which widgets should always check before they interact with the cache. For example, a widget’s widget()method which caches its output should do something like this:

if ( ! $this->is_preview() ) {
    $cached = wp_cache_get( 'foo-widget' );
    if ( ! empty( $cached ) ) {
        echo $cached;
        return;
    }
    ob_start();
}

extract( $args );
echo $before_widget;
// ...
echo $after_widget;

if ( ! $this->is_preview() ) {
    $cached = ob_get_flush();
    wp_cache_set( 'foo-widget', $cached );
}

Look forward to more improvements to widgets and the customizer in future releases!

#3-9, #appearance, #dev-notes, #widgets