WordPress.org

Make WordPress Core

#27993 closed enhancement (fixed)

Support for contexts in the Customizer

Reported by: danielbachhuber Owned by:
Milestone: 4.0 Priority: normal
Severity: normal Version:
Component: Customize Keywords: has-patch commit
Focuses: ui, javascript Cc:

Description

To provide contextually-relevant customization experiences, it would be nice if Customizer sections and controls supported contexts like is_home() or is_singular().

Attachments (2)

27993.diff (11.8 KB) - added by westonruter 13 months ago.
Initial patch. PR: https://github.com/x-team/wordpress-develop/pull/26
27993.2.diff (667 bytes) - added by kovshenin 12 months ago.

Download all attachments as: .zip

Change History (31)

comment:1 @ircbot16 months ago

This ticket was mentioned in IRC in #wordpress-dev by danielbachhuber. View the logs.

comment:2 @westonruter16 months ago

For an example for how to accomplish this, see the code for Widget Customizer now in core. It toggles customizer sections for widget areas based on whether there is a corresponding dynamic_sidebar() call used in the rendered preview.

comment:3 @danielbachhuber15 months ago

This is what I ended up with: https://github.com/INN/Largo/pull/228/files

I suppose the scope of this ticket would be able to register one or more contexts with a section or control, and have the Customizer do essentially this PR behind the scenes.

comment:4 @nacin15 months ago

Pretty cool idea.

comment:5 @westonruter15 months ago

Yes. Love this. It could be as simple as the Customizer Section or Customizer Control taking a callback argument that returns true or false based on whether or not it is relevant to the currently-previewed URL. Each section and control could be iterated over in the preview, and the callbacks could be fired and then exported to the parent customizer frame which in turn would toggle the visibility of the respective Sections and Controls. (We may want to use the Customizer Setting instead of the Customizer Control for attaching these context callbacks, as I believe this would more easily facilitate dynamically-created Controls since the settings are what get POSTed to the preview, not the controls.)

Just to note that the Widget Customizer takes this same approach of passing upstream the context (which dynamic_sidebar() calls are used at the previewed URL), but it's now using postMessage passing instead of doing direct cross-frame communication. We were using cross-frame communication initially and I never encountered a problem with cross-domain security (since the previewed is rendered via document.write()), but just to be safe we used the message passing system that the Customizer makes available.

IFRAME: customize-preview-widgets.js

this.preview.bind( 'active', function() {
	self.preview.send( 'rendered-sidebars', self.renderedSidebars );
	self.preview.send( 'rendered-widgets', self.renderedWidgets );
} );

Customizer parent frame: customize-widgets.js

// Update widget control to indicate whether it is currently rendered
api.Widgets.Previewer.bind( 'rendered-widgets', function( renderedWidgets ) {
	var isRendered = !! renderedWidgets[self.params.widget_id];
	self.container.toggleClass( 'widget-rendered', isRendered );
} );

/* ... */

// Update the model with whether or not the sidebar is rendered
api.Widgets.Previewer.bind( 'rendered-sidebars', function( renderedSidebars ) {
	var isRendered = !! renderedSidebars[self.params.sidebar_id];
	registeredSidebar.set( 'is_rendered', isRendered );
} );

comment:6 @westonruter15 months ago

@danielbachhuber: Regarding the need to reduce clutter in the Customizer by not always showing all sections (e.g. hiding global ones), one approach is suggested in #27406 where we could introduce a Customizer Section Page, or even just electing for certain Customizer Sections to open in a maximized view, where all other sections are removed from view until the user goes back up a level.

comment:7 @westonruter14 months ago

When implemented, the Widget Customizer code referred to above should be reworked to leverage these generalized customizer contexts.

comment:8 @ircbot13 months ago

This ticket was mentioned in IRC in #wordpress-dev by westonruter. View the logs.

comment:9 follow-up: @westonruter13 months ago

  • Focuses ui javascript added
  • Keywords has-patch added; needs-patch removed
  • Milestone changed from Future Release to 4.0

In 27993.diff, introduce WP_Customize_Control::active() to access whether the control is relevant to the current context (i.e. to the current URL being previewed). Control can indicate its active state by a subclass overriding the active_callback method, by supplying a callable active_callback argument into the control’s constructor, or by filtering customize_control_active.

When the preview loads, the active controls are sent along with the ready state as a activeControls argument. Each control has a new active state which gets toggled based on the corresponding values in activeControls. By default, when a control's active state becomes false, the control will slideUp, and then slideDown when it becomes true again. Controls may override this default toggle behavior by overwriting this method.

I've implemented this new active capability for Widget Customizer. The toggle method for the WidgetControl has been overridden to toggle the widget-rendered class name on the control container, which results in the partially-faded widget control title for widgets that are not rendered in the current preview (e.g. for Jetpack's Widget Visibility). Likewise, the toggle method for the SidebarControl has also been overridden to toggle the visibility the containing Customizer section when the control's active state is changed.

