Building and Managing Dynamic Multi-part Pages with WordPress

Twenty Seventeen is the first bundled theme to provide a way to create multi-part pages with WordPress, via a front page sections option that features multiple pages on the front page. This is useful for largely single-page sites, but limits the functionality of a front page as a showcase for and gateway to content throughout larger sites.

For sites with more content, and even multiple post types, there isn’t an established pattern for building pages that feature multiple pieces of content. Core archives exist for single taxonomy terms, authors, and post types, but widgets or custom implementations are needed to tie these pieces together. Twenty Fourteen explored strategies for high-content sites by providing numerous widget areas, specialized widgets, and featured content on the front page. The featured content concept provides two major benefits: visitors are exposed to a diverse array of content that would otherwise be buried deeper in a site, and the featured items are updated automatically as new content is created.

WordPress is especially strong in its use of taxonomies to group content together. Extending this functionality one more level to allow terms and posts to be grouped into navigational indexes provides immense power to build complex pages that can be managed within WordPress rather than being defined in code. Featuring taxonomy terms in addition to single posts makes these multi-part pages dynamic, with featured content updating automatically as new items are published.

It can be difficult to visualize the end goal for these types of pages, so here are a few examples of dynamic multi-part pages that should be possible to build and manage with WordPress:

Suggested Solution: Menus

WordPress menus organize navigation to different elements of a site. This is traditionally handled as vertical or horizontal lists, often with hierarchy. Recently, themes have expanded this concept by presenting additional layouts such as social menus that display icons in place of text labels. Some themes show the menu item’s description (a core feature) for additional context.

From a technical perspective, menus have a dedicated post type with a post for each menu item, and a taxonomy term for each menu to group the items together. I became far too familiar with the benefits and challenges of this approach in the year that I spent bringing menu management into the customizer with live preview. The potential to do more with menus struck me as the biggest opportunity for themes (and plugins) to innovate site management without major core changes.

The basic concept of a menu is a way to organize content on a site. And in core, menus can contain items for posts and terms of any type (that supports it), as well as custom links. What if we took this concept a step further, encouraging themes to pull additional elements such as featured images or even post excerpts or content from the objects associated with menus?

While this may seem like an odd proposal if you think of menus as primarily useful for navigation, setting this up in a dynamic way actually preserves the organizational intent by providing navigation to content within the site. The difference is that more context is available for each item, and some items may be presented largely in their entirety without linking off to a single post view, perhaps enabling the option for more of a single page site feel. Taxonomy terms featured in a menu could display the first few posts within them as well as any taxonomy meta and the term description, opening the doors for a deeper showcase of content with a menu and introducing dynamic hierarchy by showing content within terms.

Perhaps the biggest advantage of this approach is that it can be implemented right now with the support of a robust management interface with live preview in WordPress core. You don’t need to build any backend functionality – core provides it already with the ability to select any content from the site to add to a menu, reordering with drag and drop, and live preview of the end product in the customizer out of the box. Everything is easily portable across themes, with menus needing assignment to the appropriate locations but otherwise persisting across theme switches. There are things that core can do to improve the experience further for developers and users, but it works today. If theme developers begin to experiment with using menus to build and manage multi-part pages, users stand to benefit significantly in their ability to control how they present their content.

I should note that this is not “content blocks.” While that term typically refers to improved management of content within a post object, this proposal is intended to improve the way that different pieces of content can be organized.

Proof of Concept: Dynamic Seventeen Theme

To showcase this concept, in both code and a functional theme, I’ve created the Dynamic Seventeen theme. A child theme of Twenty Seventeen, it replaces the static page-based front page sections with dynamic menus-based content. It also adds a page template that turns any page into a dynamic content page, automatically creating an associated menu location for each page with the template. Page templates are not a fantastic experience currently, but this is a good example of how they can provide useful functionality.

