Changes to Customizer Panels and Sections in 4.3

WordPress 4.3 contains some important changes to the Panels and Sections APIs. These won’t impact basic usage – add_section() and add_panel(), for example – but could potentially have compatibility issues for any custom panels or sections that override default methods. If you have themes or plugins that create custom sections or panels, please be sure to test with the latest 4.3 beta. Additionally, we’ve built out the API to support dynamically-added and more JS-driven panels and sections in 4.3. Details below.

Changes to the Default Panel & Section UI

Ticket #31336 introduced a new user experience for core Customizer sections, and made some adjustments to panels in the process. Sections in general now slide in sideways (like panels) rather than expanding open vertically in the “accordion” style. This change adds a heading within section containers that contains context for the user’s location within the Customizer hierarchy of sections/panels as well as the navigation back out of the panel.

If you have a custom section that overrides WP_Customize_Section::render(), you will probably need to add the new section header markup that has been added to the base section. Without at least the back button, users could potentially become stuck within an open section because of this core change. Additionally, the panel-back button has moved from a single button in the header (over the Customizer close button) to being contained within the panel header, similarly to the section header. If you’re overriding WP_Customize_Panel::render_content() in a custom panel, you’ll probably need to add the panel back button there. For panels and sections, if you’re overriding the onChangeExpanded method in JavaScript, you would need to implement the core changes there as well, although odds are your implementation is custom enough that the changes to the default objects won’t affect you. Because custom section and panel implementations will vary widely, your best bet is to look at the revised code in trunk and adjust your code accordingly (see wp-includes/class-wp-customize-section.php). However, for basic sections using a UI close to the default one, adding the following to your render_template() method within ul.accordion-section-content is likely to work (note this is a JS template; can be converted to a PHP template fairly easily):



<li class="customize-section-description-container">


	<div class="customize-section-title">
		<button class="customize-section-back" tabindex="-1">
			<span class="screen-reader-text"><?php _e( 'Back' ); ?></span>
		</button>


		<h3>
			<span class="customize-action">
				{{{ data.customizeAction }}}
			</span>
			{{ data.title }}
		</h3>


	</div>


	<# if ( data.description ) { #>


		<div class="description customize-section-description">
			{{{ data.description }}}
		</div>


	<# } #>
</li>


Note that custom sections and panels are generally designed to allow different UI implementations than the default behavior, so in many cases the changes to the default objects shouldn’t break your custom versions.

JavaScript-templated Panels and Sections

Building on the work done in 4.1 for controls, #30737 introduces JavaScript templates for panels and sections. The built-in core panel and section objects now use JS templates to render all instances by default. The PHP-rendered object API is still present and fully supported, but developers are recommended to strongly consider using JavaScript templates for control, section, and panel UI instead. These templates allow objects to be more easily created dynamically and improve performance by loading an object’s data (in JSON format) and a single template for the HTML structure rather than the complete fully-rendered HTML structure for repetitive (PHP-generated) UI.

