So I've enabled the option to not show controls if the one control is not active, but the issue is that this works only when you refresh the customizer page.
In my example I have a boxed body control, which is a checkbox (although I used the custom control to style it a bit differently, you can find code here). If it's disabled the associated controls that can change the boxed body border width, color, background color etc. are all hidden. If you enable the boxed body you need to refresh the page to see those controls, which is UX disaster. The code is:
INITIAL CODE
add_action( 'customize_register', 'mytheme_customize_register' );
/**
* Register customizer settings
*
* @see add_action('customize_register',$func)
* @param \WP_Customize_Manager $wp_customize WP Customize object.
* @since 1.0.0
*/
function mytheme_customize_register( WP_Customize_Manager $wp_customize ) {
/**
Boxed Body
*/
$wp_customize->add_setting( 'boxed_body', array(
'default' => false,
'transport' => 'postMessage',
'sanitize_callback' => 'mytheme_checkbox_sanitization',
) );
$wp_customize->add_control(new Toggle_Checkbox_Custom_Control( $wp_customize, 'boxed_body', array(
'label' => esc_html__( 'Boxed Body', 'mytheme' ),
'description' => esc_html__( 'Check this to enable boxed body layout', 'mytheme' ),
'type' => 'checkbox',
'section' => 'section_general',
) ) );
/**
* Callback function for boxed body choice
*
* @param object $control Control object.
* @return boolean
*/
function boxed_body_choice( $control ) {
if ( 1 === $control->manager->get_setting( 'boxed_body' )->value() ) {
return true;
} else {
return false;
}
}
/**
Boxed Body Border Color
*/
$wp_customize->add_setting( 'boxed_body_border_color', array(
'default' => '',
'transport' => 'postMessage',
'sanitize_callback' => 'sanitize_hex_color',
) );
$wp_customize->add_control(new WP_Customize_Color_Control( $wp_customize, 'boxed_body_border_color', array(
'label' => esc_html__( 'Boxed Body Border Color', 'mytheme' ),
'settings' => 'boxed_body_border_color',
'section' => 'section_general',
'active_callback' => 'boxed_body_choice',
) ) );
}
add_action( 'customize_preview_init', 'mytheme_customizer_live_preview' );
/**
* Live preview script enqueue
*
* @since 1.0.0
*/
function mytheme_customizer_live_preview() {
wp_enqueue_script( 'mytheme-themecustomizer', get_template_directory_uri() . '/inc/customizer/js/customizer.js?v=' . rand(), array( 'jquery', 'customize-preview' ), '', true );
}
I've tried following Weston Ruter's example on contextual controls here and gist, but no luck. I've added in my customizer.js
(function($) {
// Add callback for when the boxed_body setting is toggled.
wp.customize( 'boxed_body', function(setting) {
var isBoxedBodyToggled, linkSettingValueToControlActiveState;
/**
* Determine whether the boxed body associated options should be displayed.
*
* @returns {boolean} Is toggled?
*/
isBoxedBodyToggled = function() {
return '' !== setting.get();
};
/**
* Update a control's active state according to the boxed_body setting's value.
*
* @param {wp.customize.Control} control Boxed body control.
*/
linkSettingValueToControlActiveState = function( control ) {
var setActiveState = function() {
control.active.set( isBoxedBodyToggled() );
};
// FYI: With the following we can eliminate all of our PHP active_callback code.
control.active.validate = isBoxedBodyToggled;
// Set initial active state.
setActiveState();
/*
* Update activate state whenever the setting is changed.
* Even when the setting does have a refresh transport where the
* server-side active callback will manage the active state upon
* refresh, having this JS management of the active state will
* ensure that controls will have their visibility toggled
* immediately instead of waiting for the preview to load.
* This is especially important if the setting has a postMessage
* transport where changing the setting wouldn't normally cause
* the preview to refresh and thus the server-side active_callbacks
* would not get invoked.
*/
setting.bind( setActiveState );
};
// Call linkSettingValueToControlActiveState on the site title and tagline controls when they exist.
wp.customize.control( 'boxed_body_border_width', linkSettingValueToControlActiveState );
wp.customize.control( 'boxed_body_border_color', linkSettingValueToControlActiveState );
wp.customize.control( 'boxed_body_border_style', linkSettingValueToControlActiveState );
wp.customize.control( 'boxed_body_bg_image', linkSettingValueToControlActiveState );
wp.customize.control( 'boxed_body_bg_image_repeat', linkSettingValueToControlActiveState );
wp.customize.control( 'boxed_body_bg_image_size', linkSettingValueToControlActiveState );
wp.customize.control( 'boxed_body_bg_image_position', linkSettingValueToControlActiveState );
wp.customize.control( 'boxed_body_bg_image_attachment', linkSettingValueToControlActiveState );
});
})(jQuery);
But on customizer refresh I get
Uncaught TypeError: wp.customize.control is not a function(…)
On the
wp.customize.control( 'boxed_body_border_width', linkSettingValueToControlActiveState );
And the toggle won't hide or show the controls. I've made some changes to fit the custom toggle control, so I'm not checking against 'blank'
but rather ''
in the isBoxedBodyToggled
function.
When I fiddle in DevTools I can see that wp.customize
is a function, not an object. In the example this should be an object (I guess).
Any help would be appreciated.
NEW CODE
So I've noticed that I don't need that many controls since I have added
add_theme_support( 'custom-background' );
which gives me built in control for the body background, so I've made it like this:
add_action( 'customize_register', 'mytheme_customize_register' );
/**
* Register customizer settings
*
* @see add_action('customize_register',$func)
* @param \WP_Customize_Manager $wp_customize WP Customize object.
* @since 1.0.0
*/
function mytheme_customize_register( WP_Customize_Manager $wp_customize ) {
/**
------------------------------------------------------------
SECTION: Body Settings
------------------------------------------------------------
*/
$wp_customize->add_section( 'background_image', array(
'title' => esc_html__( 'Body Settings', 'mytheme' ),
'priority' => 0,
) );
/**
Boxed Body
*/
$wp_customize->add_setting( 'boxed_body', array(
'default' => false,
'transport' => 'postMessage',
'sanitize_callback' => 'mytheme_checkbox_sanitization',
) );
$wp_customize->add_control(new Toggle_Checkbox_Custom_Control( $wp_customize, 'boxed_body', array(
'label' => esc_html__( 'Boxed Body', 'mytheme' ),
'description' => esc_html__( 'Check this to enable boxed body layout', 'mytheme' ),
'type' => 'checkbox',
'section' => 'background_image',
'priority' => 0,
) ) );
/**
* Callback function for boxed body choice
*
* @param object $control Control object.
* @return boolean
*/
function boxed_body_choice( $control ) {
if ( 1 === $control->manager->get_setting( 'boxed_body' )->value() ) {
return true;
} else {
return false;
}
}
/**
Boxed Body Border Color
*/
$wp_customize->add_setting( 'boxed_body_border_color', array(
'default' => '',
'transport' => 'postMessage',
'sanitize_callback' => 'sanitize_hex_color',
) );
$wp_customize->add_control(new WP_Customize_Color_Control( $wp_customize, 'boxed_body_border_color', array(
'label' => esc_html__( 'Boxed Body Border Color', 'mytheme' ),
'settings' => 'boxed_body_border_color',
'section' => 'background_image',
'active_callback' => 'boxed_body_choice',
'priority' => 0,
) ) );
}
add_action( 'customize_preview_init', 'mytheme_customizer_live_preview' );
/**
* Live preview script enqueue
*
* @since 1.0.0
*/
function mytheme_customizer_live_preview() {
wp_enqueue_script( 'mytheme-themecustomizer', get_template_directory_uri() . '/inc/customizer/js/customizer.js?v=' . rand(), array( 'jquery', 'customize-preview' ), '', true );
}
And the customizer.js
looks like this
(function($, api) {
'use strict';
//Boxed Body Toggle
api( 'boxed_body', function(value) {
value.bind(function(newval) {
if (newval) {
$('body').wrapInner('<div class="boxed_body_wrapper" />');
} else {
$('.boxed_body_wrapper').contents().unwrap();
}
});
});
// Add callback for when the boxed_body setting is toggled.
api( 'boxed_body', function(value) {
value.bind(function(setting){
var isBoxedBodyToggled, linkSettingValueToControlActiveState;
/**
* Determine whether the boxed body associated options should be displayed.
*
* @returns {boolean} Is toggled?
*/
isBoxedBodyToggled = function() {
return '' !== setting.get();
};
/**
* Update a control's active state according to the boxed_body setting's value.
*
* @param {api.Control} control Boxed body control.
*/
linkSettingValueToControlActiveState = function( control ) {
var setActiveState = function() {
control.active.set( isBoxedBodyToggled() );
};
// FYI: With the following we can eliminate all of our PHP active_callback code.
control.active.validate = isBoxedBodyToggled;
// Set initial active state.
setActiveState();
/*
* Update activate state whenever the setting is changed.
* Even when the setting does have a refresh transport where the
* server-side active callback will manage the active state upon
* refresh, having this JS management of the active state will
* ensure that controls will have their visibility toggled
* immediately instead of waiting for the preview to load.
* This is especially important if the setting has a postMessage
* transport where changing the setting wouldn't normally cause
* the preview to refresh and thus the server-side active_callbacks
* would not get invoked.
*/
setting.bind( setActiveState );
};
// Call linkSettingValueToControlActiveState on the site title and tagline controls when they exist.
api.control( 'boxed_body_border_color', linkSettingValueToControlActiveState );
});
});
})(jQuery, wp.customize);
I still get
customizer.js?v=1256189197&ver=4.7:111 Uncaught TypeError: api.control is not a function
at i (jquery.js?ver=1.12.4:2)
at Object.fireWith (jquery.js?ver=1.12.4:2)
at Function.set (customize-base.min.js?ver=4.7:1)
at Function.d [as set] (jquery.js?ver=1.12.4:2)
at c (customize-preview.min.js?ver=4.7:1)
at f. (customize-preview.min.js?ver=4.7:1)
at i (jquery.js?ver=1.12.4:2)
at Object.fireWith (jquery.js?ver=1.12.4:2)
at f.trigger (customize-base.min.js?ver=4.7:1)
If I add 'customize-controls'
as a dependency then I get the error:
Uncaught TypeError: Cannot read property 'dirty' of undefined
at Function.initialize (customize-controls.min.js?ver=4.7:1)
at f.e.Class (customize-base.min.js?ver=4.7:1)
at f [as constructor] (customize-base.min.js?ver=4.7:1)
at new f (customize-base.min.js?ver=4.7:1)
at Function.create (customize-base.min.js?ver=4.7:1)
at String.c (customize-preview.min.js?ver=4.7:1)
at Function.each (jquery.js?ver=1.12.4:2)
at f. (customize-preview.min.js?ver=4.7:1)
at i (jquery.js?ver=1.12.4:2)
at Object.fireWith (jquery.js?ver=1.12.4:2)
Still not sure what I'm doing wrong :\
EDIT
I used the wrong action hook when enqueuing. I created another file in which I added:
add_action( 'customize_controls_enqueue_scripts', 'utter_customizer_control_toggle' );
/**
* Custom contextual controls
*
* @since 1.0.0
*/
function utter_customizer_control_toggle() {
wp_enqueue_script( 'utter-contextualcontrols', UTTER_TEMPPATH . '/inc/customizer/js/customizer-contextual.js?v=' . rand(), array( 'customize-controls' ), false );
}
This gets rid of all errors. And toggle works the first time, but not when I toggle back, but I guess that has to do with the custom control I'm using instead of regular checkbox. I'll probably fix this soon :D
wp.customize.bind( 'ready', function(){ ... } );
to ensure everything is available. Also, you need to make sure that you addcustomize-controls
as a dependency for the script you are enqueueing.customize-controls
as a dependency produced the error:Uncaught TypeError: Cannot read property 'dirty' of undefined
with a bunch of functions. I tried with the use of binding but that didn't work as well. The odd thing is that when I fiddle in the console, I can get the code that is in my js script. It almost looks like #31320 ticket. Which is also weird because I have other controls that are working fine...