Implementing Selective Refresh in the Customizer

Authored by

To make the Customizer a great user experience, themes and plugins should implement settings that use the postMessage transport as much as possible. By default, settings get registered with a refresh transport meaning that every change to that setting will trigger a refresh in the preview so that PHP can re-render the preview with the settings applied via filters. When a theme/plugin implements the postMessage transport, it is doing an opt-in for using JavaScript to apply the setting changes to the preview instantly without any reloading of the preview window. For example, the Twenty Fifteen applies previews changes to the Site Title via code like:

wp.customize( 'blogname', function( setting ) {
	/* Deferred callback for when setting exists */
	setting.bind( function( newValue ) {
		/* Update callback for setting change */
		$( '.site-title a' ).text( newValue );
	} );
} );

In PHP, the logic for rendering the setting’s value into the template is just:

<?php bloginfo( 'name' ); ?>

In both JS and PHP, the actual logic that applies the value to the template is just one line. However, what if the setting being previewed was a complex object that required complicated PHP logic to render the data in the desired way? You could try to re-implement all of this PHP logic in JS, but this would violate the DRY (Don’t Repeat Yourself) principle. Ideally then, the setting value could be rendered on the server by PHP via a Mustache template, and this same template could be loaded into the Customizer JavaScript so that changes to the setting could be re-rendered onto that Mustache template instantly and completely on the client (via the postMessage transport).

Often, however, the world is not ideal. Most existing WordPress code is written solely with server-side rendering via PHP templates. In WordPress 4.3, management of nav menus was added to the Customizer. The rendering of the nav menu items is still done completely with PHP since plugins and themes may be adding any number of filters to customize the output. So we couldn’t use JavaScript entirely for re-rendering changes to nav menus. Nevertheless, we could make the changes appear much faster than when a full preview refresh is involved. What was implemented then is a selective/partial refresh of the nav menu container element via an Ajax request. (See class-wp-customize-nav-menus.php and customize-preview-nav-menus.js.)

Like menus, widgets also rely on PHP extensively (see #33507 for what we can do to make them more JS-driven). Implementing live updates for current widgets in the Customizer preview cannot rely on JavaScript alone due to the PHP dependencies for form rendering, instance sanitization, and widget rendering. Like Customizer Nav Menus in 4.3, the Customize Partial Refresh plugin implements a faster previewing for changes to widgets by using selective refresh, using postMessage transport to notify the preview of a change and then fetching the updated widget contents via Ajax. (See class-wp-customize-partial-refresh-widgets.php, customize-partial-refresh-widgets-pane.js, and customize-partial-refresh-widgets-preview.js.)

For a generalized framework in Core for selective refresh, see #27355 and the pull request on the Customize Partial Refresh plugin.

So both nav menus and widgets implemented a hybrid approach to previewing setting changes, involving both PHP and JS, refresh and postMessage. In the normal case, a control is manipulated in the Customizer pane which then modifies the corresponding setting, and these setting values are then sent to the preview instantly via postMessage. When the Customizer preview receives the setting’s value, it determines which menu or sidebar is being modified and then it does an Ajax request to an endpoint with the modified setting (and all other dirty settings) to obtain the template partial with the new setting values previewed.

But as noted previously, the world is often not ideal. Sometimes the selective/partial refresh logic in the Customizer preview is unable to update the partial template’s container via Ajax. For instance, if a menu is newly assigned to a theme location (or unassigned from one) or if the first widget is added to a widget area (or a widget area is newly emptied), the page template’s page structure may change entirely to add new columns or include additional wrapper elements. In these cases the partial refresh logic must fall back to doing a full refresh.

To initiate a Customizer preview refresh from inside the preview, some message passing is required. In the Customizer preview, all you have to do is:

if ( myPreviewNeedsRefresh ) {
    wp.customize.preview.send( 'refresh' );
}

Then in the Customizer pane all you do is (which is actually included in the nav menus implementation):

wp.customize.previewer.bind( 'refresh', function() {
    wp.customize.previewer.refresh();
} );

Note that this cross-frame message passing will no longer be required once Customizer transactions are introduced to Core, because the preview iframe gets re-architected to load using a natural URL, as opposed to doing an Ajax request and then doing document.write( response ) into an about:blank iframe. A Customizer preview with transactions would use an iframe with a natural URL like http://src.wordpress-develop.dev/?customize_transaction_uuid=9ff82d40-3f80-4f29-afc0-d19fc0da96d9. With this in place, then all that the Customizer preview will need to do to refresh itself is simply:

if ( myPreviewNeedsRefresh ) {
    window.location.reload( true );
}

As an aside, you would also be able to open the iframe’s URL in a new window, or even on an entirely different device. Since all of the settings would be written to the transaction in the authenticated Customizer state, a Customizer preview using transactions could be safely viewed by unauthenticated users. For an initial implementation of some of these transaction concepts, see our Customize Snapshots feature plugin.

7 thoughts on “Implementing Selective Refresh in the Customizer”

  1. Thanks for the great article.

    When I click on the link to Customizer transactions, I see it’s replaced by Customizer Changeset in WP 4.7. Is there any change in the code above affected by that?

    Also, do you know how Customizer “knows” when a change is made to refresh the preview pane? I have a problem with a theme on WordPress.com where the site is in French and the Customizer keeps refreshing all the time. I’m not sure if any translation causes the Customizer “think” that’s a change or somehow.

    1. @Anh Thanks. The selective refresh code in core was not changed much with the introduction of changesets in 4.7. The customizer decides to refresh the entire preview in two scenarios: if a control changes a setting that has the refresh transport, and the other is if the setting has a postMessage transport and has an associated partial registered runs its fallback refresh behavior when the partial refresh request fails. And actually, there are three tickets in Trac related to this:

      • #38612: Infinite refresh loop in Customizer when using the filter ‘theme_mod_nav_menu_locations’
      • #37032: Guard against infinite reload when setting change causes premature selective refresh
      • #32894: Customizer Menus: page enters an infinite reloading loop when using a menu with a walker

      To me it seems like you’re probably facing something related to the first issue since it’s related to translation. This was supposed to have been fixed in 4.7, but if you’re still facing the issue then it seems not. If you could read up on the discussion in that ticket and isolate where exactly the issue is happening that would be helpful.

      To help debug, I suggest applying the following patch in order to intercept the call to reload the page to then see the stack trace and identify where the fallback behavior is initated from:

      --- src/wp-includes/js/customize-selective-refresh.js
      +++ src/wp-includes/js/customize-selective-refresh.js
      @@ -670,6 +670,7 @@ wp.customize.selectiveRefresh = ( function( $, api ) {
       	 * @since 4.5.0
       	 */
       	self.requestFullRefresh = function() {
      +		debugger;
       		api.preview.send( 'refresh' );
       	};
      
  2. When i use selective_refresh but i want to load all js so my slider can run. What should I do?

    $wp_customize->selective_refresh->add_partial( 'slider', array(
    	'selector' => '.content-slider',
    	'settings' => array('homepage_hide_slider'),
    	'render_callback' => function() {
    		return get_template_part( 'template-parts/content', 'slider' );;
    	},
    ) );
    1. I’m not sure I understand the question. If you need to re-initialize JS after the slider has been refreshed, then you can use the partial-content-rendered event that is triggered on wp.customize.selectiveRefresh. For example:

      wp.customize.selectiveRefresh.bind( 'partial-content-rendered', function( placement ) {
          // ...
      } );

      The placement param will have a partial property which you can use to detect what was refreshed, and then the container property will be the element that is the container for your slider.

Leave a Reply

Your email address will not be published. Required fields are marked *