Ideally when all controls in a given section are not active, the section would in general also collapse automatically. The same goes for the new Customizer panels (#27406). However, there is currently no JS models provided for managing the state of the Sections or Panels. I've filed #28709 to introduce these models.

For some examples.

To add a control which only appears on the front page, presumably because only the front-page.php template renders the associated setting, you may supply is_front_page as the active_callback argument:

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

Alternatively, you may do so via a subclass:

class WP_Greeting_Control extends WP_Customize_Control {
	// ...

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

Or you may do so via the customize_control_active filter:

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

In each case, the active_callback will get fired when customize.php initially loads, and then again when the preview loads (and for each page load as the user navigates around the site within the preview). Depending on of the control is expected to be hidden more than shown, then it may be desirable for the callback to return false if is_admin() so that it would be hidden by default, and then only appear when the user visits a URL for which the control is contextual.

comment:10 @celloexpressions13 months ago

This is really awesome. Looking through the patch, the implementation is pretty solid. Having three different ways to specify the callback is really good, and they all fit within the existing API structures as I would expect. It lends itself to both simple usages like is_front_page and complex ones like widgets well. And it's extendable on the JS side too.

We should make sure that the docs specify the priority of the different callback options, looks like it's: filter overrides subclass overrides parameter, which makes sense.

I think we should definitely be able to get this into 4.0.

comment:11 @westonruter13 months ago

  • Keywords commit added

comment:12 @ircbot13 months ago

This ticket was mentioned in IRC in #wordpress-dev by helen. View the logs.

comment:13 @SergeyBiryukov13 months ago

In 29051:

Customizer: Introduce WP_Customize_Control::active() method to determine whether the control is relevant to the current context (i.e. to the current URL being previewed).

Control can indicate its active state by a subclass overriding the 'active_callback' method, by supplying a callable 'active_callback' argument into the control's constructor, or by filtering 'customize_control_active'.

props westonruter.
see #27993.

comment:14 follow-up: @BFTrick13 months ago

Maybe this is a newb mistake but I did some testing and couldn't get this to work. I applied the patch (I can see the updated source code applied), and modified a TwentyFourteen control (screenshot: http://cld.wthms.co/xG1I) with the active_callback argument and no matter where I go on the site via the customizer I still see the control.

Am I doing something wrong here? Am I leaving something obvious out?

comment:15 in reply to: ↑ 14 @westonruter13 months ago

Replying to BFTrick:

Maybe this is a newb mistake but I did some testing and couldn't get this to work. I applied the patch (I can see the updated source code applied), and modified a TwentyFourteen control (screenshot: http://cld.wthms.co/xG1I) with the active_callback argument and no matter where I go on the site via the customizer I still see the control.

Am I doing something wrong here? Am I leaving something obvious out?

Humm, works for me. Now that it has been committed to core, can you try again to see if your addition of the active_callback argument to the featured_content_layout control has the desired effect? Perhaps there was a problem with applying the patch.

I tried a similar test on the latest on trunk [29057], but used an active_callbackwhich returned a random true/false value. As expected, the control expanded and collapsed randomly as I navigated around the site in the customizer preview:

  • src/wp-content/themes/twentyfourteen/inc/customizer.php

    function twentyfourteen_customize_register( $wp_customize ) { 
    5454                        'grid'   => __( 'Grid',   'twentyfourteen' ), 
    5555                        'slider' => __( 'Slider', 'twentyfourteen' ), 
    5656                ), 
     57                'active_callback' => function () { return (bool) rand( 0, 1 ); }, 
    5758        ) ); 
    5859} 
    5960add_action( 'customize_register', 'twentyfourteen_customize_register' ); 

comment:16 @BFTrick13 months ago

Yep. Looks like it was a noob mistake. I used the 4.0-b1 release and it worked just fine. Well done sir. :)

comment:17 @DrewAPicture13 months ago

In 29156:

Inline documentation cleanup for 4.0 audit.

phpDoc tweaks for methods, properties, and filters added in [29051]:

  • WP_Customize_Control->$active_callback property
  • WP_Customize_Control::active() method
  • WP_Customize_Control::active_callback() method
  • WP_Widget_Area_Customize_Control::active_callback() method
  • WP_Widget_Form_Customize_Control::active_callback() method
  • 'customize_control_active' filter

Added in [28930]:

  • WP_Customize_Control::input_attrs() method

See #27993 and #28885.

comment:18 @DrewAPicture13 months ago

In 29159:

Inline documentation cleanup for 4.0 audit.

phpDoc tweaks for methods, properties, and filters added in [29051]:

  • WP_Customize_Widgets::is_widget_rendered() method
  • WP_Customize_Widgets::is_sidebar_rendered() method

