WordPress 5.4 added the wp_nav_menu_item_custom_fields_customize_template
hook to add custom fields to the Nav Menu Item settings in the customizer. I've figured out how to display the additional fields I would like to add (As part of my Nav Menu Roles plugin), with the following snippet:
/**
* Display the fields in the Customizer.
*/
function kia_customizer_custom_fields() {
global $wp_roles;
/**
* Pass the menu item to the filter function.
* This change is suggested as it allows the use of information from the menu item (and
* by extension the target object) to further customize what filters appear during menu
* construction.
*/
$display_roles = apply_filters( 'nav_menu_roles', $wp_roles->role_names );
if( ! $display_roles ) return;
?>
<p class="field-nav_menu_logged_in_out nav_menu_logged_in_out nav_menu_logged_in_out-thin">
<fieldset>
<legend><?php _e( 'Display Mode', 'nav-menu-roles' ); ?></legend>
<label for="edit-menu-item-role_logged_in-{{ data.menu_item_id }}">
<input type="radio" id="edit-menu-item-role_logged_in-{{ data.menu_item_id }}" class="edit-menu-item-logged_in_out" value="in" name="menu-item-role_logged_in" />
<?php _e( 'Logged In Users', 'nav-menu-roles' ); ?><br/>
</label>
<label for="edit-menu-item-role_logged_out-{{ data.menu_item_id }}">
<input type="radio" id="edit-menu-item-role_logged_out-{{ data.menu_item_id }}" class="edit-menu-item-logged_in_out" value="out" name="menu-item-role_logged_out" />
<?php _e( 'Logged Out Users', 'nav-menu-roles' ); ?><br/>
</label>
<label for="edit-menu-item-role_everyone-{{ data.menu_item_id }}">
<input type="radio" id="edit-menu-item-role_everyone-{{ data.menu_item_id }}" class="edit-menu-item-logged_in_out" value="" name="menu-item-role_everyone" />
<?php _e( 'Everyone', 'nav-menu-roles' ); ?><br/>
</label>
</fieldset>
</p>
<p class="field-nav_menu_roles nav_menu_roles nav_menu_roles-thin">
<fieldset class="roles">
<legend><?php _e( 'Restrict menu item to a minimum role', 'nav-menu-roles' ); ?></legend>
<?php
/* Loop through each of the available roles. */
foreach ( $display_roles as $role => $name ) : ?>
<label for="edit-menu-item-role_<?php echo $role; ?>-{{ data.menu_item_id }}">
<input type="checkbox" id="edit-menu-item-role_<?php echo esc_attr( $role ); ?>-{{ data.menu_item_id }}" class="edit-menu-item-role" value="<?php echo esc_attr( $role ); ?>" name="menu-item-role_<?php echo esc_attr( $role ); ?>" />
<?php echo esc_html( $name ); ?><br/>
</label>
<?php endforeach; ?>
</fieldset>
</p>
<?php
}
add_action( 'wp_nav_menu_item_custom_fields_customize_template', 'kia_customizer_custom_fields' );
but I'm not sure how to add any data as the default fields are all getting data from some Javascript templating.
For example the description's value is {{ data.description }}
.
It looks like the data is being added in the WP_Customize_Manager
class and that the data is fetched from each setting's json()
method, which has no filters to modify the result.
If I'm tracing it back correctly, the json()
method tries to get the values from the WP_Customize_Nav_Menu_Item_Setting
class which also has no filter for adding custom data.
So, I'm trying to track down how to 1. pass some additional properties to data
and 2. set the radio and checkbox values when loaded in the Customizer.
Update #1
I already was filtering wp_setup_nav_menu_item
like so:
/**
* Adds value of new field to nav menu $item object
*/
function kia_setup_nav_item( $menu_item ) {
if( is_object( $menu_item ) && isset( $menu_item->ID ) ) {
$roles = get_post_meta( $menu_item->ID, '_nav_menu_role', true );
}
return $menu_item;
}
add_filter( 'wp_setup_nav_menu_item', 'kia_setup_nav_item' );
But per @Nikola Ivanov Nikolov's comment I realized that this data is available in the customizer script object... it seems like it's in wp.customize.settings.settings
And I have tracked down the customizer control for wp.customize.Menus.MenuItemControl
so now it seems to be an issue of running some script when the menu item control is ready/expanded.
Update #2
I have some Javascript that will at least get the current values checked!
/**
* Customizer Communicator
*/
jQuery( document ).ready(function($) {
"use strict";
$( '.customize-control-nav_menu_item' ).on( 'expanded', function() {
var $control = $(this);
var menu_item_id = $(this).find( '.nav_menu_logged_in_out' ).data( 'menu_item_id' );
var control = wp.customize.control( 'nav_menu_item[' + menu_item_id + ']' );
var settings = control.setting.get();
if ( 'undefined' === typeof( settings.roles ) || '' === settings.roles ) {
$control.find( '.edit-menu-item-logged_in_out[value=""]' ).prop( 'checked', true ).change();
} else if ( 'out' === settings.roles ) {
$control.find( '.edit-menu-item-logged_in_out[value="out"]' ).prop( 'checked', true ).change();
} else if ( 'in' === settings.roles ) {
$control.find( '.edit-menu-item-logged_in_out[value="in"]' ).prop( 'checked', true ).change();
} else if ( $.isArray( settings.roles ) ) {
$control.find( '.edit-menu-item-logged_in_out[value="in"]' ).prop( 'checked', true ).change();
$.each( settings.roles, function( index, role ) {
$control.find( '.edit-menu-item-role[value="' + role + '"]' ).prop( 'checked', true );
} );
}
$( '.edit-menu-item-logged_in_out' ).on( 'change', function() {
var $roles = $control.find( '.nav_menu_roles' );
if ( 'in' === $(this).val() ) {
$roles.show();
} else {
$roles.hide();
}
});
});
});
So, this still leaves how to ensure SAVING the data.
wp_setup_nav_menu_item
filter, you can pass data to the output of thejson()
method. HOWEVER this doesn't get passed to the template'sdata
variable for some reason. Try adding this:function kia_filter_nav_menu_item( $menu_item ) {$menu_item->test_setting = 'IT WORKS!';return $menu_item;}add_filter( 'wp_setup_nav_menu_item', 'kia_filter_nav_menu_item', 10 );
and then check the source of the customizer page. You should find "IT WORKS!" there.wp_setup_nav_menu_item
and so the data is in the source! Next step is to figure out how to access it via JS.