Dynamic Seventeen sticks with the base design of Twenty Seventeen, featuring a list-type layout interspersed with large featured images. It also extends the concept of the two column layout by providing the ability to add subtitles or descriptive text to each section, under the post title. Leveraging the menu item description field for this is an example of how menus can provide additional functionality over other approaches without custom admin UI.

The child theme is available on WordPress.org repository. The code is not particularly complex, using a custom nav menu walker to retrieve information about objects associated with menu items and display templates matching the rest of the theme. I encourage theme developers to take this first example and adapt it to their themes, adjusting exactly what content is shown, what content types are supported (for example, not showing custom links), and the markup and styling of the results as needed.

Gif demonstration of setting up the Dynamic Seventeen themr in the customizer with menus.
Dynamic Seventeen dynamic content management with menus demonstration.

The Future of Dynamic Multi-part Pages in WordPress

Dynamic, multi-part pages are an important aspect of many larger sites because they offer a gateway to a diverse array of content in the site. But if site owners are unable to manage their content without hiring a developer, WordPress is only realistically useful in the long run for businesses with in-house development teams. Empower users to organize and showcase their own content by creating usable interfaces with live preview, allowing them to trust that their changes work as intended.

While the menus approach works without any core changes, there are several things that would make the user experience better for this and other innovative uses of menus:

  • #38957 – Customize Menus: Menu locations should be able to opt-out of menu item types that can be added to associated menus
  • #38956 – Customize Menus: menus assigned to locations with limited depths should not allow deeper depths
  • #18584 – Nav menus need more hooks for extensibility

There is also a core ticket to expand the functionality of static multi-part pages like Twenty Seventeen’s front page. I remain skeptical of this approach because of the significant technical debt and future maintenance associated with the major additions proposed, and the redundancy with the existing functionality of menus. Let’s improve what we have and build on it rather than turning to new features that duplicate functionality and ultimately further fragment the user experience.

WordPress 4.0 Customizer API Improvements

I cross-posted much of this post to Make WordPress Core before WordPress 4.0 Beta 1. I’ll be updating this version with more examples throughout the beta period.

WordPress 4.0 features several new additions to the Customizer API (see also Theme Customization API). In this post, I’ll discuss the improvements in detail.

Customizer Panels

The Customizer now includes a new way to group options together: panels. Just like a section is a container for controls, a panel is a container for sections. This was implemented in #27406. In WordPress Core, widget management in the customizer has been consolidated into the “Widgets” panel. Instead of having as many sections as there are widget areas making a mess out of the customization experience, only one widgets section appears alongside the other customizer sections. But when you select it, rather then sliding down like regular sections, it slides over to reveal a new context for the entire customizer controls area. My Menu Customizer project also relies on this new panels functionality, as all menus can be consolidated into one panel. But themes and/or plugins with extensive options may also wish to create their own panels to better organize their controls, so we built an API for customizer panels. The Panels API is easy-to-use and works almost identically to the existing customizer section API. Add a panel with the following, within the customize_register action:

$wp_customize->add_panel( 'panel_id', array(
	'priority' => 10,
	'capability' => 'edit_theme_options',
	'theme_supports' => '',
	'title' => '',
	'description' => '',
) );

As always, only use the arguments where you aren’t using the default values. Note that the panel will not be displayed unless sections are assigned to it. To add a section to a panel, just use the panel id for the new panel argument:

$wp_customize->add_section( 'section_id', array(
	'priority' => 10,
	'capability' => 'edit_theme_options',
	'theme_supports' => '',
	'title' => '',
	'description' => '',
	'panel' => 'panel_id',
) );

You may notice that $wp_customize->add_panel and $wp_customize->add_section have the same arguments (other than panel, of course). This is because panels are a special type of section; technically speaking, WP_Customize_Panel extends WP_Customize_Section. Your sections are backwards-compatible: you can add the panel argument to existing sections without issues. However, you do need to check for the existence of WP_Customize_Manager->add_panel() if you’re maintaining pre-4.0 compatibility. As with Customizer Sections, you can access and modify Panels via:

  • $wp_customize->get_panel( $id );
  • $wp_customize->remove_panel( $id );

