Improvements to the Customize JS API in 4.9

There are many user-facing Customizer improvements in 4.9, including: drafting/scheduling of changesets, autosave revisions, changeset post locking, frontend public previews, a new experience for browsing and installing themes, updated nav menu creation UX, and the code editing improvements for the Custom HTML widget and Additional CSS. But in addition to all of these, there are also many improvements for developers which will make extending the Customizer much more pleasant.

Something important to remember about the Customizer is that it is a single page application that is powered by JavaScript. Many developers may only interact with the PHP API for registering controls, settings, sections, panels, and partials. But controls, sections, and panels do not need to be registered in PHP at all. The PHP API for registration is essentially a wrapper for the underlying JS API. When you load the Customizer all of the params for the PHP-registered constructs are exported to the client for the JavaScript API to instantiate and initially add to the UI, but this JS API can dynamically instantiate additional constructs at any time thereafter in a Customizer session. This is how new widgets, nav menus, and nav menu items are added without requiring the entire Customizer to reload. You can also avoid statically registering settings and partials in PHP by instead adding filters to dynamically recognize settings and partials, allowing them to be registered on demand. All of this allows the Customizer application to scale out to be able to customize and preview an unlimited number of things on a site (e.g. any post or page with their postmeta in the Customize Posts feature plugin). The point here is that in order for the Customizer to scale, the JavaScript API must be used directly. So this is why the Customizer JS API improvements in 4.9 are important as they fix many longstanding annoyances and shortcomings with the JS API.

This dev note contains the following sections:

Elimination of Repeated ID when Adding Constructs

Previously in the Customizer when you wanted to add a new construct dynamically you would need to pass the ID into the constructor itself and then pass the ID in when adding it to a collection as well. This was unfortunate as it required keeping multiple references to the same ID in sync, for example:

/* Before WordPress 4.9 */
wp.customize.control.add(
    'foo',
    new wp.customize.Control( 'foo', { /* … */ } )
);

Or to store the identifier in a variable to re-use, as the two should never differ:

/* Before WordPress 4.9 */
var code = 'too_short';
wp.customize.control( 'blogname' ).notifications.add(
    code, 
    new wp.customize.Notification( code, {
        message: 'Site title too short.'
    } ) 
);

In 4.9 this is no longer required, as the ID will be automatically read from the object being passed to the add method. So now you can simply do:

/* Since WordPress 4.9 */
wp.customize.control.add(
    new wp.customize.Control( 'foo', { /* … */ } ) 
);

And:

/* Since WordPress 4.9 */
wp.customize.control( 'blogname' ).notifications.add(
    new wp.customize.Notification( 'too_short', {
        message: 'Site title too short.'
    } ) 
);

Naturally the change here to the add method is backwards-compatible, and existing code that passes an ID as the first argument will still continue to work.

Flattened Constructor Parameters

When creating a setting in JavaScript, the supplied options argument is simply a 1-level deep object:

var setting = new wp.customize.Setting( 'foo', value, {
    transport: 'postMessage',
    /* ... */
} );

However, when constructing  something else—controls, sections, panels, or partials—the supplied options object required the properties to be wrapped in an intermediary params property like this:

/* Before WordPress 4.9 */
var control = new wp.customize.Control( 'bar', {
    params: {
        section: 'title_tagline'
        /* ... */
    }
} );

This was annoying and the params property has been unwrapped to allow you to pass the props in the root options object:

/* Since WordPress 4.9 */
var control = new wp.customize.Control( 'bar', {
    section: 'title_tagline'
    /* ... */
} );

Backwards compatibility is maintained, so existing code that passes props via options.params will continue to be read from first, and then fall back to the new unwrapped options for props. The options.params property is soft-deprecated in 4.9 (using it will raise no warning) but it may be hard-deprecated in the future.

Default Parameters

Another longstanding annoyance with adding constructs in the Customizer is that there was a lack of parameter defaults. When creating a Color control, for example, the following is the minimum required prior to 4.9:

/* Before WordPress 4.9 */
var control = new wp.customize.ColorControl( 'favorite_color', {
    params: {
        type: 'color',
        content: '<li class="customize-control customize-control-color"></li>',
        priority: 10,
        active: true,
        section: 'colors',
        label: 'Favorite Color',
        settings: { 'default': 'favorite_color' },
    }
} );
wp.customize.control.add( 'favorite_color', control );

