Customize: Improve the menu creation flow.

Often, folks run into two issues when they create new menus: they click "Add a Menu" thinking it will add a new page to their menu, or they forget to assign their new menu to a location, and then wonder why it doesn't show up on their site.

This commit rearranges the order of items in the menu panel, and updates the flow for creating a menu by breaking it up into steps. Additionally, more help text has been added to guide people through the process of creating a menu.

Also adds default `type` lookups for Panel and Section instances. See #30741.

Props bpayton, obenland, westonruter, celloexpessions, afercia, melchoyce, zoonini, michelleweber.
Fixes #40104.


git-svn-id: https://develop.svn.wordpress.org/trunk@41768 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Weston Ruter
2017-10-05 02:21:22 +00:00
parent ff65c814f9
commit fb28cd4663
14 changed files with 592 additions and 350 deletions
+9 -3
View File
@@ -557,11 +557,16 @@ body.trashing #publish-settings {
border-bottom-color: #ddd;
}
#customize-theme-controls .control-panel-content .control-section:nth-child(2),
#customize-theme-controls .control-panel-nav_menus .control-section:nth-child(3) {
#customize-theme-controls .control-panel-content:not(.control-panel-nav_menus) .control-section:nth-child(2),
#customize-theme-controls .control-panel-nav_menus .control-section-nav_menu,
#customize-theme-controls .control-section-nav_menu_locations .accordion-section-title {
border-top: 1px solid #ddd;
}
#customize-theme-controls .control-panel-nav_menus .control-section-nav_menu + .control-section-nav_menu {
border-top: none;
}
#customize-theme-controls > ul {
margin: 0;
}
@@ -661,7 +666,8 @@ body.trashing #publish-settings {
position: static;
}
.customize-section-description-container {
.customize-section-description-container,
.control-section-nav_menu .customize-section-description-container {
margin-bottom: 15px;
}
+61 -21
View File
@@ -1,10 +1,22 @@
#customize-theme-controls #accordion-section-menu_locations {
position: relative;
margin-bottom: 15px;
margin-top: 15px;
}
#customize-theme-controls #accordion-section-menu_locations > .accordion-section-title {
border-bottom-color: #ddd;
margin-top: 15px;
}
#customize-theme-controls .customize-section-title-nav_menus-heading,
#customize-theme-controls .customize-section-title-menu_locations-heading,
#customize-theme-controls .customize-section-title-menu_locations-description {
padding: 0 12px 0 12px;
}
#customize-theme-controls .customize-control-description.customize-section-title-menu_locations-description {
/* Override the default italic style for control descriptions */
font-style: normal;
}
.menu-in-location,
@@ -27,6 +39,23 @@
line-height: 28px;
}
#customize-controls .customize-control-nav_menu_name {
margin-bottom: 12px;
}
.customize-control-nav_menu_name p:last-of-type {
margin-bottom: 0;
}
#customize-new-menu-submit {
float: right;
min-width: 85px;
}
#customize-new-menu-submit-description {
margin: 0;
}
.wp-customizer .menu-item-bar .menu-item-handle,
.wp-customizer .menu-item-settings,
.wp-customizer .menu-item-settings .description-thin {
@@ -175,12 +204,25 @@
border-top: none;
}
.menu-settings .customize-control-checkbox label {
.wp-customizer .menu-location-settings {
margin-top: 12px;
border-top: none;
}
.wp-customizer .control-section-nav_menu .menu-location-settings {
margin-top: 24px;
border-top: 1px solid #ddd;
padding-top: 12px;
}
.menu-settings .customize-control-checkbox label,
.menu-location-settings .customize-control-checkbox label {
line-height: 1;
}
/* @todo update selector or potentially remove */
.menu-settings .customize-control.customize-control-checkbox {
.menu-settings .customize-control.customize-control-checkbox,
.menu-location-settings .customize-control.customize-control-checkbox {
margin-bottom: 8px; /* Override collapsing at smaller viewports. */
}
@@ -738,27 +780,16 @@ body.adding-menu-items #customize-preview iframe {
/* @todo update selector */
#accordion-section-add_menu {
margin: 15px 12px;
overflow: hidden;
text-align: right;
}
.new-menu-section-content {
display: none;
padding: 15px 0 0 0;
clear: both;
#accordion-section-add_menu h3,
#accordion-section-add_menu .customize-add-menu-button {
margin: 0;
}
/* @todo update selector */
#accordion-section-add_menu .accordion-section-title {
padding-left: 45px;
}
/* @todo update selector */
#accordion-section-add_menu .accordion-section-title:before {
font: normal 20px/1 dashicons;
position: absolute;
top: 12px;
left: 14px;
content: "\f132";
#accordion-section-add_menu .customize-add-menu-button {
font-weight: normal;
}
#create-new-menu-submit {
@@ -772,6 +803,10 @@ body.adding-menu-items #customize-preview iframe {
width: 100%;
}
.assigned-menu-locations-title p {
margin: 0 0 8px 0;
}
li.assigned-to-menu-location .menu-delete-item {
display: none;
}
@@ -808,7 +843,12 @@ li.assigned-to-menu-location .add-new-menu-item {
margin-bottom: 0;
}
.customize-control-nav_menu {
.customize-control-nav_menu .new-menu-item-invitation {
margin-top: 0;
margin-bottom: 0;
}
.customize-control-nav_menu .customize-control-nav_menu-buttons {
margin-top: 12px;
}
+34 -5
View File
@@ -1234,8 +1234,21 @@
* @param {object} [options.params] - Deprecated wrapper for the above properties.
*/
initialize: function ( id, options ) {
var section = this;
Container.prototype.initialize.call( section, id, options );
var section = this, params;
params = options.params || options;
// Look up the type if one was not supplied.
if ( ! params.type ) {
_.find( api.sectionConstructor, function( Constructor, type ) {
if ( Constructor === section.constructor ) {
params.type = type;
return true;
}
return false;
} );
}
Container.prototype.initialize.call( section, id, params );
section.id = id;
section.panel = new api.Value();
@@ -2507,8 +2520,22 @@
* @param {object} [options.params] - Deprecated wrapper for the above properties.
*/
initialize: function ( id, options ) {
var panel = this;
Container.prototype.initialize.call( panel, id, options );
var panel = this, params;
params = options.params || options;
// Look up the type if one was not supplied.
if ( ! params.type ) {
_.find( api.panelConstructor, function( Constructor, type ) {
if ( Constructor === panel.constructor ) {
params.type = type;
return true;
}
return false;
} );
}
Container.prototype.initialize.call( panel, id, params );
panel.embed();
panel.deferred.embedded.done( function () {
panel.ready();
@@ -3095,7 +3122,7 @@
* @param {string} [options.active=true] - Whether the control is active.
* @param {string} options.section - The ID of the section the control belongs to.
* @param {mixed} [options.setting] - The ID of the main setting or an instance of this setting.
* @param {mixed} options.settings - An object with keys (e.g. default) that maps to setting IDs or Setting/Value objects, or an array of setting IDs or Setting/Value objects.
* @param {mixed} options.settings - An object with keys (e.g. default) that maps to setting IDs or Setting/Value objects, or an array of setting IDs or Setting/Value objects.
* @param {mixed} options.settings.default - The ID of the setting the control relates to.
* @param {string} options.settings.data - @todo Is this used?
* @param {string} options.label - Label.
@@ -3107,6 +3134,8 @@
defaultActiveArguments: { duration: 'fast', completeCallback: $.noop },
defaults: {
label: '',
description: '',
active: true,
priority: 10
},
+306 -207
View File
@@ -811,6 +811,18 @@
panel.container.find( '.hide-column-tog' ).click( function() {
panel.saveManageColumnsState();
});
// Wait until after construction to patch the UI
_.defer( function () {
panel.contentContainer.find( '#accordion-section-menu_locations' ).prepend(
wp.template( 'nav-menu-locations-header' )( api.Menus.data )
);
panel.contentContainer.find( '#accordion-section-add_menu .accordion-section-title' ).replaceWith(
wp.template( 'nav-menu-create-menu-section-title' )
);
} );
},
/**
@@ -961,7 +973,16 @@
},
populateControls: function() {
var section = this, menuNameControlId, menuAutoAddControlId, menuControl, menuNameControl, menuAutoAddControl;
var section = this,
menuNameControlId,
menuLocationsControlId,
menuAutoAddControlId,
menuDeleteControlId,
menuControl,
menuNameControl,
menuLocationsControl,
menuAutoAddControl,
menuDeleteControl;
// Add the control for managing the menu name.
menuNameControlId = section.id + '[name]';
@@ -996,6 +1017,22 @@
menuControl.active.set( true );
}
// Add the menu locations control.
menuLocationsControlId = section.id + '[locations]';
menuLocationsControl = api.control( menuLocationsControlId );
if ( ! menuLocationsControl ) {
menuLocationsControl = new api.controlConstructor.nav_menu_locations( menuLocationsControlId, {
section: section.id,
priority: 999,
settings: {
'default': section.id
},
menu_id: section.params.menu_id
} );
api.control.add( menuLocationsControl.id, menuLocationsControl );
menuControl.active.set( true );
}
// Add the control for managing the menu auto_add.
menuAutoAddControlId = section.id + '[auto_add]';
menuAutoAddControl = api.control( menuAutoAddControlId );
@@ -1004,7 +1041,7 @@
type: 'nav_menu_auto_add',
label: '',
section: section.id,
priority: 999,
priority: 1000,
settings: {
'default': section.id
}
@@ -1013,6 +1050,25 @@
menuAutoAddControl.active.set( true );
}
// Add the control for deleting the menu
menuDeleteControlId = section.id + '[delete]';
menuDeleteControl = api.control( menuDeleteControlId );
if ( ! menuDeleteControl ) {
menuDeleteControl = new api.Control( menuDeleteControlId, {
section: section.id,
priority: 1001,
templateId: 'nav-menu-delete-button'
} );
api.control.add( menuDeleteControl.id, menuDeleteControl );
menuDeleteControl.active.set( true );
menuDeleteControl.deferred.embedded.done( function () {
menuDeleteControl.container.find( 'button' ).on( 'click', function() {
var menuId = section.params.menu_id;
var menuControl = api.Menus.getMenuControl( menuId );
menuControl.setting.set( false );
});
} );
}
},
/**
@@ -1093,7 +1149,6 @@
* wp.customize.Menus.NewMenuSection
*
* Customizer section for new menus.
* Note that 'new_menu' must match the WP_Customize_New_Menu_Section::$type.
*
* @constructor
* @augments wp.customize.Section
@@ -1106,51 +1161,164 @@
* @since 4.3.0
*/
attachEvents: function() {
var section = this;
this.container.on( 'click', '.add-menu-toggle', function() {
if ( section.expanded() ) {
section.collapse();
} else {
section.expand();
}
var section = this,
container = section.container,
contentContainer = section.contentContainer;
/*
* We have to manually handle section expanded because we do not
* apply the `accordion-section-title` class to this button-driven section.
*/
container.on( 'click', '.customize-add-menu-button', function() {
section.expand();
});
contentContainer.on( 'keydown', '.menu-name-field', function( event ) {
if ( 13 === event.which ) { // Enter.
section.submit();
}
} );
contentContainer.on( 'click', '#customize-new-menu-submit', function( event ) {
section.submit();
event.stopPropagation();
event.preventDefault();
} );
api.Section.prototype.attachEvents.apply( this, arguments );
},
/**
* Update UI to reflect expanded state.
* Set up the control.
*
* @since 4.1.0
*
* @param {Boolean} expanded
* @since 4.9.0
*/
onChangeExpanded: function( expanded ) {
ready: function() {
this.populateControls();
},
/**
* Create the controls for this section.
*
* @since 4.9.0
*/
populateControls: function() {
var section = this,
button = section.container.find( '.add-menu-toggle' ),
content = section.contentContainer,
customizer = section.headContainer.closest( '.wp-full-overlay-sidebar-content' );
if ( expanded ) {
button.addClass( 'open' );
button.attr( 'aria-expanded', 'true' );
content.slideDown( 'fast', function() {
customizer.scrollTop( customizer.height() );
});
} else {
button.removeClass( 'open' );
button.attr( 'aria-expanded', 'false' );
content.slideUp( 'fast' );
content.find( '.menu-name-field' ).removeClass( 'invalid' );
menuNameControlId,
menuLocationsControlId,
newMenuSubmitControlId,
menuNameControl,
menuLocationsControl,
newMenuSubmitControl;
menuNameControlId = section.id + '[name]';
menuNameControl = api.control( menuNameControlId );
if ( ! menuNameControl ) {
menuNameControl = new api.controlConstructor.nav_menu_name( menuNameControlId, {
label: api.Menus.data.l10n.menuNameLabel,
description: api.Menus.data.l10n.newMenuNameDescription,
section: section.id,
priority: 0
} );
api.control.add( menuNameControl.id, menuNameControl );
menuNameControl.active.set( true );
}
menuLocationsControlId = section.id + '[locations]';
menuLocationsControl = api.control( menuLocationsControlId );
if ( ! menuLocationsControl ) {
menuLocationsControl = new api.controlConstructor.nav_menu_locations( menuLocationsControlId, {
section: section.id,
priority: 1,
menu_id: ''
} );
api.control.add( menuLocationsControlId, menuLocationsControl );
menuLocationsControl.active.set( true );
}
newMenuSubmitControlId = section.id + '[submit]';
newMenuSubmitControl = api.control( newMenuSubmitControlId );
if ( !newMenuSubmitControl ) {
newMenuSubmitControl = new api.Control( newMenuSubmitControlId, {
section: section.id,
priority: 1,
templateId: 'nav-menu-submit-new-button'
} );
api.control.add( newMenuSubmitControlId, newMenuSubmitControl );
newMenuSubmitControl.active.set( true );
}
},
/**
* Find the content element.
* Create the new menu with name and location supplied by the user.
*
* @since 4.7.0
*
* @returns {jQuery} Content UL element.
* @since 4.9.0
*/
getContent: function() {
return this.container.find( 'ul:first' );
submit: function() {
var section = this,
contentContainer = section.contentContainer,
nameInput = contentContainer.find( '.menu-name-field' ).first(),
name = nameInput.val(),
menuSection,
customizeId,
placeholderId = api.Menus.generatePlaceholderAutoIncrementId();
if ( ! name ) {
nameInput.addClass( 'invalid' );
nameInput.focus();
return;
}
customizeId = 'nav_menu[' + String( placeholderId ) + ']';
// Register the menu control setting.
api.create( customizeId, customizeId, {}, {
type: 'nav_menu',
transport: api.Menus.data.settingTransport,
previewer: api.previewer
} );
api( customizeId ).set( $.extend(
{},
api.Menus.data.defaultSettingValues.nav_menu,
{
name: name
}
) );
/*
* Add the menu section (and its controls).
* Note that this will automatically create the required controls
* inside via the Section's ready method.
*/
menuSection = new api.Menus.MenuSection( customizeId, {
panel: 'nav_menus',
title: displayNavMenuName( name ),
customizeAction: api.Menus.data.l10n.customizingMenus,
priority: 10,
menu_id: placeholderId
} );
api.section.add( customizeId, menuSection );
// Clear name field.
nameInput.val( '' );
nameInput.removeClass( 'invalid' );
contentContainer.find( '.assigned-menu-location input[type=checkbox]' ).each( function() {
var checkbox = $( this ),
navMenuLocationSetting;
if ( checkbox.prop( 'checked' ) ) {
navMenuLocationSetting = api( 'nav_menu_locations[' + checkbox.data( 'location-id' ) + ']' );
navMenuLocationSetting.set( placeholderId );
// Reset state for next new menu
checkbox.prop( 'checked', false );
}
} );
wp.a11y.speak( api.Menus.data.l10n.menuAdded );
// Focus on the new menu section.
api.section( customizeId ).focus(); // @todo should we focus on the new menu's control and open the add-items panel? Thinking user flow...
}
});
@@ -1512,6 +1680,8 @@
wp.a11y.speak( api.Menus.data.l10n.itemDeleted );
$adjacentFocusTarget.focus(); // keyboard accessibility
} );
control.setting.set( false );
} );
},
@@ -2034,45 +2204,88 @@
api.Menus.MenuNameControl = api.Control.extend({
ready: function() {
var control = this,
settingValue = control.setting();
var control = this;
/*
* Since the control is not registered in PHP, we need to prevent the
* preview's sending of the activeControls to result in this control
* being deactivated.
*/
control.active.validate = function() {
var value, section = api.section( control.section() );
if ( section ) {
value = section.active();
} else {
value = false;
}
return value;
};
control.nameElement = new api.Element( control.container.find( '.menu-name-field' ) );
control.nameElement.bind(function( value ) {
if ( control.setting ) {
var settingValue = control.setting();
if ( settingValue && settingValue.name !== value ) {
settingValue = _.clone( settingValue );
settingValue.name = value;
control.setting.set( settingValue );
}
});
if ( settingValue ) {
control.nameElement.set( settingValue.name );
}
control.setting.bind(function( object ) {
if ( object ) {
control.nameElement.set( object.name );
control.nameElement = new api.Element( control.container.find( '.menu-name-field' ) );
control.nameElement.bind(function( value ) {
var settingValue = control.setting();
if ( settingValue && settingValue.name !== value ) {
settingValue = _.clone( settingValue );
settingValue.name = value;
control.setting.set( settingValue );
}
});
if ( settingValue ) {
control.nameElement.set( settingValue.name );
}
control.setting.bind(function( object ) {
if ( object ) {
control.nameElement.set( object.name );
}
});
}
}
});
/**
* wp.customize.Menus.MenuLocationsControl
*
* Customizer control for a nav menu's locations.
*
* @since 4.9.0
* @constructor
* @augments wp.customize.Control
*/
api.Menus.MenuLocationsControl = api.Control.extend({
/**
* Set up the control.
*
* @since 4.9.0
*/
ready: function () {
var control = this;
control.container.find( '.assigned-menu-location' ).each(function() {
var container = $( this ),
checkbox = container.find( 'input[type=checkbox]' ),
element = new api.Element( checkbox ),
navMenuLocationSetting = api( 'nav_menu_locations[' + checkbox.data( 'location-id' ) + ']' ),
isNewMenu = control.params.menu_id === '',
updateCheckbox = isNewMenu ? _.noop : function( checked ) {
element.set( checked );
},
updateSetting = isNewMenu ? _.noop : function( checked ) {
navMenuLocationSetting.set( checked ? control.params.menu_id : 0 );
},
updateSelectedMenuLabel = function( selectedMenuId ) {
var menuSetting = api( 'nav_menu[' + String( selectedMenuId ) + ']' );
if ( ! selectedMenuId || ! menuSetting || ! menuSetting() ) {
container.find( '.theme-location-set' ).hide();
} else {
container.find( '.theme-location-set' ).show().find( 'span' ).text( displayNavMenuName( menuSetting().name ) );
}
};
updateCheckbox( navMenuLocationSetting.get() === control.params.menu_id );
checkbox.on( 'change', function() {
// Note: We can't use element.bind( function( checked ){ ... } ) here because it will trigger a change as well.
updateSetting( this.checked );
} );
navMenuLocationSetting.bind( function( selectedMenuId ) {
updateCheckbox( selectedMenuId === control.params.menu_id );
updateSelectedMenuLabel( selectedMenuId );
} );
updateSelectedMenuLabel( navMenuLocationSetting.get() );
});
}
});
/**
@@ -2180,7 +2393,6 @@
} );
this._setupAddition();
this._setupLocations();
this._setupTitle();
// Add menu to Custom Menu widgets.
@@ -2210,6 +2422,15 @@
select.append( new Option( name, menuId ) );
}
}
/*
* Wait for menu items to be added.
* Ideally, we'd bind to an event indicating construction is complete,
* but deferring appears to be the best option today.
*/
_.defer( function () {
control.updateInvitationVisibility();
} );
},
/**
@@ -2235,11 +2456,6 @@
});
}
} );
control.container.find( '.menu-delete-item .button-link-delete' ).on( 'click', function( event ) {
event.preventDefault();
control.setting.set( false );
});
},
/**
@@ -2393,43 +2609,6 @@
widgetTemplate.find( 'option[value=' + String( menuId ) + ']' ).remove();
},
// Setup theme location checkboxes.
_setupLocations: function() {
var control = this;
control.container.find( '.assigned-menu-location' ).each(function() {
var container = $( this ),
checkbox = container.find( 'input[type=checkbox]' ),
element,
updateSelectedMenuLabel,
navMenuLocationSetting = api( 'nav_menu_locations[' + checkbox.data( 'location-id' ) + ']' );
updateSelectedMenuLabel = function( selectedMenuId ) {
var menuSetting = api( 'nav_menu[' + String( selectedMenuId ) + ']' );
if ( ! selectedMenuId || ! menuSetting || ! menuSetting() ) {
container.find( '.theme-location-set' ).hide();
} else {
container.find( '.theme-location-set' ).show().find( 'span' ).text( displayNavMenuName( menuSetting().name ) );
}
};
element = new api.Element( checkbox );
element.set( navMenuLocationSetting.get() === control.params.menu_id );
checkbox.on( 'change', function() {
// Note: We can't use element.bind( function( checked ){ ... } ) here because it will trigger a change as well.
navMenuLocationSetting.set( this.checked ? control.params.menu_id : 0 );
} );
navMenuLocationSetting.bind(function( selectedMenuId ) {
element.set( selectedMenuId === control.params.menu_id );
updateSelectedMenuLabel( selectedMenuId );
});
updateSelectedMenuLabel( navMenuLocationSetting.get() );
});
},
/**
* Update Section Title as menu name is changed.
*/
@@ -2607,6 +2786,7 @@
currentAbsolutePosition: 0
} );
menuControl.updateInvitationVisibility( menuItemControls );
menuControl.container.find( '.reorder-toggle' ).toggle( menuItemControls.length > 1 );
},
@@ -2681,103 +2861,22 @@
wp.a11y.speak( api.Menus.data.l10n.itemAdded );
return menuItemControl;
},
/**
* Show an invitation to add new menu items when there are no menu items.
*
* @since 4.9.0
*
* @param {wp.customize.controlConstructor.nav_menu_item[]} optionalMenuItemControls
*/
updateInvitationVisibility: function ( optionalMenuItemControls ) {
var menuItemControls = optionalMenuItemControls || this.getMenuItemControls();
this.container.find( '.new-menu-item-invitation' ).toggle( menuItemControls.length === 0 );
}
} );
/**
* wp.customize.Menus.NewMenuControl
*
* Customizer control for creating new menus and handling deletion of existing menus.
* Note that 'new_menu' must match the WP_Customize_New_Menu_Control::$type.
*
* @constructor
* @augments wp.customize.Control
*/
api.Menus.NewMenuControl = api.Control.extend({
/**
* Set up the control.
*/
ready: function() {
this._bindHandlers();
},
_bindHandlers: function() {
var self = this,
name = $( '#customize-control-new_menu_name input' ),
submit = $( '#create-new-menu-submit' );
name.on( 'keydown', function( event ) {
if ( 13 === event.which ) { // Enter.
self.submit();
}
} );
submit.on( 'click', function( event ) {
self.submit();
event.stopPropagation();
event.preventDefault();
} );
},
/**
* Create the new menu with the name supplied.
*/
submit: function() {
var control = this,
container = control.container.closest( '.accordion-section-new-menu' ),
nameInput = container.find( '.menu-name-field' ).first(),
name = nameInput.val(),
menuSection,
customizeId,
placeholderId = api.Menus.generatePlaceholderAutoIncrementId();
if ( ! name ) {
nameInput.addClass( 'invalid' );
nameInput.focus();
return;
}
customizeId = 'nav_menu[' + String( placeholderId ) + ']';
// Register the menu control setting.
api.create( customizeId, customizeId, {}, {
type: 'nav_menu',
transport: api.Menus.data.settingTransport,
previewer: api.previewer
} );
api( customizeId ).set( $.extend(
{},
api.Menus.data.defaultSettingValues.nav_menu,
{
name: name
}
) );
/*
* Add the menu section (and its controls).
* Note that this will automatically create the required controls
* inside via the Section's ready method.
*/
menuSection = new api.Menus.MenuSection( customizeId, {
panel: 'nav_menus',
title: displayNavMenuName( name ),
customizeAction: api.Menus.data.l10n.customizingMenus,
type: 'nav_menu',
priority: 10,
menu_id: placeholderId
} );
api.section.add( menuSection );
// Clear name field.
nameInput.val( '' );
nameInput.removeClass( 'invalid' );
wp.a11y.speak( api.Menus.data.l10n.menuAdded );
// Focus on the new menu section.
api.section( customizeId ).focus(); // @todo should we focus on the new menu's control and open the add-items panel? Thinking user flow...
}
});
/**
* Extends wp.customize.controlConstructor with control constructor for
* menu_location, menu_item, nav_menu, and new_menu.
@@ -2787,8 +2886,8 @@
nav_menu_item: api.Menus.MenuItemControl,
nav_menu: api.Menus.MenuControl,
nav_menu_name: api.Menus.MenuNameControl,
nav_menu_auto_add: api.Menus.MenuAutoAddControl,
new_menu: api.Menus.NewMenuControl
nav_menu_locations: api.Menus.MenuLocationsControl,
nav_menu_auto_add: api.Menus.MenuAutoAddControl
});
/**