Menus in the Customizer drove the completion of this API addition, and it facilitates the dynamic creation of Customizer sections and controls when creating new menus. Further plans in this area include using JS templates for the base WP_Customize_Control object by default ( #30738) and building out the API for dynamically-created controls by implementing container templates similar to what panels have ( #30741).

The remainder of this post will outline the various API additions that facilitate this, with code examples. Please review the initial post about JS templates and sections, as the new API for sections and panels is conceptually identical to what was introduced for controls in 4.1, taking a few more steps beyond it.

Registered Panel and Section Types

Similarly to the concept of registered control types introduced in 4.1, WP_Customize_Manager now supports registered section and panel types. For those that haven’t previously worked with custom sections or panels, they’re just like custom controls. You can override different pieces of the base object as needed to create custom UI. A great example in core is WP_Customize_New_Menu_Section, which is displayed as a button that toggles display of its controls inline, rather than a sliding panel.

Registering a section or panel type tells the Customizer to print one copy of the object’s JS template to the page, so that any object with this type can be rendered dynamically. Instances added in PHP will be automatically inserted into the DOM after being rendered from the JS template when ready, and the template can also be used when adding an instance dynamically in JS (for example, when adding a new section when a new menu is created).

Registering panel and section types works just like it does for controls:

add_action( 'customize_register', 'prefix_customize_register' );
function prefix_customize_register( $wp_customize ) {
  // Define a custom section class, WP_Customize_Custom_Section.
  // Register the class so that its JS template is available in the Customizer.
  $wp_customize->register_section_type( 'WP_Customize_Custom_Section' );
}

 

Sending PHP Data to JavaScript

Customizer panel and section data has been passed to the relevant JS models since 4.1, but you’re much more likely to need to send data down when working with JS templates. Anything that you would want access to in render_content() in PHP will need to be exported to JavaScript to be accessible in your section or panel template. Since JS templates are now used by default for sections and panels, most of the core class variables for these objects are passed already. To add custom ones, override WP_Customize_Panel::json()/WP_Customize_Section::json() in your custom subclass. In most cases, you’ll want to merge with the return value of the parent class’s json() method also, to ensure that all core variables are exported as well. Here’s an example from the core nav menu section class:

/**
 * Get section parameters for JS.
 *
 * @since 4.3.0
 * @access public
 * @return array Exported parameters.
 */
public function json() {
	$exported = parent::json();
	$exported['menu_id'] = intval( preg_replace( '/^nav_menu\[(\d+)\]/', '$1', $this->id ) );
	return $exported;
}

Using JS/Underscore Templates

Once you’ve registered your custom section or panel class as a section or panel type and exported any custom class variables, you can create the template that will render the section or panel UI. This works slightly differently for sections and panels, because panels have a distinct container and content that can be overridden separately as needed.

Sections

For sections, you’ll override WP_Customize_Section::render_template() instead of WP_Customize_Section::render(). The render function is now empty in the base core control, so there’s no need to override it if you’re using a custom template. If you are using the existing PHP API, the JS template will not be used if content exists after running the render() function. The default section’s template looks like this in 4.3:

/**
 * An Underscore (JS) template for rendering this section.
 *
 * Class variables for this section class are available in the `data` JS object;
 * export custom variables by overriding WP_Customize_Section::json().
 *
 * @since 4.3.0
 * @access protected
 *
 * @see WP_Customize_Section::print_template()
 */
protected function render_template() {


<li id="accordion-section-{{ data.id }}" class="accordion-section control-section control-section-{{ data.type }}">


	<h3 class="accordion-section-title" tabindex="0">
		{{ data.title }}
		<span class="screen-reader-text"><?php _e( 'Press return or enter to open' ); ?></span>
	</h3>




	<ul class="accordion-section-content">


		<li class="customize-section-description-container">


			<div class="customize-section-title">
				<button class="customize-section-back" tabindex="-1">
					<span class="screen-reader-text"><?php _e( 'Back' ); ?></span>
				</button>


				<h3>
					<span class="customize-action">
						{{{ data.customizeAction }}}
					</span>
					{{ data.title }}
				</h3>


			</div>


			<# if ( data.description ) { #>


				<div class="description customize-section-description">
					{{{ data.description }}}
				</div>


			<# } #>
		</li>


	</ul>


</li>


}

As you can see, Underscore-style templates are very similar to PHP. As more and more of WordPress core becomes JavaScript-driven, these templates are becoming increasingly more common. Besides the Customizer, sample template code in core can be found in media, revisions, the theme browser, and even in the Twenty Fifteen theme, where a JS template is used to both save the color scheme data and instantly preview color scheme changes in the Customizer. The best way to learn how these templates work is to study similar code in core.

Panels

Panels have two distinct pieces to their UI: the container, which is displayed in a list with other panels and sections at the top level of the Customizer UI, and the content, which is the context that the Customizer enters when the panel is opened. Note that this distinction is due to the philosophical design of Panels to be distinct contexts, rather than simply being a way to group related sections together. This is also based on the distinction between the container and the content in control objects. However, the distinction between panels and sections in terms of UI is less visually clear in WordPress 4.3.

Panel containers are rendered with the render_template() function, and the content template is inserted into the container in JS by looking for .accordion-sub-container. It will rarely be necessary to override the container template in a custom panel, as the custom UI will typically go in the content of the panel. The default panel template looks like this:

/**
 * An Underscore (JS) template for rendering this panel's container.
 *
 * Class variables for this panel class are available in the `data` JS object;
 * export custom variables by overriding WP_Customize_Panel::json().
 *
 * @see WP_Customize_Panel::print_template()
 *
 * @since 4.3.0
 * @access protected
 */
protected function render_template() {
	?>


	<li id="accordion-panel-{{ data.id }}" class="accordion-section control-section control-panel control-panel-{{ data.type }}">


		<h3 class="accordion-section-title" tabindex="0">
			{{ data.title }}
			<span class="screen-reader-text"><?php _e( 'Press return or enter to open this panel' ); ?></span>
		</h3>




		<ul class="accordion-sub-container control-panel-content"></ul>


	</li>


	<?php
}

Panel contents are rendered with the content_template() function, similarly to the way control templates work. In the future, this part of the API will be completed with the addition of container templates for controls, see #30741. The default panel content template in 4.3 looks like this:

/**
 * An Underscore (JS) template for this panel's content (but not its container).
 *
 * Class variables for this panel class are available in the `data` JS object;
 * export custom variables by overriding WP_Customize_Panel::json().
 *
 * @see WP_Customize_Panel::print_template()
 *
 * @since 4.3.0
 * @access protected
 */
protected function content_template() {
	?>


	<li class="panel-meta customize-info accordion-section <# if ( ! data.description ) { #> cannot-expand<# } #>">
		<button class="customize-panel-back" tabindex="-1"><span class="screen-reader-text"><?php _e( 'Back' ); ?></span></button>


		<div class="accordion-section-title">
			<span class="preview-notice"><?php
				/* translators: %s is the site/panel title in the Customizer */
				echo sprintf( __( 'You are customizing %s' ), '<strong class="panel-title">{{ data.title }}</strong>' );
			?></span>
			<button class="customize-help-toggle dashicons dashicons-editor-help" tabindex="0" aria-expanded="false"><span class="screen-reader-text"><?php _e( 'Help' ); ?></span></button>
		</div>


		<# if ( data.description ) { #>


			<div class="description customize-panel-description">
				{{{ data.description }}}
			</div>


		<# } #>
	</li>


	<?php
}

In core, the nav menus panel, which adds screen options for the panel, is a good example of a panel with a custom content template.

#4-3, #customize, #dev-notes