This is a lot of code, considering that in PHP all that a user would have to do is:

$control = new WP_Customize_Color_Control( $wp_customize, 'favorite_color', array(
    'section' => 'colors',
    'label' =>'Favorite Color',
    'settings' => array( 'default' => 'favorite_color' ),
) );
$wp_customize->add_control( $control );

The type, content, priority, and active properties have defaults in PHP and so too they should have defaults in JS. And now they do. Creating a Color control in JS, combined with the flattening of params above, is now just:

/* Since WordPress 4.9 */
var control = new wp.customize.ColorControl( 'favorite_color', {
    section: 'colors',
    label: 'Favorite Color',
    settings: { 'default': 'favorite_color' },
} );
wp.customize.control.add( control );

So creating controls in JS is now pretty much identical to creating them in PHP, aside from the differences in syntax.

Beyond controls, defaults are also available for settings, sections, panels, and partials. For settings, defaults are provided for transport (refresh) and previewer (which should never vary):

/* Before WordPress 4.9 */ 
var setting = new wp.customize.Setting( 'favorite_color', '#000000', {
    transport: 'refresh',
    previewer: wp.customize.previewer
} );
wp.customize.add( 'favorite_color', setting );
/* Since WordPress 4.9 */
var setting = new wp.customize.Setting( 'favorite_color', '#000000' );
wp.customize.add( setting );

On all constructs (controls, settings, panels, sections, partials) there is a defaults property on their function prototypes which contains the default params. This can be leveraged to add new defaults to subclasses, in addition to any extended methods. For example:

var ColorSetting = wp.customize.Setting.extend({
    defaults: _.extend(
        {},
        wp.customize.Setting.prototype.defaults,
        {
            transport: 'postMessage'
        }
    ),

    validate: function( value ) {
        var setting = this, validatedValue;
        validatedValue = wp.customize.Setting.prototype.validate.call( setting, value );
        if ( null === validatedValue ) {
            return validatedValue;
        }
        if ( /^#\d{6}$/.test( validatedValue ) ) {
            setting.notifications.remove( 'invalid_color' );
        } else {
            setting.notifications.add( new wp.customize.Notification( 'invalid_color', {
                message: 'Bad color format'
            } ) );
        }
        return validatedValue;
    }
});

When this ColorSetting is instantiated, the transport of postMessage will not need to be explicitly provided each time.

Passing Settings when Instantiating Controls

When instantiating controls in JS prior to 4.9, you had to pass settings as an object mapping a setting key to the setting ID. For example, to add a control for manipulating the site title, you would do:

var blognameControl = new wp.customize.Control( 'blogname2', {
    /* ... */
    settings: {
        'default': 'blogname'
    }
} );

If a control is manipulating multiple settings, then the keys for the settings object will get additional values:

var bgPositionControl = new wp.customize.BackgroundPositionControl( 'mobile_bg_position', {
   /* ... */
   settings: {
      x: 'mobile_background_position_x',
      y: 'mobile_background_position_y'
   }
} );

The settings object here is really just indicating which settings the control depends on, so that it can defer initialization until all of the settings are created. Prior to 4.9, the keys served no purpose other than to give a symbolic reference to a given setting. When a default setting is used, then it also can be accessed directly via a setting shortcut. For example:

var xValue = bgPositionControl.settings.x.get();
var title = blognameControl.settings['default'].get();
var title2 = blognameControl.setting.get();

In 4.9, the way settings are passed to controls is getting revamped. Additionally, the symbolic keys supplied for the settings are now more useful in that they can be used to create input element links.

First of all, if you have a control that just takes one setting, you can now just pass a single setting:

var control = new wp.customize.Control( 'blogname2', {
    /* ... */
    setting: 'blogname'
} );