New Built-in Customizer Controls

WordPress core now provides support for a much wider array of Customizer controls. Implemented in #28477, these changes eliminate the need to create custom controls for most common use cases. The textarea type is now supported in core. For any type of input that uses an input element, you can simply specify the type attribute using the type parameter of $wp_customize->add_control(). Here’s an example:

$wp_customize->add_control( 'setting_id', array(
	'type' => 'url',
	'priority' => 10,
	'section' => 'title_tagline',
	'label' => 'URL Field',
) );

Which results in the following markup in the Customizer:

<li id="customize-control-setting_id" class="customize-control customize-control-url">
<label>
<span class="customize-control-title">URL Field</span>
<input type="url" value="" data-customize-setting-link="setting_id">
</label>
</li>

This is pretty powerful, as you can now use the built-in WP_Customize_Control for most common use-cases rather than creating a custom control. But what about input types like number and range that require additional attributes like min, max, and step?

New Built-in Customizer Control Parameters

First of all, all of the built-in Customizer controls (including the custom controls such as WP_Customizer_Color_Control) now support descriptions, just like Customizer sections have descriptions (see #27981). This was much-needed and allows for inline help text at the control level. More interestingly, to complement the new support for arbitrary input types, a new input_attrs parameter allows you to add attributes to the input element (also implemented in #28477). This extends beyond just using min, max, and step for number and range, to the ability to add custom classes, placeholders, the pattern attribute, and anything else you need to the input element. Here’s an example:

$wp_customize->add_control( 'setting_id', array(
	'type' => 'range',
	'priority' => 10,
	'section' => 'title_tagline',
	'label' => 'Range',
	'description' => 'This is the range control description.',
	'input_attrs' => array(
		'min' => 0,
		'max' => 10,
		'step' => 2,
		'class' => 'test-class test',
		'style' => 'color: #0a0',
	),
) );

Which results in the following markup in the Customizer:

<li id="customize-control-setting_id" class="customize-control customize-control-range">
	 <label>
		 <span class="customize-control-title">Range</span>
		 <span class="description customize-control-description">This is the range control description.</span>
 <input type="range" min="0" max="10" step="2" class="test-class test" style="color: #0a0;" value="" data-customize-setting-link="setting_id">
	 </label>
 </li>

Which displays as follows (in Chrome 35): customizer-4.0-range-control

The ability to add classes is particularly useful if you need to target specific controls with CSS or JS, but it doesn’t need to have any special markup. I’m using this in the Menu Customizer for the Menu Name field, which is just an ordinary text control with a special setting type.

Contextual Controls

Customizer controls can now be displayed or hidden based on the Customizer’s preview context. For example, options that are only relevant to the front page can be shown only when the user is previewing their front page in the Customizer (see #27993). This is already implemented in core for Widgets; Widgets have always been contextually faded and shown/hidden based on their visibility in the preview, but this functionality is now built off of the core active_callback API in both PHP and JS. There are three different ways to specify whether a given control should only be displayed in a certain context. The first, and most straightforward, is to use the active_callback argument in $wp_customize->add_control().

$wp_customize->add_control( 'front_page_greeting', array(
	'label'      => __( 'Greeting' ),
	'type' => 'textarea',
	'section' =>
	'title_tagline',
	'active_callback' => 'is_front_page',
) );

Note that you may use either built-in conditional functions or a custom function. If you have a custom control (via a subclass of WP_Customize_Control) and a custom callback function, you can skip the active_callback argument and override the active_callback function instead:

class WP_Greeting_Control extends WP_Customize_Control {
	// ...

	function active_callback() {
		return is_front_page();
	}
}

Finally, the customize_control_active filter will override all of the other active callback options and may be a better solution in certain cases (note that this particular example will be avoidable with future work on expanding the Customizer’s JS API, and does not hide the title_tagline section, only the controls in it):

function titletagline_customize_control_active_filter( $active, $control ) {
	if ( 'title_tagline' === $control->section ) {
		$active = is_front_page();
	}
	return $active;
}
add_filter( 'customize_control_active', 'titletagline_customize_control_active_filter', 10, 2 );

In addition to the PHP API for contextual controls, you can override the control-visibility-toggle function on the JS side. By default, controls will slideUp and slideDown as they become visible or hidden when the Customizer preview is navigated. If you’re familiar with the Customizer control JS API (see wp-admin/js/customize-controls.js, and wp.customize.Control), the Widgets implementation of a custom toggle function is a good example:

api.Widgets.WidgetControl = api.Control.extend({
// ...
   /**
    * Update widget control to indicate whether it is currently rendered.
    *
    * Overrides api.Control.toggle()
    *
    * <a href='http://profiles.wordpress.org/param' class='mention'>@param</a> {Boolean} active
    */
    toggle: function ( active ) {
        this.container.toggleClass( 'widget-rendered', active );
    },
// ...
) };

/**
 * Extends wp.customize.controlConstructor with control constructor for widget_form.
 */
$.extend( api.controlConstructor, {
    widget_form: api.Widgets.WidgetControl,
} );

Changes to the customize_update_ and customize_preview_ Actions

You probably already know that the Customizer supports both option and theme_mod types for settings. But did you know that you can register arbitrary types? Since this is generally undocumented, I’ll show how it works (this has been in place since 3.4):

$wp_customize->add_setting( 'setting_id', array(
	'type' => 'custom_type',
	'capability' => 'edit_theme_options',
	'theme_supports' => '',
	'default' => '',
	'transport' => 'refresh',
	'sanitize_callback' => '',
	'sanitize_js_callback' => '',
) );

There are a few actions that you can use to handle saving and previewing of custom types (option and theme_mod are handled automatically). Namely, customize_update_$type and customize_preview_$type are useful here. Previously, the value of the setting was passed to these actions, but there was no context. In 4.0, via #27979, the WP_Customize_Setting instance is passed to these actions, allowing more advanced saving and previewing operations. Here’s an example from my Menu Customizer project:

function menu_customizer_update_menu_name( $value, $setting ) {
...
	// Update the menu name with the new $value.
	wp_update_nav_menu_object( $setting->menu_id, array( 'menu-name' => trim( esc_html( $value ) ) ) );
}
add_action( 'customize_update_menu_name', 'menu_customizer_update_menu_name' );

This part of the Customizer API is a bit too complex to fully explain here, as most of it already existed, but suffice to say that the addition of the setting instance to these actions greatly expands the possibilities of working with custom setting types in the Customizer.

New “customize” Capability

Note: this API component is coming in 4.0 beta 2.

The Customizer has been decoupled from edit_theme_options in favor of a customize meta capability, which is mapped to edit_theme_options by default. This allows for wider use of the Customizer’s extensive capability-access options, which are built into panels, sections, and settings. Additionally, this makes it possible to allow non-administrators to use the customizer for, for example, customizing posts. This change is an important step to expanding the scope of the Customizer beyond themes. See #28605. To allow users with lower capabilities to access the Customizer, use something like the following:

function allow_users_who_can_edit_posts_to_customize( $caps, $cap, $user_id ) {
        $required_cap = 'edit_posts';
        if ( 'customize' === $cap && user_can( $user_id, $required_cap ) ) {
                $caps = array( $required_cap );
        }
        return $caps;
}
add_filter( 'map_meta_cap', 'allow_users_who_can_edit_posts_to_customize', 10, 3 );

Customizer Conditional Function

The new is_customize_preview() conditional function can be used to check whether the front-end is being displayed in the Customizer. The naming derives from the fact that the term “preview” applies to both theme previews and previewing changes before saving them. See #23509 for some sample use-cases from WordPress.com.

Examples

Look at the Widget Customizer and Menu Customizer code for more examples of many of these features. Please feel free to ask in the comments if you have you questions with any of these implementations; I spent a good amount of time preparing and reviewing the associated patches and soliciting feedback in the process of adding these changes to core.

Use This API for Good, not Evil

These new additions to the Customizer API greatly expand the ability to create intricate options for users. But please don’t forget about WordPress’ core philosophies. So, before you go adding entire panels full of new options, remember to strive for simplicity and design for the majority, even in themes and plugins. These additions make the Customizer better for developers; now, let’s make it as good as it can be for our end-users.

GSoC Menu Customizer Revised Schedule/Scope

Previously: GSoC Project Proposal: WordPress Menu Customizer.

Week 1 – 5/19: Introduce the ability to view all existing menus as customizer sections with menu items as customizer controls.

Week 2 – 5/26: Add the ability to edit menus (change labels, attributes, re-order items), including a temporary solution that includes the screen options found on the existing menus screen. Run user tests on the existing menu management screen to inform UI decisions for the initial iteration, coordinating with the WordPress core UI team.

Week 3 – 6/2: Implement an input-type-agnostic re-ordering mode, merging the existing UIs on the Menus screen and in the Widget Customizer (should particularly help with touch & keyboard input). Implement a version of #27406 into the plugin (pending its completion for core, potentially also assist with the core patch if needed), to maintain 3.9 compatibility. Move all menus (customizer sections) to the menus/navigation super-section, and implement the theme location controls in the new interface. Begin implementing a panel to add new widgets, potentially re-using components from the Widget Customizer’s equivalent UI (and potentially making it more easily re-usable outside of core).

Week 4 – 6/9: Create the ability to add items to menus, utilizing the slide-out panel from the Widget Customizer. For the first iteration, include sections containing pages, custom links, and categories, similar to those on the existing menus page.

Week 5 – 6/16: Second iteration of the add-new interface. Introduce a global search feature, an experimental approach to adding custom links, add all appropriate post types and taxonomies, and create a one-click add/remove menu items mode. Implement any additional ideas for adding items proposed by the WordPress core UI team.

Week 6 – 6/23: Midterm evaluations – critical functionality should be complete (editing & adding to menus, changing menu locations). Publish plugin (with WordPress 3.9 compatibility) on WordPress.org repository to facilitate community testing & feedback. Begin extensive user testing to facilitate further UI iterations. Add ability to create new menus, and delete menus, with the appropriate corresponding menu location UIs adapting accordingly.

Week 7 – 6/30: Address the need to implement screen options into the new menus interface. Work through the list of outstanding issues with the implementation, and begin responding to community feedback. Much of these will likely be UI/UX quirks that come up through the development process and are delayed in favor of completing the basic functionality. Examples could include dealing with deep menu-nesting (sub-menus) within the narrow customize controls area.

Week 8 – 7/7: Implement refinements for simplified use-cases, such as those added in WordPress 3.6 (#23119). Continue addressing bug fixes and enhancements based on community feedback. Add new hooks and create usage examples for them (#18584), if scaling issues can be resolved.

Weeks 9 – 12 – 7/14-8/4: Run user tests on the existing menus screen and the Menu Customizer. Iterate the UI and codebase based on user tests and feedback from WordPress core designers and developers, giving special attention to the experience for new users and a stronger emphasis on the ability to use menus in widget areas. Ensure the code is thoroughly documented and meets all core style guidelines. Test in all supported browsers and devices, with as many themes as possible, and with as many edge-cases as is feasible (ie, massive menus, huge numbers of menus, deep hierarchies, etc.). Fix bugs as they come up. Address whether the functionality is ready for core. If all goes well, consider whether the Menu Customizer could potentially replace the existing menus screen for supported users and develop approaches for user-discovery (for example, deep-linking).

Week 13 – 8/11: Prepare a core patch for the plugin merge, and draft final proposals for inclusion of the feature in core. Final project, as both a functioning plugin and a core patch, should be competed by the firm pencils-down date of 8/18. In the event that the plugin is unlikely to be accepted for core inclusion, continue development of outstanding issues and gathering feedback from the community, with the goal of reaching a good stopping point for the official end of GSoC.