See #27993 and #28885.

comment:19 in reply to: ↑ 9 @philiparthurmoore13 months ago

I'm coming to this late, so apologies in advance if this has already been addressed. A question regarding the following statement:

Replying to westonruter:

Ideally when all controls in a given section are not active, the section would in general also collapse automatically. The same goes for the new Customizer panels (#27406). However, there is currently no JS models provided for managing the state of the Sections or Panels. I've filed #28709 to introduce these models.

So that I'm clear about this enhancement, does this apply to both controls and sections or only controls? active_callback currently works beautifully with add_control but it does not seem to affect add_section. I can see a case where entire sections are contextual, and custom controls (say, five or so) inside of a context-dependent section wouldn't need active_callback if the add_section call itself had active_callback on it.

Again, sorry if I've missed something above or in other discussions. Want to make sure that I'm clear on this change, as I'm already implementing this against Core in a few themes. I'm a big fan of this enhancement.

comment:20 follow-up: @celloexpressions13 months ago

That would probably be how we'd want to do that - if the section is hidden, all of the controls inside of it would also be hidden, and vice-versa. But this isn't implemented at all for add_section or add_panel yet - that'll come once we get an expanded JS API to support it, hopefully in 4.1. For now, core support for contextual Customizer UI is limited to controls.

In 4.0, you have to manually hide empty sections where all controls are hidden, and although it's possible (widget areas do it), it's messy.

We'll likely try to use exactly the same API for contextual sections when we do get there. Worth noting that when we do implement it for sections we'll get it for panels with minimal extra work since panels are sections.

comment:21 @ircbot13 months ago

This ticket was mentioned in IRC in #wordpress-dev by celloexpressions. View the logs.

comment:22 in reply to: ↑ 20 @philiparthurmoore13 months ago

Replying to celloexpressions:

That would probably be how we'd want to do that - if the section is hidden, all of the controls inside of it would also be hidden, and vice-versa. But this isn't implemented at all for add_section or add_panel yet - that'll come once we get an expanded JS API to support it, hopefully in 4.1. For now, core support for contextual Customizer UI is limited to controls.

Sounds great. Until a more section/panel-specific hiding mechanism is in place I think I'll hold off on trying to get too clever with sections that have controls that are all contextual. Seems like it'd be a lot of messy code, as you've said. Thanks for clearing everything up for me!

comment:23 @ircbot13 months ago

This ticket was mentioned in IRC in #wordpress-dev by celloexpressions. View the logs.

comment:24 @celloexpressions13 months ago

  • Resolution set to fixed
  • Status changed from new to closed

Looks like this is holding up well. Please reopen with any issues until 4.0, or open a new ticket and reference this one.

comment:25 follow-up: @lancewillett12 months ago

  • Resolution fixed deleted
  • Status changed from closed to reopened

Noticed this isn't fully back compat, the Jetpack "Social Links" Customizer panel controls are hidden after the changes in r29051. Looks like specifically the control.active.bind "active" value is true during the Customizer load but false just as it ends loading.

To repeat:

  1. Activate latest Jetpack version (3.0.2) on an install of latest WP trunk (4.0-beta2-20140729)
  2. Enable "Publicize" module and connect to a social service like Twitter, Facebook, etc
  3. Go to Customizer and as the page loads, open the "Connect" panel -- you'll see the controls are visible
  4. Just before the page is loaded fully the controls disappear as display: none is added to the container element

(Source code for the plugin.)

@kovshenin12 months ago

comment:26 in reply to: ↑ 25 @kovshenin12 months ago

  • Keywords commit removed

Replying to lancewillett: Looks like the problem here is that Jetpack checks is_admin() before hooking to customize_register. That code runs both on the customizer frame as well as the preview frame, and for the preview frame is_admin() returns false, so the preview frame has really no idea about the registered controls.

Proposed fix in 27993.2.diff which doesn't touch controls that the preview frame has no information about. Here's also some simple plugin code to reproduce.

comment:27 @celloexpressions12 months ago

  • Keywords commit added

+1 for 27993.2.diff. This was also causing issues with dynamically-added controls in the Menu Customizer, and controls that aren't previewed. We should default to showing controls that the preview doesn't know about.

Only hooking into customize_register if is_admin() is not a good idea (considering several potential enhancements coming in the future), and simply is unnecessary. Generally, it's a bad idea to consider the Customizer to be part of wp-admin; rather, it's its own thing that bridges the front-end and the admin, and will become more front-end-oriented moving forward.

comment:28 @SergeyBiryukov12 months ago

In 29329:

Customizer: Don't hide controls the preview frame has no information about.

props kovshenin.
see #27993.

comment:29 @ocean9012 months ago

  • Resolution set to fixed
  • Status changed from reopened to closed
Note: See TracTickets for help on using tickets.