Additionally, just as with partials you can pass settings as an array (#36167), where the first item then becomes the default.

Furthermore, instead of passing a setting’s string ID you can now pass an existing Setting object, promoting better encapsulation (#37964):

var control = new wp.customize.Control( 'blogname2', {
    /* ... */
    setting: wp.customize( 'blogname' )
} );

Where this gets particularly important is that you can also pass Value instances as settings instead of registering a Setting, which would then attempt to get saved in the changeset (and fail as being unrecognized, without static of dynamic server-side registration). In other words, a control (view) can be supplied a model which is either a Setting or a Value (and the former is a subclass of the latter). This is how the controls for the changeset status and date are constructed. For example, to create a control which manages the sidebar width, one could do (which also shows how to add a control to the new Publish Settings section):

wp.customize.control.add( new wp.customize.Control( 'pane_width', {
    type: 'number',
    section: 'publish_settings',
    setting: new wp.customize.Value( 300 ),
    label: 'Pane Width',
    priority: 110,
    input_attrs: {
        min: 150,
        max: 1000
    }
} ) );

A plugin could then listen for changes to that control’s setting model to then perhaps store the value in localStorage to persist the user preference for how wide they want their sidebar to be. Being able to construct controls with Value models is a next iteration beyond setting-less controls introduced in 4.5.

Prior to 4.9, in spite of having these symbolic references to a control’s settings the control’s content would require the actual setting ID to be used in any input element links (the bidirectional data binding between a control’s setting and input). For example, the site title control has a default setting pointing to blogname, but in spite of this, the content for the control would contain:

<input type="text" data-customize-setting-link="blogname">

The fact that data-customize-setting-link required an actual setting ID to be used as its value made it difficult to create reusable control templates. This now has been improved in 4.9 by allowing the setting keys to be used in data-customize-setting-key-link attributes:

<input type="text" data-customize-setting-key-link="default">

The WP_Customize_Control::get_link() PHP method has been updated to output a data-customize-setting-key-link attribute with the supplied setting key as its value when the associated setting does not exist. Otherwise, it maintains its prior behavior of using the setting ID as the value for a data-customize-setting-link attribute. The JS logic responsible for linking inputs with settings via elements is now contained in a new linkElements method on Control.

Control Templates

Refer back to the “Pane Width” example above where a type of number was supplied when constructing a base wp.customize.Control class. This would not work prior to 4.9 because there were no content templates defined for base control types. This is no longer the case with the resolution of #30738. If you construct a control and supply a type param that corresponds to an HTML5 input type, then the default control template will be used to render the control. Note that for color inputs, one should use the dedicated ColorControl.

A control’s template is normally derived by its type; for instance, a Background Position control has a type of background_position and then has a wp.template ID of customize-control-background_position-content. The template is normally output by a control’s WP_Customize_Control::content_template() template after the control has been registered via WP_Customize_Manager::register_control_type(). In 4.9 however, this can all be bypassed as controls now understand a templateId param.

An example of supplying an ad hoc templateId when constructing a control can be seen in the changeset status control, where it overrides the template used for this one radio control instance:

var statusControl = new api.Control( 'changeset_status', {
    priority: 10,
    type: 'radio',
    section: 'publish_settings',
    setting: api.state( 'selectedChangesetStatus' ),
    templateId: 'customize-selected-changeset-status-control',
    label: api.l10n.action,
    choices: api.settings.changeset.statusChoices
} );
api.control.add( statusControl );

Alternatively, if you have a custom control that you always want to have a specific template (as when calling WP_Customize_Manager::register_control_type() in PHP), you can now do this entirely in JS by adding templateId to the control subclass’s defaults, as can be seen in the PreviewLinkControl:

var PreviewLinkControl = api.Control.extend({
   defaults: _.extend(
      {},
      api.Control.prototype.defaults,
      {
         templateId: 'customize-preview-link-control'
      }
   )
   /* ... */
});

For some more examples, see answer on the WordPress Development Stack Exchange.

Speaking of WP_Customize_Manager::register_control_type(), the types for panels, sections, and controls now are all registered before the customize_register action is triggered. This means that if you have a plugin that unregisters all customize_register actions in order to create a “blank slate” plugin-specific Customizer, you no longer have to re-register these types to be able to use them.

Date/Time Control

In support of the 4.9 feature for scheduling changesets for publishing (#28721), there is a new control for representing a date/time. The control shows fields for editing the date with optional fields also for editing the time. The control syncs to a setting a date in the Y-m-d H:i:s format. The time in the control can either be presented in 12-hour format or 24-hour format. Additionally, the control can specify that it requires a future date. The PHP class is WP_Customize_Date_Time_Control and the JS class is wp.customize.DateTimeControl. Here is how to create a few of the controls in JS with screenshots for how they appear:

wp.customize.control.add( new wp.customize.DateTimeControl( 'birthdate', {
    label: 'Birthdate',
    description: "Someone was born on this day.",
    section: sectionId,
    includeTime: false,
    setting: new wp.customize.Value( '2000-01-02' )
} ) );

wp.customize.control.add( new wp.customize.DateTimeControl( 'special_datetime', {
    label: 'Special Datetime',
    description: 'There are incrementing digits in this datetime!',
    section: sectionId,
    includeTime: true,
    twelveHourFormat: false,
    allowPastDate: true,
    setting: new wp.customize.Value( '2001-02-03 13:57' )
} ) );

wp.customize.control.add( new wp.customize.DateTimeControl( 'the_future', {
    label: 'Future Date',
    description: 'A unique future date time.',
    section: sectionId,
    includeTime: true,
    twelveHourFormat: true,
    allowPastDate: false,
    minYear: ( new Date() ).getFullYear(),
    setting: new wp.customize.Value( '2222-11-22 11:22:11' )
} ) );

Note that for the twelveHourFormat param you may want to let the site’s own preference determine the value. The site’s time_format option is now exported as wp.customize.settings.timeFormat so you can opt to show 12-hour format if this PHP time format contains a, as this is whether AM or PM is shown. The PHP date format is also exported as wp.customize.settings.dateFormat. See core example.

New Utility Methods

  • wp.customize.utils.highlightButton() is used to bounce a given button to draw a user’s attention. This is used on the save button after changing the status and closing the Publish Settings section. It is also used when creating a new nav menu, to draw attention to the button for adding new nav menu items.
  • wp.customize.utils.getCurrentTimestamp() is the JS-version of a PHP call to current_time( 'mysql', false ). Note that it will attempt to compensate for differences between the server’s clock time and the clock time on the client.
  • wp.customize.utils.getRemainingTime() returns the number of milliseconds until the provided DateTime object.

Updates to Changeset Update Requests

  • wp.customize.requestChangesetUpdate() now allows an args parameter after its initial data parameter. The args can include title, date, autosave, and force. Calling this method will also now short-circuit with a rejected promise if a request is currently being processed. Note that this method is a lower-level version of what the older wp.customize.previewer.save() does when a user hits Save, though the overlap is not complete.
  • Both wp.customize.previewer.save() and wp.customize.requestChangesetUpdate() will trigger a save-request-params event on wp.customize with the underlying request params object provided so plugins can modify the params as required.
  • There is now a wp.customize.previewer.trash() method for trashing any current changeset to reset to the previously published state. An overlay notification (see below) is displayed when the trashing request is made, and once it finishes the Customizer will refresh in a pristine state.
  • In calls to wp.customize.previewer.save(), the request will now be short-circuited if any registered control has an error notification. Previously it would only short-circuit if a registered setting had an error notification. This is used in core for the changeset scheduled date control, to block saving if the user provided an invalid date or one that is not in the future.

Discontinued Customize Loader in Core

There has historically been two separate ways that the Customizer has been loaded. The first is just to navigate straight to /wp-admin/customize.php, and this is what happens if you click “Customize” in the admin bar or the admin menu. However, if you would click on “Customize” from the admin dashboard or any of the “Live Preview” buttons on the themes admin screen, then the Customizer would get opened in a full-screen iframe with the URL being rewritten to look like you are at /wp-admin/customize.php even though the previous page was still present under the iframe overlay.

There were some advantages to this customize-loader in that it allowed the Customizer to be loaded with a spinner animation as well as being able to be quickly closed by just destroying the iframe. However, there were also disadvantages. It was difficult to maintain the two loading methods, the customize-loader made it extremely difficult to implement browser history for navigation in the Customizer (#28536), and there were accessibility problems with the iframe overlay. The customize-loader also added to the overall weight of the page in the browser’s memory. Ultimately, the customize-loader wasn’t necessary because the themes admin screen uses history management to ensure the URL reflects any searches, so the previous search will be restored when closing the Customizer regardless. For these reasons, the use of customize-loader has been discontinued in core (see #40254). It does remain in core for plugins to use, but it may become deprecated in the future.

Outer Sections

The panes that slide out for adding widgets or nav menu items are ad hoc constructs without being built to be re-usable. In 4.9 with the introduction of the Publish Settings pane for managing a changeset’s status and scheduled date, this same kind of pane was needed again. Instead of creating another ad hoc pane, the Publish Settings pane is using a brand new “outer” section. This section type can be expanded along with another non-outer section being expanded at the same time. As this was developed specifically for the needs of the Publish Settings, it should be considered somewhat experimental for plugins to use for their own purposes. It can be instantiated in JS via the wp.customize.OuterSection class, or in PHP by registering a section with the outer type.

Publish Settings pane on right side is an outer section

Notification Improvements

Notifications for controls and their underlying settings were first introduced in 4.6. Now in 4.9 the notifications API has been vastly expanded beyond controls to be integrated throughout the Customizer. In addition to controls, notifications can now also be added to sections and panels (#38794). Instead of there being an ad hoc error notice displayed in the header media section for when the current previewed URL doesn’t show a header video, this is now leveraging section notifications in core. In addition, notifications now recognize a dismissible param which allows the user to remove a notification. Just as a setting or control has a notifications property which contains a collection of Notification instances, so too a panel or section now has the same. Here are some examples of section and panel notifications:

Additionally, there is now a global notification area (see #35210) for meta notifications that aren’t logically related to any specific control, section, or panel. One longstanding defect with the Customizer is what would happen when the user tries to Save changes, but then the request fails due to a server error or connection problem. Now a dismissible error notification is displayed when this happens. The global notification area is also shown when there is an autosave revision that can be restored:

There is also a special overlay notification type that can be added to the global notification area. When an OverlayNotification is added then it will overtake the entire screen. This is used to display notifications for changeset post locking, changeset trashing, theme installation, and theme switching.

As with control templates above, notifications can now be constructed with a templateId string param (or template function param) to customize how a given message is rendered, and to allow for a notification to include additional UI in addition to the message (see #37727). This is used in the changeset post locking notification to add the UI for overriding a lock or linking to the frontend to preview the changes.

Finally, there is now a wp.customize.Notifications that subclasses wp.customize.Values; this is a collection for Notification instances and it is responsible for rendering notifications, including handling calls to wp.a11y.speak(). Controls, sections, and panels each have setupNotifications and getNotificationsContainerElement methods which will start calling the render method when notifications are added or removed from the collection.

Aside: When removing something from a Values instance, a removed event will now be triggered after the value is deleted. Previously, a remove event would be triggered but before the deletion happened.

Read More

Most of the JS changes have been documented inline with JSDoc, so browsing customize-controls.js is recommended to learn more about the new APIs introduced in this release. And yes, this massive file does need to be broken up (#30277), something that we’ll plan to do as part of a Customizer v2 feature plugin where we work to integrate the building blocks of Gutenberg into the Customizer. You may also want to look at the Customize Featured Content demo plugin which tries to showcase as many of the Customize JS APIs as possible, and specifically look at the pull request which updates the plugin’s minimum required version of WP 4.9, so there is a lot of code that can be removed since it is no longer required.

See related tickets:

  • #30738: JS content templates for base WP_Customize_Control
  • #31582: Catching PHP Fatal Errors on WordPress Theme Customizer
  • #35210: Add notification area to Customizer
  • #36167: Improve alignment between settings params between Customizer Controls and Partials
  • #37269: Introduce removed event for wp.customize.Values collection
  • #37727: Allow for customize control notifications to have extensible templates
  • #37964: Allow customizer controls to be encapsulated by accepting pre-instantiated settings
  • #38794: Customize: Allow panels and sections to have notifications just like controls can
  • #39275: Improve wp.customize.previewer.save() for changesets.
  • #39930: Adding documentation for wp.customize properties
  • #42083: Customize: Clean up longstanding annoyances in JS API

See also the wp.customize.CodeEditorControl described in:

Code Editing Improvements in WordPress 4.9

See also changes to the API for themes in the Customizer:

A New Themes Experience in the Customizer

Props to @dlh for contributing to this post.

#4-9, #customize, #dev-notes