wp/wp-admin/js/customize-nav-menus.js
changeset 7 cf61fcea0001
child 9 177826044cd9
equal deleted inserted replaced
6:490d5cc509ed 7:cf61fcea0001
       
     1 /* global _wpCustomizeNavMenusSettings, wpNavMenu, console */
       
     2 ( function( api, wp, $ ) {
       
     3 	'use strict';
       
     4 
       
     5 	/**
       
     6 	 * Set up wpNavMenu for drag and drop.
       
     7 	 */
       
     8 	wpNavMenu.originalInit = wpNavMenu.init;
       
     9 	wpNavMenu.options.menuItemDepthPerLevel = 20;
       
    10 	wpNavMenu.options.sortableItems         = '> .customize-control-nav_menu_item';
       
    11 	wpNavMenu.options.targetTolerance       = 10;
       
    12 	wpNavMenu.init = function() {
       
    13 		this.jQueryExtensions();
       
    14 	};
       
    15 
       
    16 	api.Menus = api.Menus || {};
       
    17 
       
    18 	// Link settings.
       
    19 	api.Menus.data = {
       
    20 		itemTypes: [],
       
    21 		l10n: {},
       
    22 		settingTransport: 'refresh',
       
    23 		phpIntMax: 0,
       
    24 		defaultSettingValues: {
       
    25 			nav_menu: {},
       
    26 			nav_menu_item: {}
       
    27 		},
       
    28 		locationSlugMappedToName: {}
       
    29 	};
       
    30 	if ( 'undefined' !== typeof _wpCustomizeNavMenusSettings ) {
       
    31 		$.extend( api.Menus.data, _wpCustomizeNavMenusSettings );
       
    32 	}
       
    33 
       
    34 	/**
       
    35 	 * Newly-created Nav Menus and Nav Menu Items have negative integer IDs which
       
    36 	 * serve as placeholders until Save & Publish happens.
       
    37 	 *
       
    38 	 * @return {number}
       
    39 	 */
       
    40 	api.Menus.generatePlaceholderAutoIncrementId = function() {
       
    41 		return -Math.ceil( api.Menus.data.phpIntMax * Math.random() );
       
    42 	};
       
    43 
       
    44 	/**
       
    45 	 * wp.customize.Menus.AvailableItemModel
       
    46 	 *
       
    47 	 * A single available menu item model. See PHP's WP_Customize_Nav_Menu_Item_Setting class.
       
    48 	 *
       
    49 	 * @constructor
       
    50 	 * @augments Backbone.Model
       
    51 	 */
       
    52 	api.Menus.AvailableItemModel = Backbone.Model.extend( $.extend(
       
    53 		{
       
    54 			id: null // This is only used by Backbone.
       
    55 		},
       
    56 		api.Menus.data.defaultSettingValues.nav_menu_item
       
    57 	) );
       
    58 
       
    59 	/**
       
    60 	 * wp.customize.Menus.AvailableItemCollection
       
    61 	 *
       
    62 	 * Collection for available menu item models.
       
    63 	 *
       
    64 	 * @constructor
       
    65 	 * @augments Backbone.Model
       
    66 	 */
       
    67 	api.Menus.AvailableItemCollection = Backbone.Collection.extend({
       
    68 		model: api.Menus.AvailableItemModel,
       
    69 
       
    70 		sort_key: 'order',
       
    71 
       
    72 		comparator: function( item ) {
       
    73 			return -item.get( this.sort_key );
       
    74 		},
       
    75 
       
    76 		sortByField: function( fieldName ) {
       
    77 			this.sort_key = fieldName;
       
    78 			this.sort();
       
    79 		}
       
    80 	});
       
    81 	api.Menus.availableMenuItems = new api.Menus.AvailableItemCollection( api.Menus.data.availableMenuItems );
       
    82 
       
    83 	/**
       
    84 	 * Insert a new `auto-draft` post.
       
    85 	 *
       
    86 	 * @since 4.7.0
       
    87 	 * @access public
       
    88 	 *
       
    89 	 * @param {object} params - Parameters for the draft post to create.
       
    90 	 * @param {string} params.post_type - Post type to add.
       
    91 	 * @param {string} params.post_title - Post title to use.
       
    92 	 * @return {jQuery.promise} Promise resolved with the added post.
       
    93 	 */
       
    94 	api.Menus.insertAutoDraftPost = function insertAutoDraftPost( params ) {
       
    95 		var request, deferred = $.Deferred();
       
    96 
       
    97 		request = wp.ajax.post( 'customize-nav-menus-insert-auto-draft', {
       
    98 			'customize-menus-nonce': api.settings.nonce['customize-menus'],
       
    99 			'wp_customize': 'on',
       
   100 			'customize_changeset_uuid': api.settings.changeset.uuid,
       
   101 			'params': params
       
   102 		} );
       
   103 
       
   104 		request.done( function( response ) {
       
   105 			if ( response.post_id ) {
       
   106 				api( 'nav_menus_created_posts' ).set(
       
   107 					api( 'nav_menus_created_posts' ).get().concat( [ response.post_id ] )
       
   108 				);
       
   109 
       
   110 				if ( 'page' === params.post_type ) {
       
   111 
       
   112 					// Activate static front page controls as this could be the first page created.
       
   113 					if ( api.section.has( 'static_front_page' ) ) {
       
   114 						api.section( 'static_front_page' ).activate();
       
   115 					}
       
   116 
       
   117 					// Add new page to dropdown-pages controls.
       
   118 					api.control.each( function( control ) {
       
   119 						var select;
       
   120 						if ( 'dropdown-pages' === control.params.type ) {
       
   121 							select = control.container.find( 'select[name^="_customize-dropdown-pages-"]' );
       
   122 							select.append( new Option( params.post_title, response.post_id ) );
       
   123 						}
       
   124 					} );
       
   125 				}
       
   126 				deferred.resolve( response );
       
   127 			}
       
   128 		} );
       
   129 
       
   130 		request.fail( function( response ) {
       
   131 			var error = response || '';
       
   132 
       
   133 			if ( 'undefined' !== typeof response.message ) {
       
   134 				error = response.message;
       
   135 			}
       
   136 
       
   137 			console.error( error );
       
   138 			deferred.rejectWith( error );
       
   139 		} );
       
   140 
       
   141 		return deferred.promise();
       
   142 	};
       
   143 
       
   144 	/**
       
   145 	 * wp.customize.Menus.AvailableMenuItemsPanelView
       
   146 	 *
       
   147 	 * View class for the available menu items panel.
       
   148 	 *
       
   149 	 * @constructor
       
   150 	 * @augments wp.Backbone.View
       
   151 	 * @augments Backbone.View
       
   152 	 */
       
   153 	api.Menus.AvailableMenuItemsPanelView = wp.Backbone.View.extend({
       
   154 
       
   155 		el: '#available-menu-items',
       
   156 
       
   157 		events: {
       
   158 			'input #menu-items-search': 'debounceSearch',
       
   159 			'keyup #menu-items-search': 'debounceSearch',
       
   160 			'focus .menu-item-tpl': 'focus',
       
   161 			'click .menu-item-tpl': '_submit',
       
   162 			'click #custom-menu-item-submit': '_submitLink',
       
   163 			'keypress #custom-menu-item-name': '_submitLink',
       
   164 			'click .new-content-item .add-content': '_submitNew',
       
   165 			'keypress .create-item-input': '_submitNew',
       
   166 			'keydown': 'keyboardAccessible'
       
   167 		},
       
   168 
       
   169 		// Cache current selected menu item.
       
   170 		selected: null,
       
   171 
       
   172 		// Cache menu control that opened the panel.
       
   173 		currentMenuControl: null,
       
   174 		debounceSearch: null,
       
   175 		$search: null,
       
   176 		$clearResults: null,
       
   177 		searchTerm: '',
       
   178 		rendered: false,
       
   179 		pages: {},
       
   180 		sectionContent: '',
       
   181 		loading: false,
       
   182 		addingNew: false,
       
   183 
       
   184 		initialize: function() {
       
   185 			var self = this;
       
   186 
       
   187 			if ( ! api.panel.has( 'nav_menus' ) ) {
       
   188 				return;
       
   189 			}
       
   190 
       
   191 			this.$search = $( '#menu-items-search' );
       
   192 			this.$clearResults = this.$el.find( '.clear-results' );
       
   193 			this.sectionContent = this.$el.find( '.available-menu-items-list' );
       
   194 
       
   195 			this.debounceSearch = _.debounce( self.search, 500 );
       
   196 
       
   197 			_.bindAll( this, 'close' );
       
   198 
       
   199 			// If the available menu items panel is open and the customize controls are
       
   200 			// interacted with (other than an item being deleted), then close the
       
   201 			// available menu items panel. Also close on back button click.
       
   202 			$( '#customize-controls, .customize-section-back' ).on( 'click keydown', function( e ) {
       
   203 				var isDeleteBtn = $( e.target ).is( '.item-delete, .item-delete *' ),
       
   204 					isAddNewBtn = $( e.target ).is( '.add-new-menu-item, .add-new-menu-item *' );
       
   205 				if ( $( 'body' ).hasClass( 'adding-menu-items' ) && ! isDeleteBtn && ! isAddNewBtn ) {
       
   206 					self.close();
       
   207 				}
       
   208 			} );
       
   209 
       
   210 			// Clear the search results and trigger a `keyup` event to fire a new search.
       
   211 			this.$clearResults.on( 'click', function() {
       
   212 				self.$search.val( '' ).focus().trigger( 'keyup' );
       
   213 			} );
       
   214 
       
   215 			this.$el.on( 'input', '#custom-menu-item-name.invalid, #custom-menu-item-url.invalid', function() {
       
   216 				$( this ).removeClass( 'invalid' );
       
   217 			});
       
   218 
       
   219 			// Load available items if it looks like we'll need them.
       
   220 			api.panel( 'nav_menus' ).container.bind( 'expanded', function() {
       
   221 				if ( ! self.rendered ) {
       
   222 					self.initList();
       
   223 					self.rendered = true;
       
   224 				}
       
   225 			});
       
   226 
       
   227 			// Load more items.
       
   228 			this.sectionContent.scroll( function() {
       
   229 				var totalHeight = self.$el.find( '.accordion-section.open .available-menu-items-list' ).prop( 'scrollHeight' ),
       
   230 					visibleHeight = self.$el.find( '.accordion-section.open' ).height();
       
   231 
       
   232 				if ( ! self.loading && $( this ).scrollTop() > 3 / 4 * totalHeight - visibleHeight ) {
       
   233 					var type = $( this ).data( 'type' ),
       
   234 						object = $( this ).data( 'object' );
       
   235 
       
   236 					if ( 'search' === type ) {
       
   237 						if ( self.searchTerm ) {
       
   238 							self.doSearch( self.pages.search );
       
   239 						}
       
   240 					} else {
       
   241 						self.loadItems( [
       
   242 							{ type: type, object: object }
       
   243 						] );
       
   244 					}
       
   245 				}
       
   246 			});
       
   247 
       
   248 			// Close the panel if the URL in the preview changes
       
   249 			api.previewer.bind( 'url', this.close );
       
   250 
       
   251 			self.delegateEvents();
       
   252 		},
       
   253 
       
   254 		// Search input change handler.
       
   255 		search: function( event ) {
       
   256 			var $searchSection = $( '#available-menu-items-search' ),
       
   257 				$otherSections = $( '#available-menu-items .accordion-section' ).not( $searchSection );
       
   258 
       
   259 			if ( ! event ) {
       
   260 				return;
       
   261 			}
       
   262 
       
   263 			if ( this.searchTerm === event.target.value ) {
       
   264 				return;
       
   265 			}
       
   266 
       
   267 			if ( '' !== event.target.value && ! $searchSection.hasClass( 'open' ) ) {
       
   268 				$otherSections.fadeOut( 100 );
       
   269 				$searchSection.find( '.accordion-section-content' ).slideDown( 'fast' );
       
   270 				$searchSection.addClass( 'open' );
       
   271 				this.$clearResults.addClass( 'is-visible' );
       
   272 			} else if ( '' === event.target.value ) {
       
   273 				$searchSection.removeClass( 'open' );
       
   274 				$otherSections.show();
       
   275 				this.$clearResults.removeClass( 'is-visible' );
       
   276 			}
       
   277 
       
   278 			this.searchTerm = event.target.value;
       
   279 			this.pages.search = 1;
       
   280 			this.doSearch( 1 );
       
   281 		},
       
   282 
       
   283 		// Get search results.
       
   284 		doSearch: function( page ) {
       
   285 			var self = this, params,
       
   286 				$section = $( '#available-menu-items-search' ),
       
   287 				$content = $section.find( '.accordion-section-content' ),
       
   288 				itemTemplate = wp.template( 'available-menu-item' );
       
   289 
       
   290 			if ( self.currentRequest ) {
       
   291 				self.currentRequest.abort();
       
   292 			}
       
   293 
       
   294 			if ( page < 0 ) {
       
   295 				return;
       
   296 			} else if ( page > 1 ) {
       
   297 				$section.addClass( 'loading-more' );
       
   298 				$content.attr( 'aria-busy', 'true' );
       
   299 				wp.a11y.speak( api.Menus.data.l10n.itemsLoadingMore );
       
   300 			} else if ( '' === self.searchTerm ) {
       
   301 				$content.html( '' );
       
   302 				wp.a11y.speak( '' );
       
   303 				return;
       
   304 			}
       
   305 
       
   306 			$section.addClass( 'loading' );
       
   307 			self.loading = true;
       
   308 
       
   309 			params = api.previewer.query( { excludeCustomizedSaved: true } );
       
   310 			_.extend( params, {
       
   311 				'customize-menus-nonce': api.settings.nonce['customize-menus'],
       
   312 				'wp_customize': 'on',
       
   313 				'search': self.searchTerm,
       
   314 				'page': page
       
   315 			} );
       
   316 
       
   317 			self.currentRequest = wp.ajax.post( 'search-available-menu-items-customizer', params );
       
   318 
       
   319 			self.currentRequest.done(function( data ) {
       
   320 				var items;
       
   321 				if ( 1 === page ) {
       
   322 					// Clear previous results as it's a new search.
       
   323 					$content.empty();
       
   324 				}
       
   325 				$section.removeClass( 'loading loading-more' );
       
   326 				$content.attr( 'aria-busy', 'false' );
       
   327 				$section.addClass( 'open' );
       
   328 				self.loading = false;
       
   329 				items = new api.Menus.AvailableItemCollection( data.items );
       
   330 				self.collection.add( items.models );
       
   331 				items.each( function( menuItem ) {
       
   332 					$content.append( itemTemplate( menuItem.attributes ) );
       
   333 				} );
       
   334 				if ( 20 > items.length ) {
       
   335 					self.pages.search = -1; // Up to 20 posts and 20 terms in results, if <20, no more results for either.
       
   336 				} else {
       
   337 					self.pages.search = self.pages.search + 1;
       
   338 				}
       
   339 				if ( items && page > 1 ) {
       
   340 					wp.a11y.speak( api.Menus.data.l10n.itemsFoundMore.replace( '%d', items.length ) );
       
   341 				} else if ( items && page === 1 ) {
       
   342 					wp.a11y.speak( api.Menus.data.l10n.itemsFound.replace( '%d', items.length ) );
       
   343 				}
       
   344 			});
       
   345 
       
   346 			self.currentRequest.fail(function( data ) {
       
   347 				// data.message may be undefined, for example when typing slow and the request is aborted.
       
   348 				if ( data.message ) {
       
   349 					$content.empty().append( $( '<li class="nothing-found"></li>' ).text( data.message ) );
       
   350 					wp.a11y.speak( data.message );
       
   351 				}
       
   352 				self.pages.search = -1;
       
   353 			});
       
   354 
       
   355 			self.currentRequest.always(function() {
       
   356 				$section.removeClass( 'loading loading-more' );
       
   357 				$content.attr( 'aria-busy', 'false' );
       
   358 				self.loading = false;
       
   359 				self.currentRequest = null;
       
   360 			});
       
   361 		},
       
   362 
       
   363 		// Render the individual items.
       
   364 		initList: function() {
       
   365 			var self = this;
       
   366 
       
   367 			// Render the template for each item by type.
       
   368 			_.each( api.Menus.data.itemTypes, function( itemType ) {
       
   369 				self.pages[ itemType.type + ':' + itemType.object ] = 0;
       
   370 			} );
       
   371 			self.loadItems( api.Menus.data.itemTypes );
       
   372 		},
       
   373 
       
   374 		/**
       
   375 		 * Load available nav menu items.
       
   376 		 *
       
   377 		 * @since 4.3.0
       
   378 		 * @since 4.7.0 Changed function signature to take list of item types instead of single type/object.
       
   379 		 * @access private
       
   380 		 *
       
   381 		 * @param {Array.<object>} itemTypes List of objects containing type and key.
       
   382 		 * @param {string} deprecated Formerly the object parameter.
       
   383 		 * @returns {void}
       
   384 		 */
       
   385 		loadItems: function( itemTypes, deprecated ) {
       
   386 			var self = this, _itemTypes, requestItemTypes = [], params, request, itemTemplate, availableMenuItemContainers = {};
       
   387 			itemTemplate = wp.template( 'available-menu-item' );
       
   388 
       
   389 			if ( _.isString( itemTypes ) && _.isString( deprecated ) ) {
       
   390 				_itemTypes = [ { type: itemTypes, object: deprecated } ];
       
   391 			} else {
       
   392 				_itemTypes = itemTypes;
       
   393 			}
       
   394 
       
   395 			_.each( _itemTypes, function( itemType ) {
       
   396 				var container, name = itemType.type + ':' + itemType.object;
       
   397 				if ( -1 === self.pages[ name ] ) {
       
   398 					return; // Skip types for which there are no more results.
       
   399 				}
       
   400 				container = $( '#available-menu-items-' + itemType.type + '-' + itemType.object );
       
   401 				container.find( '.accordion-section-title' ).addClass( 'loading' );
       
   402 				availableMenuItemContainers[ name ] = container;
       
   403 
       
   404 				requestItemTypes.push( {
       
   405 					object: itemType.object,
       
   406 					type: itemType.type,
       
   407 					page: self.pages[ name ]
       
   408 				} );
       
   409 			} );
       
   410 
       
   411 			if ( 0 === requestItemTypes.length ) {
       
   412 				return;
       
   413 			}
       
   414 
       
   415 			self.loading = true;
       
   416 
       
   417 			params = api.previewer.query( { excludeCustomizedSaved: true } );
       
   418 			_.extend( params, {
       
   419 				'customize-menus-nonce': api.settings.nonce['customize-menus'],
       
   420 				'wp_customize': 'on',
       
   421 				'item_types': requestItemTypes
       
   422 			} );
       
   423 
       
   424 			request = wp.ajax.post( 'load-available-menu-items-customizer', params );
       
   425 
       
   426 			request.done(function( data ) {
       
   427 				var typeInner;
       
   428 				_.each( data.items, function( typeItems, name ) {
       
   429 					if ( 0 === typeItems.length ) {
       
   430 						if ( 0 === self.pages[ name ] ) {
       
   431 							availableMenuItemContainers[ name ].find( '.accordion-section-title' )
       
   432 								.addClass( 'cannot-expand' )
       
   433 								.removeClass( 'loading' )
       
   434 								.find( '.accordion-section-title > button' )
       
   435 								.prop( 'tabIndex', -1 );
       
   436 						}
       
   437 						self.pages[ name ] = -1;
       
   438 						return;
       
   439 					} else if ( ( 'post_type:page' === name ) && ( ! availableMenuItemContainers[ name ].hasClass( 'open' ) ) ) {
       
   440 						availableMenuItemContainers[ name ].find( '.accordion-section-title > button' ).click();
       
   441 					}
       
   442 					typeItems = new api.Menus.AvailableItemCollection( typeItems ); // @todo Why is this collection created and then thrown away?
       
   443 					self.collection.add( typeItems.models );
       
   444 					typeInner = availableMenuItemContainers[ name ].find( '.available-menu-items-list' );
       
   445 					typeItems.each( function( menuItem ) {
       
   446 						typeInner.append( itemTemplate( menuItem.attributes ) );
       
   447 					} );
       
   448 					self.pages[ name ] += 1;
       
   449 				});
       
   450 			});
       
   451 			request.fail(function( data ) {
       
   452 				if ( typeof console !== 'undefined' && console.error ) {
       
   453 					console.error( data );
       
   454 				}
       
   455 			});
       
   456 			request.always(function() {
       
   457 				_.each( availableMenuItemContainers, function( container ) {
       
   458 					container.find( '.accordion-section-title' ).removeClass( 'loading' );
       
   459 				} );
       
   460 				self.loading = false;
       
   461 			});
       
   462 		},
       
   463 
       
   464 		// Adjust the height of each section of items to fit the screen.
       
   465 		itemSectionHeight: function() {
       
   466 			var sections, lists, totalHeight, accordionHeight, diff;
       
   467 			totalHeight = window.innerHeight;
       
   468 			sections = this.$el.find( '.accordion-section:not( #available-menu-items-search ) .accordion-section-content' );
       
   469 			lists = this.$el.find( '.accordion-section:not( #available-menu-items-search ) .available-menu-items-list:not(":only-child")' );
       
   470 			accordionHeight =  46 * ( 1 + sections.length ) + 14; // Magic numbers.
       
   471 			diff = totalHeight - accordionHeight;
       
   472 			if ( 120 < diff && 290 > diff ) {
       
   473 				sections.css( 'max-height', diff );
       
   474 				lists.css( 'max-height', ( diff - 60 ) );
       
   475 			}
       
   476 		},
       
   477 
       
   478 		// Highlights a menu item.
       
   479 		select: function( menuitemTpl ) {
       
   480 			this.selected = $( menuitemTpl );
       
   481 			this.selected.siblings( '.menu-item-tpl' ).removeClass( 'selected' );
       
   482 			this.selected.addClass( 'selected' );
       
   483 		},
       
   484 
       
   485 		// Highlights a menu item on focus.
       
   486 		focus: function( event ) {
       
   487 			this.select( $( event.currentTarget ) );
       
   488 		},
       
   489 
       
   490 		// Submit handler for keypress and click on menu item.
       
   491 		_submit: function( event ) {
       
   492 			// Only proceed with keypress if it is Enter or Spacebar
       
   493 			if ( 'keypress' === event.type && ( 13 !== event.which && 32 !== event.which ) ) {
       
   494 				return;
       
   495 			}
       
   496 
       
   497 			this.submit( $( event.currentTarget ) );
       
   498 		},
       
   499 
       
   500 		// Adds a selected menu item to the menu.
       
   501 		submit: function( menuitemTpl ) {
       
   502 			var menuitemId, menu_item;
       
   503 
       
   504 			if ( ! menuitemTpl ) {
       
   505 				menuitemTpl = this.selected;
       
   506 			}
       
   507 
       
   508 			if ( ! menuitemTpl || ! this.currentMenuControl ) {
       
   509 				return;
       
   510 			}
       
   511 
       
   512 			this.select( menuitemTpl );
       
   513 
       
   514 			menuitemId = $( this.selected ).data( 'menu-item-id' );
       
   515 			menu_item = this.collection.findWhere( { id: menuitemId } );
       
   516 			if ( ! menu_item ) {
       
   517 				return;
       
   518 			}
       
   519 
       
   520 			this.currentMenuControl.addItemToMenu( menu_item.attributes );
       
   521 
       
   522 			$( menuitemTpl ).find( '.menu-item-handle' ).addClass( 'item-added' );
       
   523 		},
       
   524 
       
   525 		// Submit handler for keypress and click on custom menu item.
       
   526 		_submitLink: function( event ) {
       
   527 			// Only proceed with keypress if it is Enter.
       
   528 			if ( 'keypress' === event.type && 13 !== event.which ) {
       
   529 				return;
       
   530 			}
       
   531 
       
   532 			this.submitLink();
       
   533 		},
       
   534 
       
   535 		// Adds the custom menu item to the menu.
       
   536 		submitLink: function() {
       
   537 			var menuItem,
       
   538 				itemName = $( '#custom-menu-item-name' ),
       
   539 				itemUrl = $( '#custom-menu-item-url' ),
       
   540 				urlRegex;
       
   541 
       
   542 			if ( ! this.currentMenuControl ) {
       
   543 				return;
       
   544 			}
       
   545 
       
   546 			/*
       
   547 			 * Allow URLs including:
       
   548 			 * - http://example.com/
       
   549 			 * - //example.com
       
   550 			 * - /directory/
       
   551 			 * - ?query-param
       
   552 			 * - #target
       
   553 			 * - mailto:foo@example.com
       
   554 			 *
       
   555 			 * Any further validation will be handled on the server when the setting is attempted to be saved,
       
   556 			 * so this pattern does not need to be complete.
       
   557 			 */
       
   558 			urlRegex = /^((\w+:)?\/\/\w.*|\w+:(?!\/\/$)|\/|\?|#)/;
       
   559 
       
   560 			if ( '' === itemName.val() ) {
       
   561 				itemName.addClass( 'invalid' );
       
   562 				return;
       
   563 			} else if ( ! urlRegex.test( itemUrl.val() ) ) {
       
   564 				itemUrl.addClass( 'invalid' );
       
   565 				return;
       
   566 			}
       
   567 
       
   568 			menuItem = {
       
   569 				'title': itemName.val(),
       
   570 				'url': itemUrl.val(),
       
   571 				'type': 'custom',
       
   572 				'type_label': api.Menus.data.l10n.custom_label,
       
   573 				'object': 'custom'
       
   574 			};
       
   575 
       
   576 			this.currentMenuControl.addItemToMenu( menuItem );
       
   577 
       
   578 			// Reset the custom link form.
       
   579 			itemUrl.val( 'http://' );
       
   580 			itemName.val( '' );
       
   581 		},
       
   582 
       
   583 		/**
       
   584 		 * Submit handler for keypress (enter) on field and click on button.
       
   585 		 *
       
   586 		 * @since 4.7.0
       
   587 		 * @private
       
   588 		 *
       
   589 		 * @param {jQuery.Event} event Event.
       
   590 		 * @returns {void}
       
   591 		 */
       
   592 		_submitNew: function( event ) {
       
   593 			var container;
       
   594 
       
   595 			// Only proceed with keypress if it is Enter.
       
   596 			if ( 'keypress' === event.type && 13 !== event.which ) {
       
   597 				return;
       
   598 			}
       
   599 
       
   600 			if ( this.addingNew ) {
       
   601 				return;
       
   602 			}
       
   603 
       
   604 			container = $( event.target ).closest( '.accordion-section' );
       
   605 
       
   606 			this.submitNew( container );
       
   607 		},
       
   608 
       
   609 		/**
       
   610 		 * Creates a new object and adds an associated menu item to the menu.
       
   611 		 *
       
   612 		 * @since 4.7.0
       
   613 		 * @private
       
   614 		 *
       
   615 		 * @param {jQuery} container
       
   616 		 * @returns {void}
       
   617 		 */
       
   618 		submitNew: function( container ) {
       
   619 			var panel = this,
       
   620 				itemName = container.find( '.create-item-input' ),
       
   621 				title = itemName.val(),
       
   622 				dataContainer = container.find( '.available-menu-items-list' ),
       
   623 				itemType = dataContainer.data( 'type' ),
       
   624 				itemObject = dataContainer.data( 'object' ),
       
   625 				itemTypeLabel = dataContainer.data( 'type_label' ),
       
   626 				promise;
       
   627 
       
   628 			if ( ! this.currentMenuControl ) {
       
   629 				return;
       
   630 			}
       
   631 
       
   632 			// Only posts are supported currently.
       
   633 			if ( 'post_type' !== itemType ) {
       
   634 				return;
       
   635 			}
       
   636 
       
   637 			if ( '' === $.trim( itemName.val() ) ) {
       
   638 				itemName.addClass( 'invalid' );
       
   639 				itemName.focus();
       
   640 				return;
       
   641 			} else {
       
   642 				itemName.removeClass( 'invalid' );
       
   643 				container.find( '.accordion-section-title' ).addClass( 'loading' );
       
   644 			}
       
   645 
       
   646 			panel.addingNew = true;
       
   647 			itemName.attr( 'disabled', 'disabled' );
       
   648 			promise = api.Menus.insertAutoDraftPost( {
       
   649 				post_title: title,
       
   650 				post_type: itemObject
       
   651 			} );
       
   652 			promise.done( function( data ) {
       
   653 				var availableItem, $content, itemElement;
       
   654 				availableItem = new api.Menus.AvailableItemModel( {
       
   655 					'id': 'post-' + data.post_id, // Used for available menu item Backbone models.
       
   656 					'title': itemName.val(),
       
   657 					'type': itemType,
       
   658 					'type_label': itemTypeLabel,
       
   659 					'object': itemObject,
       
   660 					'object_id': data.post_id,
       
   661 					'url': data.url
       
   662 				} );
       
   663 
       
   664 				// Add new item to menu.
       
   665 				panel.currentMenuControl.addItemToMenu( availableItem.attributes );
       
   666 
       
   667 				// Add the new item to the list of available items.
       
   668 				api.Menus.availableMenuItemsPanel.collection.add( availableItem );
       
   669 				$content = container.find( '.available-menu-items-list' );
       
   670 				itemElement = $( wp.template( 'available-menu-item' )( availableItem.attributes ) );
       
   671 				itemElement.find( '.menu-item-handle:first' ).addClass( 'item-added' );
       
   672 				$content.prepend( itemElement );
       
   673 				$content.scrollTop();
       
   674 
       
   675 				// Reset the create content form.
       
   676 				itemName.val( '' ).removeAttr( 'disabled' );
       
   677 				panel.addingNew = false;
       
   678 				container.find( '.accordion-section-title' ).removeClass( 'loading' );
       
   679 			} );
       
   680 		},
       
   681 
       
   682 		// Opens the panel.
       
   683 		open: function( menuControl ) {
       
   684 			var panel = this, close;
       
   685 
       
   686 			this.currentMenuControl = menuControl;
       
   687 
       
   688 			this.itemSectionHeight();
       
   689 
       
   690 			if ( api.section.has( 'publish_settings' ) ) {
       
   691 				api.section( 'publish_settings' ).collapse();
       
   692 			}
       
   693 
       
   694 			$( 'body' ).addClass( 'adding-menu-items' );
       
   695 
       
   696 			close = function() {
       
   697 				panel.close();
       
   698 				$( this ).off( 'click', close );
       
   699 			};
       
   700 			$( '#customize-preview' ).on( 'click', close );
       
   701 
       
   702 			// Collapse all controls.
       
   703 			_( this.currentMenuControl.getMenuItemControls() ).each( function( control ) {
       
   704 				control.collapseForm();
       
   705 			} );
       
   706 
       
   707 			this.$el.find( '.selected' ).removeClass( 'selected' );
       
   708 
       
   709 			this.$search.focus();
       
   710 		},
       
   711 
       
   712 		// Closes the panel
       
   713 		close: function( options ) {
       
   714 			options = options || {};
       
   715 
       
   716 			if ( options.returnFocus && this.currentMenuControl ) {
       
   717 				this.currentMenuControl.container.find( '.add-new-menu-item' ).focus();
       
   718 			}
       
   719 
       
   720 			this.currentMenuControl = null;
       
   721 			this.selected = null;
       
   722 
       
   723 			$( 'body' ).removeClass( 'adding-menu-items' );
       
   724 			$( '#available-menu-items .menu-item-handle.item-added' ).removeClass( 'item-added' );
       
   725 
       
   726 			this.$search.val( '' ).trigger( 'keyup' );
       
   727 		},
       
   728 
       
   729 		// Add a few keyboard enhancements to the panel.
       
   730 		keyboardAccessible: function( event ) {
       
   731 			var isEnter = ( 13 === event.which ),
       
   732 				isEsc = ( 27 === event.which ),
       
   733 				isBackTab = ( 9 === event.which && event.shiftKey ),
       
   734 				isSearchFocused = $( event.target ).is( this.$search );
       
   735 
       
   736 			// If enter pressed but nothing entered, don't do anything
       
   737 			if ( isEnter && ! this.$search.val() ) {
       
   738 				return;
       
   739 			}
       
   740 
       
   741 			if ( isSearchFocused && isBackTab ) {
       
   742 				this.currentMenuControl.container.find( '.add-new-menu-item' ).focus();
       
   743 				event.preventDefault(); // Avoid additional back-tab.
       
   744 			} else if ( isEsc ) {
       
   745 				this.close( { returnFocus: true } );
       
   746 			}
       
   747 		}
       
   748 	});
       
   749 
       
   750 	/**
       
   751 	 * wp.customize.Menus.MenusPanel
       
   752 	 *
       
   753 	 * Customizer panel for menus. This is used only for screen options management.
       
   754 	 * Note that 'menus' must match the WP_Customize_Menu_Panel::$type.
       
   755 	 *
       
   756 	 * @constructor
       
   757 	 * @augments wp.customize.Panel
       
   758 	 */
       
   759 	api.Menus.MenusPanel = api.Panel.extend({
       
   760 
       
   761 		attachEvents: function() {
       
   762 			api.Panel.prototype.attachEvents.call( this );
       
   763 
       
   764 			var panel = this,
       
   765 				panelMeta = panel.container.find( '.panel-meta' ),
       
   766 				help = panelMeta.find( '.customize-help-toggle' ),
       
   767 				content = panelMeta.find( '.customize-panel-description' ),
       
   768 				options = $( '#screen-options-wrap' ),
       
   769 				button = panelMeta.find( '.customize-screen-options-toggle' );
       
   770 			button.on( 'click keydown', function( event ) {
       
   771 				if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
       
   772 					return;
       
   773 				}
       
   774 				event.preventDefault();
       
   775 
       
   776 				// Hide description
       
   777 				if ( content.not( ':hidden' ) ) {
       
   778 					content.slideUp( 'fast' );
       
   779 					help.attr( 'aria-expanded', 'false' );
       
   780 				}
       
   781 
       
   782 				if ( 'true' === button.attr( 'aria-expanded' ) ) {
       
   783 					button.attr( 'aria-expanded', 'false' );
       
   784 					panelMeta.removeClass( 'open' );
       
   785 					panelMeta.removeClass( 'active-menu-screen-options' );
       
   786 					options.slideUp( 'fast' );
       
   787 				} else {
       
   788 					button.attr( 'aria-expanded', 'true' );
       
   789 					panelMeta.addClass( 'open' );
       
   790 					panelMeta.addClass( 'active-menu-screen-options' );
       
   791 					options.slideDown( 'fast' );
       
   792 				}
       
   793 
       
   794 				return false;
       
   795 			} );
       
   796 
       
   797 			// Help toggle
       
   798 			help.on( 'click keydown', function( event ) {
       
   799 				if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
       
   800 					return;
       
   801 				}
       
   802 				event.preventDefault();
       
   803 
       
   804 				if ( 'true' === button.attr( 'aria-expanded' ) ) {
       
   805 					button.attr( 'aria-expanded', 'false' );
       
   806 					help.attr( 'aria-expanded', 'true' );
       
   807 					panelMeta.addClass( 'open' );
       
   808 					panelMeta.removeClass( 'active-menu-screen-options' );
       
   809 					options.slideUp( 'fast' );
       
   810 					content.slideDown( 'fast' );
       
   811 				}
       
   812 			} );
       
   813 		},
       
   814 
       
   815 		/**
       
   816 		 * Update field visibility when clicking on the field toggles.
       
   817 		 */
       
   818 		ready: function() {
       
   819 			var panel = this;
       
   820 			panel.container.find( '.hide-column-tog' ).click( function() {
       
   821 				panel.saveManageColumnsState();
       
   822 			});
       
   823 
       
   824 			// Inject additional heading into the menu locations section's head container.
       
   825 			api.section( 'menu_locations', function( section ) {
       
   826 				section.headContainer.prepend(
       
   827 					wp.template( 'nav-menu-locations-header' )( api.Menus.data )
       
   828 				);
       
   829 			} );
       
   830 		},
       
   831 
       
   832 		/**
       
   833 		 * Save hidden column states.
       
   834 		 *
       
   835 		 * @since 4.3.0
       
   836 		 * @private
       
   837 		 *
       
   838 		 * @returns {void}
       
   839 		 */
       
   840 		saveManageColumnsState: _.debounce( function() {
       
   841 			var panel = this;
       
   842 			if ( panel._updateHiddenColumnsRequest ) {
       
   843 				panel._updateHiddenColumnsRequest.abort();
       
   844 			}
       
   845 
       
   846 			panel._updateHiddenColumnsRequest = wp.ajax.post( 'hidden-columns', {
       
   847 				hidden: panel.hidden(),
       
   848 				screenoptionnonce: $( '#screenoptionnonce' ).val(),
       
   849 				page: 'nav-menus'
       
   850 			} );
       
   851 			panel._updateHiddenColumnsRequest.always( function() {
       
   852 				panel._updateHiddenColumnsRequest = null;
       
   853 			} );
       
   854 		}, 2000 ),
       
   855 
       
   856 		/**
       
   857 		 * @deprecated Since 4.7.0 now that the nav_menu sections are responsible for toggling the classes on their own containers.
       
   858 		 */
       
   859 		checked: function() {},
       
   860 
       
   861 		/**
       
   862 		 * @deprecated Since 4.7.0 now that the nav_menu sections are responsible for toggling the classes on their own containers.
       
   863 		 */
       
   864 		unchecked: function() {},
       
   865 
       
   866 		/**
       
   867 		 * Get hidden fields.
       
   868 		 *
       
   869 		 * @since 4.3.0
       
   870 		 * @private
       
   871 		 *
       
   872 		 * @returns {Array} Fields (columns) that are hidden.
       
   873 		 */
       
   874 		hidden: function() {
       
   875 			return $( '.hide-column-tog' ).not( ':checked' ).map( function() {
       
   876 				var id = this.id;
       
   877 				return id.substring( 0, id.length - 5 );
       
   878 			}).get().join( ',' );
       
   879 		}
       
   880 	} );
       
   881 
       
   882 	/**
       
   883 	 * wp.customize.Menus.MenuSection
       
   884 	 *
       
   885 	 * Customizer section for menus. This is used only for lazy-loading child controls.
       
   886 	 * Note that 'nav_menu' must match the WP_Customize_Menu_Section::$type.
       
   887 	 *
       
   888 	 * @constructor
       
   889 	 * @augments wp.customize.Section
       
   890 	 */
       
   891 	api.Menus.MenuSection = api.Section.extend({
       
   892 
       
   893 		/**
       
   894 		 * Initialize.
       
   895 		 *
       
   896 		 * @since 4.3.0
       
   897 		 *
       
   898 		 * @param {String} id
       
   899 		 * @param {Object} options
       
   900 		 */
       
   901 		initialize: function( id, options ) {
       
   902 			var section = this;
       
   903 			api.Section.prototype.initialize.call( section, id, options );
       
   904 			section.deferred.initSortables = $.Deferred();
       
   905 		},
       
   906 
       
   907 		/**
       
   908 		 * Ready.
       
   909 		 */
       
   910 		ready: function() {
       
   911 			var section = this, fieldActiveToggles, handleFieldActiveToggle;
       
   912 
       
   913 			if ( 'undefined' === typeof section.params.menu_id ) {
       
   914 				throw new Error( 'params.menu_id was not defined' );
       
   915 			}
       
   916 
       
   917 			/*
       
   918 			 * Since newly created sections won't be registered in PHP, we need to prevent the
       
   919 			 * preview's sending of the activeSections to result in this control
       
   920 			 * being deactivated when the preview refreshes. So we can hook onto
       
   921 			 * the setting that has the same ID and its presence can dictate
       
   922 			 * whether the section is active.
       
   923 			 */
       
   924 			section.active.validate = function() {
       
   925 				if ( ! api.has( section.id ) ) {
       
   926 					return false;
       
   927 				}
       
   928 				return !! api( section.id ).get();
       
   929 			};
       
   930 
       
   931 			section.populateControls();
       
   932 
       
   933 			section.navMenuLocationSettings = {};
       
   934 			section.assignedLocations = new api.Value( [] );
       
   935 
       
   936 			api.each(function( setting, id ) {
       
   937 				var matches = id.match( /^nav_menu_locations\[(.+?)]/ );
       
   938 				if ( matches ) {
       
   939 					section.navMenuLocationSettings[ matches[1] ] = setting;
       
   940 					setting.bind( function() {
       
   941 						section.refreshAssignedLocations();
       
   942 					});
       
   943 				}
       
   944 			});
       
   945 
       
   946 			section.assignedLocations.bind(function( to ) {
       
   947 				section.updateAssignedLocationsInSectionTitle( to );
       
   948 			});
       
   949 
       
   950 			section.refreshAssignedLocations();
       
   951 
       
   952 			api.bind( 'pane-contents-reflowed', function() {
       
   953 				// Skip menus that have been removed.
       
   954 				if ( ! section.contentContainer.parent().length ) {
       
   955 					return;
       
   956 				}
       
   957 				section.container.find( '.menu-item .menu-item-reorder-nav button' ).attr({ 'tabindex': '0', 'aria-hidden': 'false' });
       
   958 				section.container.find( '.menu-item.move-up-disabled .menus-move-up' ).attr({ 'tabindex': '-1', 'aria-hidden': 'true' });
       
   959 				section.container.find( '.menu-item.move-down-disabled .menus-move-down' ).attr({ 'tabindex': '-1', 'aria-hidden': 'true' });
       
   960 				section.container.find( '.menu-item.move-left-disabled .menus-move-left' ).attr({ 'tabindex': '-1', 'aria-hidden': 'true' });
       
   961 				section.container.find( '.menu-item.move-right-disabled .menus-move-right' ).attr({ 'tabindex': '-1', 'aria-hidden': 'true' });
       
   962 			} );
       
   963 
       
   964 			/**
       
   965 			 * Update the active field class for the content container for a given checkbox toggle.
       
   966 			 *
       
   967 			 * @this {jQuery}
       
   968 			 * @returns {void}
       
   969 			 */
       
   970 			handleFieldActiveToggle = function() {
       
   971 				var className = 'field-' + $( this ).val() + '-active';
       
   972 				section.contentContainer.toggleClass( className, $( this ).prop( 'checked' ) );
       
   973 			};
       
   974 			fieldActiveToggles = api.panel( 'nav_menus' ).contentContainer.find( '.metabox-prefs:first' ).find( '.hide-column-tog' );
       
   975 			fieldActiveToggles.each( handleFieldActiveToggle );
       
   976 			fieldActiveToggles.on( 'click', handleFieldActiveToggle );
       
   977 		},
       
   978 
       
   979 		populateControls: function() {
       
   980 			var section = this,
       
   981 				menuNameControlId,
       
   982 				menuLocationsControlId,
       
   983 				menuAutoAddControlId,
       
   984 				menuDeleteControlId,
       
   985 				menuControl,
       
   986 				menuNameControl,
       
   987 				menuLocationsControl,
       
   988 				menuAutoAddControl,
       
   989 				menuDeleteControl;
       
   990 
       
   991 			// Add the control for managing the menu name.
       
   992 			menuNameControlId = section.id + '[name]';
       
   993 			menuNameControl = api.control( menuNameControlId );
       
   994 			if ( ! menuNameControl ) {
       
   995 				menuNameControl = new api.controlConstructor.nav_menu_name( menuNameControlId, {
       
   996 					type: 'nav_menu_name',
       
   997 					label: api.Menus.data.l10n.menuNameLabel,
       
   998 					section: section.id,
       
   999 					priority: 0,
       
  1000 					settings: {
       
  1001 						'default': section.id
       
  1002 					}
       
  1003 				} );
       
  1004 				api.control.add( menuNameControl );
       
  1005 				menuNameControl.active.set( true );
       
  1006 			}
       
  1007 
       
  1008 			// Add the menu control.
       
  1009 			menuControl = api.control( section.id );
       
  1010 			if ( ! menuControl ) {
       
  1011 				menuControl = new api.controlConstructor.nav_menu( section.id, {
       
  1012 					type: 'nav_menu',
       
  1013 					section: section.id,
       
  1014 					priority: 998,
       
  1015 					settings: {
       
  1016 						'default': section.id
       
  1017 					},
       
  1018 					menu_id: section.params.menu_id
       
  1019 				} );
       
  1020 				api.control.add( menuControl );
       
  1021 				menuControl.active.set( true );
       
  1022 			}
       
  1023 
       
  1024 			// Add the menu locations control.
       
  1025 			menuLocationsControlId = section.id + '[locations]';
       
  1026 			menuLocationsControl = api.control( menuLocationsControlId );
       
  1027 			if ( ! menuLocationsControl ) {
       
  1028 				menuLocationsControl = new api.controlConstructor.nav_menu_locations( menuLocationsControlId, {
       
  1029 					section: section.id,
       
  1030 					priority: 999,
       
  1031 					settings: {
       
  1032 						'default': section.id
       
  1033 					},
       
  1034 					menu_id: section.params.menu_id
       
  1035 				} );
       
  1036 				api.control.add( menuLocationsControl.id, menuLocationsControl );
       
  1037 				menuControl.active.set( true );
       
  1038 			}
       
  1039 
       
  1040 			// Add the control for managing the menu auto_add.
       
  1041 			menuAutoAddControlId = section.id + '[auto_add]';
       
  1042 			menuAutoAddControl = api.control( menuAutoAddControlId );
       
  1043 			if ( ! menuAutoAddControl ) {
       
  1044 				menuAutoAddControl = new api.controlConstructor.nav_menu_auto_add( menuAutoAddControlId, {
       
  1045 					type: 'nav_menu_auto_add',
       
  1046 					label: '',
       
  1047 					section: section.id,
       
  1048 					priority: 1000,
       
  1049 					settings: {
       
  1050 						'default': section.id
       
  1051 					}
       
  1052 				} );
       
  1053 				api.control.add( menuAutoAddControl );
       
  1054 				menuAutoAddControl.active.set( true );
       
  1055 			}
       
  1056 
       
  1057 			// Add the control for deleting the menu
       
  1058 			menuDeleteControlId = section.id + '[delete]';
       
  1059 			menuDeleteControl = api.control( menuDeleteControlId );
       
  1060 			if ( ! menuDeleteControl ) {
       
  1061 				menuDeleteControl = new api.Control( menuDeleteControlId, {
       
  1062 					section: section.id,
       
  1063 					priority: 1001,
       
  1064 					templateId: 'nav-menu-delete-button'
       
  1065 				} );
       
  1066 				api.control.add( menuDeleteControl.id, menuDeleteControl );
       
  1067 				menuDeleteControl.active.set( true );
       
  1068 				menuDeleteControl.deferred.embedded.done( function () {
       
  1069 					menuDeleteControl.container.find( 'button' ).on( 'click', function() {
       
  1070 						var menuId = section.params.menu_id;
       
  1071 						var menuControl = api.Menus.getMenuControl( menuId );
       
  1072 						menuControl.setting.set( false );
       
  1073 					});
       
  1074 				} );
       
  1075 			}
       
  1076 		},
       
  1077 
       
  1078 		/**
       
  1079 		 *
       
  1080 		 */
       
  1081 		refreshAssignedLocations: function() {
       
  1082 			var section = this,
       
  1083 				menuTermId = section.params.menu_id,
       
  1084 				currentAssignedLocations = [];
       
  1085 			_.each( section.navMenuLocationSettings, function( setting, themeLocation ) {
       
  1086 				if ( setting() === menuTermId ) {
       
  1087 					currentAssignedLocations.push( themeLocation );
       
  1088 				}
       
  1089 			});
       
  1090 			section.assignedLocations.set( currentAssignedLocations );
       
  1091 		},
       
  1092 
       
  1093 		/**
       
  1094 		 * @param {Array} themeLocationSlugs Theme location slugs.
       
  1095 		 */
       
  1096 		updateAssignedLocationsInSectionTitle: function( themeLocationSlugs ) {
       
  1097 			var section = this,
       
  1098 				$title;
       
  1099 
       
  1100 			$title = section.container.find( '.accordion-section-title:first' );
       
  1101 			$title.find( '.menu-in-location' ).remove();
       
  1102 			_.each( themeLocationSlugs, function( themeLocationSlug ) {
       
  1103 				var $label, locationName;
       
  1104 				$label = $( '<span class="menu-in-location"></span>' );
       
  1105 				locationName = api.Menus.data.locationSlugMappedToName[ themeLocationSlug ];
       
  1106 				$label.text( api.Menus.data.l10n.menuLocation.replace( '%s', locationName ) );
       
  1107 				$title.append( $label );
       
  1108 			});
       
  1109 
       
  1110 			section.container.toggleClass( 'assigned-to-menu-location', 0 !== themeLocationSlugs.length );
       
  1111 
       
  1112 		},
       
  1113 
       
  1114 		onChangeExpanded: function( expanded, args ) {
       
  1115 			var section = this, completeCallback;
       
  1116 
       
  1117 			if ( expanded ) {
       
  1118 				wpNavMenu.menuList = section.contentContainer;
       
  1119 				wpNavMenu.targetList = wpNavMenu.menuList;
       
  1120 
       
  1121 				// Add attributes needed by wpNavMenu
       
  1122 				$( '#menu-to-edit' ).removeAttr( 'id' );
       
  1123 				wpNavMenu.menuList.attr( 'id', 'menu-to-edit' ).addClass( 'menu' );
       
  1124 
       
  1125 				_.each( api.section( section.id ).controls(), function( control ) {
       
  1126 					if ( 'nav_menu_item' === control.params.type ) {
       
  1127 						control.actuallyEmbed();
       
  1128 					}
       
  1129 				} );
       
  1130 
       
  1131 				// Make sure Sortables is initialized after the section has been expanded to prevent `offset` issues.
       
  1132 				if ( args.completeCallback ) {
       
  1133 					completeCallback = args.completeCallback;
       
  1134 				}
       
  1135 				args.completeCallback = function() {
       
  1136 					if ( 'resolved' !== section.deferred.initSortables.state() ) {
       
  1137 						wpNavMenu.initSortables(); // Depends on menu-to-edit ID being set above.
       
  1138 						section.deferred.initSortables.resolve( wpNavMenu.menuList ); // Now MenuControl can extend the sortable.
       
  1139 
       
  1140 						// @todo Note that wp.customize.reflowPaneContents() is debounced, so this immediate change will show a slight flicker while priorities get updated.
       
  1141 						api.control( 'nav_menu[' + String( section.params.menu_id ) + ']' ).reflowMenuItems();
       
  1142 					}
       
  1143 					if ( _.isFunction( completeCallback ) ) {
       
  1144 						completeCallback();
       
  1145 					}
       
  1146 				};
       
  1147 			}
       
  1148 			api.Section.prototype.onChangeExpanded.call( section, expanded, args );
       
  1149 		},
       
  1150 
       
  1151 		/**
       
  1152 		 * Highlight how a user may create new menu items.
       
  1153 		 *
       
  1154 		 * This method reminds the user to create new menu items and how.
       
  1155 		 * It's exposed this way because this class knows best which UI needs
       
  1156 		 * highlighted but those expanding this section know more about why and
       
  1157 		 * when the affordance should be highlighted.
       
  1158 		 *
       
  1159 		 * @since 4.9.0
       
  1160 		 *
       
  1161 		 * @returns {void}
       
  1162 		 */
       
  1163 		highlightNewItemButton: function() {
       
  1164 			api.utils.highlightButton( this.contentContainer.find( '.add-new-menu-item' ), { delay: 2000 } );
       
  1165 		}
       
  1166 	});
       
  1167 
       
  1168 	/**
       
  1169 	 * Create a nav menu setting and section.
       
  1170 	 *
       
  1171 	 * @since 4.9.0
       
  1172 	 *
       
  1173 	 * @param {string} [name=''] Nav menu name.
       
  1174 	 * @returns {wp.customize.Menus.MenuSection} Added nav menu.
       
  1175 	 */
       
  1176 	api.Menus.createNavMenu = function createNavMenu( name ) {
       
  1177 		var customizeId, placeholderId, setting;
       
  1178 		placeholderId = api.Menus.generatePlaceholderAutoIncrementId();
       
  1179 
       
  1180 		customizeId = 'nav_menu[' + String( placeholderId ) + ']';
       
  1181 
       
  1182 		// Register the menu control setting.
       
  1183 		setting = api.create( customizeId, customizeId, {}, {
       
  1184 			type: 'nav_menu',
       
  1185 			transport: api.Menus.data.settingTransport,
       
  1186 			previewer: api.previewer
       
  1187 		} );
       
  1188 		setting.set( $.extend(
       
  1189 			{},
       
  1190 			api.Menus.data.defaultSettingValues.nav_menu,
       
  1191 			{
       
  1192 				name: name || ''
       
  1193 			}
       
  1194 		) );
       
  1195 
       
  1196 		/*
       
  1197 		 * Add the menu section (and its controls).
       
  1198 		 * Note that this will automatically create the required controls
       
  1199 		 * inside via the Section's ready method.
       
  1200 		 */
       
  1201 		return api.section.add( new api.Menus.MenuSection( customizeId, {
       
  1202 			panel: 'nav_menus',
       
  1203 			title: displayNavMenuName( name ),
       
  1204 			customizeAction: api.Menus.data.l10n.customizingMenus,
       
  1205 			priority: 10,
       
  1206 			menu_id: placeholderId
       
  1207 		} ) );
       
  1208 	};
       
  1209 
       
  1210 	/**
       
  1211 	 * wp.customize.Menus.NewMenuSection
       
  1212 	 *
       
  1213 	 * Customizer section for new menus.
       
  1214 	 *
       
  1215 	 * @constructor
       
  1216 	 * @augments wp.customize.Section
       
  1217 	 */
       
  1218 	api.Menus.NewMenuSection = api.Section.extend({
       
  1219 
       
  1220 		/**
       
  1221 		 * Add behaviors for the accordion section.
       
  1222 		 *
       
  1223 		 * @since 4.3.0
       
  1224 		 */
       
  1225 		attachEvents: function() {
       
  1226 			var section = this,
       
  1227 				container = section.container,
       
  1228 				contentContainer = section.contentContainer,
       
  1229 				navMenuSettingPattern = /^nav_menu\[/;
       
  1230 
       
  1231 			section.headContainer.find( '.accordion-section-title' ).replaceWith(
       
  1232 				wp.template( 'nav-menu-create-menu-section-title' )
       
  1233 			);
       
  1234 
       
  1235 			/*
       
  1236 			 * We have to manually handle section expanded because we do not
       
  1237 			 * apply the `accordion-section-title` class to this button-driven section.
       
  1238 			 */
       
  1239 			container.on( 'click', '.customize-add-menu-button', function() {
       
  1240 				section.expand();
       
  1241 			});
       
  1242 
       
  1243 			contentContainer.on( 'keydown', '.menu-name-field', function( event ) {
       
  1244 				if ( 13 === event.which ) { // Enter.
       
  1245 					section.submit();
       
  1246 				}
       
  1247 			} );
       
  1248 			contentContainer.on( 'click', '#customize-new-menu-submit', function( event ) {
       
  1249 				section.submit();
       
  1250 				event.stopPropagation();
       
  1251 				event.preventDefault();
       
  1252 			} );
       
  1253 
       
  1254 			/**
       
  1255 			 * Get number of non-deleted nav menus.
       
  1256 			 *
       
  1257 			 * @since 4.9.0
       
  1258 			 * @returns {number} Count.
       
  1259 			 */
       
  1260 			function getNavMenuCount() {
       
  1261 				var count = 0;
       
  1262 				api.each( function( setting ) {
       
  1263 					if ( navMenuSettingPattern.test( setting.id ) && false !== setting.get() ) {
       
  1264 						count += 1;
       
  1265 					}
       
  1266 				} );
       
  1267 				return count;
       
  1268 			}
       
  1269 
       
  1270 			/**
       
  1271 			 * Update visibility of notice to prompt users to create menus.
       
  1272 			 *
       
  1273 			 * @since 4.9.0
       
  1274 			 * @returns {void}
       
  1275 			 */
       
  1276 			function updateNoticeVisibility() {
       
  1277 				container.find( '.add-new-menu-notice' ).prop( 'hidden', getNavMenuCount() > 0 );
       
  1278 			}
       
  1279 
       
  1280 			/**
       
  1281 			 * Handle setting addition.
       
  1282 			 *
       
  1283 			 * @since 4.9.0
       
  1284 			 * @param {wp.customize.Setting} setting - Added setting.
       
  1285 			 * @returns {void}
       
  1286 			 */
       
  1287 			function addChangeEventListener( setting ) {
       
  1288 				if ( navMenuSettingPattern.test( setting.id ) ) {
       
  1289 					setting.bind( updateNoticeVisibility );
       
  1290 					updateNoticeVisibility();
       
  1291 				}
       
  1292 			}
       
  1293 
       
  1294 			/**
       
  1295 			 * Handle setting removal.
       
  1296 			 *
       
  1297 			 * @since 4.9.0
       
  1298 			 * @param {wp.customize.Setting} setting - Removed setting.
       
  1299 			 * @returns {void}
       
  1300 			 */
       
  1301 			function removeChangeEventListener( setting ) {
       
  1302 				if ( navMenuSettingPattern.test( setting.id ) ) {
       
  1303 					setting.unbind( updateNoticeVisibility );
       
  1304 					updateNoticeVisibility();
       
  1305 				}
       
  1306 			}
       
  1307 
       
  1308 			api.each( addChangeEventListener );
       
  1309 			api.bind( 'add', addChangeEventListener );
       
  1310 			api.bind( 'removed', removeChangeEventListener );
       
  1311 			updateNoticeVisibility();
       
  1312 
       
  1313 			api.Section.prototype.attachEvents.apply( section, arguments );
       
  1314 		},
       
  1315 
       
  1316 		/**
       
  1317 		 * Set up the control.
       
  1318 		 *
       
  1319 		 * @since 4.9.0
       
  1320 		 */
       
  1321 		ready: function() {
       
  1322 			this.populateControls();
       
  1323 		},
       
  1324 
       
  1325 		/**
       
  1326 		 * Create the controls for this section.
       
  1327 		 *
       
  1328 		 * @since 4.9.0
       
  1329 		 */
       
  1330 		populateControls: function() {
       
  1331 			var section = this,
       
  1332 				menuNameControlId,
       
  1333 				menuLocationsControlId,
       
  1334 				newMenuSubmitControlId,
       
  1335 				menuNameControl,
       
  1336 				menuLocationsControl,
       
  1337 				newMenuSubmitControl;
       
  1338 
       
  1339 			menuNameControlId = section.id + '[name]';
       
  1340 			menuNameControl = api.control( menuNameControlId );
       
  1341 			if ( ! menuNameControl ) {
       
  1342 				menuNameControl = new api.controlConstructor.nav_menu_name( menuNameControlId, {
       
  1343 					label: api.Menus.data.l10n.menuNameLabel,
       
  1344 					description: api.Menus.data.l10n.newMenuNameDescription,
       
  1345 					section: section.id,
       
  1346 					priority: 0
       
  1347 				} );
       
  1348 				api.control.add( menuNameControl.id, menuNameControl );
       
  1349 				menuNameControl.active.set( true );
       
  1350 			}
       
  1351 
       
  1352 			menuLocationsControlId = section.id + '[locations]';
       
  1353 			menuLocationsControl = api.control( menuLocationsControlId );
       
  1354 			if ( ! menuLocationsControl ) {
       
  1355 				menuLocationsControl = new api.controlConstructor.nav_menu_locations( menuLocationsControlId, {
       
  1356 					section: section.id,
       
  1357 					priority: 1,
       
  1358 					menu_id: '',
       
  1359 					isCreating: true
       
  1360 				} );
       
  1361 				api.control.add( menuLocationsControlId, menuLocationsControl );
       
  1362 				menuLocationsControl.active.set( true );
       
  1363 			}
       
  1364 
       
  1365 			newMenuSubmitControlId = section.id + '[submit]';
       
  1366 			newMenuSubmitControl = api.control( newMenuSubmitControlId );
       
  1367 			if ( !newMenuSubmitControl ) {
       
  1368 				newMenuSubmitControl = new api.Control( newMenuSubmitControlId, {
       
  1369 					section: section.id,
       
  1370 					priority: 1,
       
  1371 					templateId: 'nav-menu-submit-new-button'
       
  1372 				} );
       
  1373 				api.control.add( newMenuSubmitControlId, newMenuSubmitControl );
       
  1374 				newMenuSubmitControl.active.set( true );
       
  1375 			}
       
  1376 		},
       
  1377 
       
  1378 		/**
       
  1379 		 * Create the new menu with name and location supplied by the user.
       
  1380 		 *
       
  1381 		 * @since 4.9.0
       
  1382 		 */
       
  1383 		submit: function() {
       
  1384 			var section = this,
       
  1385 				contentContainer = section.contentContainer,
       
  1386 				nameInput = contentContainer.find( '.menu-name-field' ).first(),
       
  1387 				name = nameInput.val(),
       
  1388 				menuSection;
       
  1389 
       
  1390 			if ( ! name ) {
       
  1391 				nameInput.addClass( 'invalid' );
       
  1392 				nameInput.focus();
       
  1393 				return;
       
  1394 			}
       
  1395 
       
  1396 			menuSection = api.Menus.createNavMenu( name );
       
  1397 
       
  1398 			// Clear name field.
       
  1399 			nameInput.val( '' );
       
  1400 			nameInput.removeClass( 'invalid' );
       
  1401 
       
  1402 			contentContainer.find( '.assigned-menu-location input[type=checkbox]' ).each( function() {
       
  1403 				var checkbox = $( this ),
       
  1404 				navMenuLocationSetting;
       
  1405 
       
  1406 				if ( checkbox.prop( 'checked' ) ) {
       
  1407 					navMenuLocationSetting = api( 'nav_menu_locations[' + checkbox.data( 'location-id' ) + ']' );
       
  1408 					navMenuLocationSetting.set( menuSection.params.menu_id );
       
  1409 
       
  1410 					// Reset state for next new menu
       
  1411 					checkbox.prop( 'checked', false );
       
  1412 				}
       
  1413 			} );
       
  1414 
       
  1415 			wp.a11y.speak( api.Menus.data.l10n.menuAdded );
       
  1416 
       
  1417 			// Focus on the new menu section.
       
  1418 			menuSection.focus( {
       
  1419 				completeCallback: function() {
       
  1420 					menuSection.highlightNewItemButton();
       
  1421 				}
       
  1422 			} );
       
  1423 		},
       
  1424 
       
  1425 		/**
       
  1426 		 * Select a default location.
       
  1427 		 *
       
  1428 		 * This method selects a single location by default so we can support
       
  1429 		 * creating a menu for a specific menu location.
       
  1430 		 *
       
  1431 		 * @since 4.9.0
       
  1432 		 *
       
  1433 		 * @param {string|null} locationId - The ID of the location to select. `null` clears all selections.
       
  1434 		 * @returns {void}
       
  1435 		 */
       
  1436 		selectDefaultLocation: function( locationId ) {
       
  1437 			var locationControl = api.control( this.id + '[locations]' ),
       
  1438 				locationSelections = {};
       
  1439 
       
  1440 			if ( locationId !== null ) {
       
  1441 				locationSelections[ locationId ] = true;
       
  1442 			}
       
  1443 
       
  1444 			locationControl.setSelections( locationSelections );
       
  1445 		}
       
  1446 	});
       
  1447 
       
  1448 	/**
       
  1449 	 * wp.customize.Menus.MenuLocationControl
       
  1450 	 *
       
  1451 	 * Customizer control for menu locations (rendered as a <select>).
       
  1452 	 * Note that 'nav_menu_location' must match the WP_Customize_Nav_Menu_Location_Control::$type.
       
  1453 	 *
       
  1454 	 * @constructor
       
  1455 	 * @augments wp.customize.Control
       
  1456 	 */
       
  1457 	api.Menus.MenuLocationControl = api.Control.extend({
       
  1458 		initialize: function( id, options ) {
       
  1459 			var control = this,
       
  1460 				matches = id.match( /^nav_menu_locations\[(.+?)]/ );
       
  1461 			control.themeLocation = matches[1];
       
  1462 			api.Control.prototype.initialize.call( control, id, options );
       
  1463 		},
       
  1464 
       
  1465 		ready: function() {
       
  1466 			var control = this, navMenuIdRegex = /^nav_menu\[(-?\d+)]/;
       
  1467 
       
  1468 			// @todo It would be better if this was added directly on the setting itself, as opposed to the control.
       
  1469 			control.setting.validate = function( value ) {
       
  1470 				if ( '' === value ) {
       
  1471 					return 0;
       
  1472 				} else {
       
  1473 					return parseInt( value, 10 );
       
  1474 				}
       
  1475 			};
       
  1476 
       
  1477 			// Create and Edit menu buttons.
       
  1478 			control.container.find( '.create-menu' ).on( 'click', function() {
       
  1479 				var addMenuSection = api.section( 'add_menu' );
       
  1480 				addMenuSection.selectDefaultLocation( this.dataset.locationId );
       
  1481 				addMenuSection.focus();
       
  1482 			} );
       
  1483 			control.container.find( '.edit-menu' ).on( 'click', function() {
       
  1484 				var menuId = control.setting();
       
  1485 				api.section( 'nav_menu[' + menuId + ']' ).focus();
       
  1486 			});
       
  1487 			control.setting.bind( 'change', function() {
       
  1488 				var menuIsSelected = 0 !== control.setting();
       
  1489 				control.container.find( '.create-menu' ).toggleClass( 'hidden', menuIsSelected );
       
  1490 				control.container.find( '.edit-menu' ).toggleClass( 'hidden', ! menuIsSelected );
       
  1491 			});
       
  1492 
       
  1493 			// Add/remove menus from the available options when they are added and removed.
       
  1494 			api.bind( 'add', function( setting ) {
       
  1495 				var option, menuId, matches = setting.id.match( navMenuIdRegex );
       
  1496 				if ( ! matches || false === setting() ) {
       
  1497 					return;
       
  1498 				}
       
  1499 				menuId = matches[1];
       
  1500 				option = new Option( displayNavMenuName( setting().name ), menuId );
       
  1501 				control.container.find( 'select' ).append( option );
       
  1502 			});
       
  1503 			api.bind( 'remove', function( setting ) {
       
  1504 				var menuId, matches = setting.id.match( navMenuIdRegex );
       
  1505 				if ( ! matches ) {
       
  1506 					return;
       
  1507 				}
       
  1508 				menuId = parseInt( matches[1], 10 );
       
  1509 				if ( control.setting() === menuId ) {
       
  1510 					control.setting.set( '' );
       
  1511 				}
       
  1512 				control.container.find( 'option[value=' + menuId + ']' ).remove();
       
  1513 			});
       
  1514 			api.bind( 'change', function( setting ) {
       
  1515 				var menuId, matches = setting.id.match( navMenuIdRegex );
       
  1516 				if ( ! matches ) {
       
  1517 					return;
       
  1518 				}
       
  1519 				menuId = parseInt( matches[1], 10 );
       
  1520 				if ( false === setting() ) {
       
  1521 					if ( control.setting() === menuId ) {
       
  1522 						control.setting.set( '' );
       
  1523 					}
       
  1524 					control.container.find( 'option[value=' + menuId + ']' ).remove();
       
  1525 				} else {
       
  1526 					control.container.find( 'option[value=' + menuId + ']' ).text( displayNavMenuName( setting().name ) );
       
  1527 				}
       
  1528 			});
       
  1529 		}
       
  1530 	});
       
  1531 
       
  1532 	/**
       
  1533 	 * wp.customize.Menus.MenuItemControl
       
  1534 	 *
       
  1535 	 * Customizer control for menu items.
       
  1536 	 * Note that 'menu_item' must match the WP_Customize_Menu_Item_Control::$type.
       
  1537 	 *
       
  1538 	 * @constructor
       
  1539 	 * @augments wp.customize.Control
       
  1540 	 */
       
  1541 	api.Menus.MenuItemControl = api.Control.extend({
       
  1542 
       
  1543 		/**
       
  1544 		 * @inheritdoc
       
  1545 		 */
       
  1546 		initialize: function( id, options ) {
       
  1547 			var control = this;
       
  1548 			control.expanded = new api.Value( false );
       
  1549 			control.expandedArgumentsQueue = [];
       
  1550 			control.expanded.bind( function( expanded ) {
       
  1551 				var args = control.expandedArgumentsQueue.shift();
       
  1552 				args = $.extend( {}, control.defaultExpandedArguments, args );
       
  1553 				control.onChangeExpanded( expanded, args );
       
  1554 			});
       
  1555 			api.Control.prototype.initialize.call( control, id, options );
       
  1556 			control.active.validate = function() {
       
  1557 				var value, section = api.section( control.section() );
       
  1558 				if ( section ) {
       
  1559 					value = section.active();
       
  1560 				} else {
       
  1561 					value = false;
       
  1562 				}
       
  1563 				return value;
       
  1564 			};
       
  1565 		},
       
  1566 
       
  1567 		/**
       
  1568 		 * Override the embed() method to do nothing,
       
  1569 		 * so that the control isn't embedded on load,
       
  1570 		 * unless the containing section is already expanded.
       
  1571 		 *
       
  1572 		 * @since 4.3.0
       
  1573 		 */
       
  1574 		embed: function() {
       
  1575 			var control = this,
       
  1576 				sectionId = control.section(),
       
  1577 				section;
       
  1578 			if ( ! sectionId ) {
       
  1579 				return;
       
  1580 			}
       
  1581 			section = api.section( sectionId );
       
  1582 			if ( ( section && section.expanded() ) || api.settings.autofocus.control === control.id ) {
       
  1583 				control.actuallyEmbed();
       
  1584 			}
       
  1585 		},
       
  1586 
       
  1587 		/**
       
  1588 		 * This function is called in Section.onChangeExpanded() so the control
       
  1589 		 * will only get embedded when the Section is first expanded.
       
  1590 		 *
       
  1591 		 * @since 4.3.0
       
  1592 		 */
       
  1593 		actuallyEmbed: function() {
       
  1594 			var control = this;
       
  1595 			if ( 'resolved' === control.deferred.embedded.state() ) {
       
  1596 				return;
       
  1597 			}
       
  1598 			control.renderContent();
       
  1599 			control.deferred.embedded.resolve(); // This triggers control.ready().
       
  1600 		},
       
  1601 
       
  1602 		/**
       
  1603 		 * Set up the control.
       
  1604 		 */
       
  1605 		ready: function() {
       
  1606 			if ( 'undefined' === typeof this.params.menu_item_id ) {
       
  1607 				throw new Error( 'params.menu_item_id was not defined' );
       
  1608 			}
       
  1609 
       
  1610 			this._setupControlToggle();
       
  1611 			this._setupReorderUI();
       
  1612 			this._setupUpdateUI();
       
  1613 			this._setupRemoveUI();
       
  1614 			this._setupLinksUI();
       
  1615 			this._setupTitleUI();
       
  1616 		},
       
  1617 
       
  1618 		/**
       
  1619 		 * Show/hide the settings when clicking on the menu item handle.
       
  1620 		 */
       
  1621 		_setupControlToggle: function() {
       
  1622 			var control = this;
       
  1623 
       
  1624 			this.container.find( '.menu-item-handle' ).on( 'click', function( e ) {
       
  1625 				e.preventDefault();
       
  1626 				e.stopPropagation();
       
  1627 				var menuControl = control.getMenuControl(),
       
  1628 					isDeleteBtn = $( e.target ).is( '.item-delete, .item-delete *' ),
       
  1629 					isAddNewBtn = $( e.target ).is( '.add-new-menu-item, .add-new-menu-item *' );
       
  1630 
       
  1631 				if ( $( 'body' ).hasClass( 'adding-menu-items' ) && ! isDeleteBtn && ! isAddNewBtn ) {
       
  1632 					api.Menus.availableMenuItemsPanel.close();
       
  1633 				}
       
  1634 
       
  1635 				if ( menuControl.isReordering || menuControl.isSorting ) {
       
  1636 					return;
       
  1637 				}
       
  1638 				control.toggleForm();
       
  1639 			} );
       
  1640 		},
       
  1641 
       
  1642 		/**
       
  1643 		 * Set up the menu-item-reorder-nav
       
  1644 		 */
       
  1645 		_setupReorderUI: function() {
       
  1646 			var control = this, template, $reorderNav;
       
  1647 
       
  1648 			template = wp.template( 'menu-item-reorder-nav' );
       
  1649 
       
  1650 			// Add the menu item reordering elements to the menu item control.
       
  1651 			control.container.find( '.item-controls' ).after( template );
       
  1652 
       
  1653 			// Handle clicks for up/down/left-right on the reorder nav.
       
  1654 			$reorderNav = control.container.find( '.menu-item-reorder-nav' );
       
  1655 			$reorderNav.find( '.menus-move-up, .menus-move-down, .menus-move-left, .menus-move-right' ).on( 'click', function() {
       
  1656 				var moveBtn = $( this );
       
  1657 				moveBtn.focus();
       
  1658 
       
  1659 				var isMoveUp = moveBtn.is( '.menus-move-up' ),
       
  1660 					isMoveDown = moveBtn.is( '.menus-move-down' ),
       
  1661 					isMoveLeft = moveBtn.is( '.menus-move-left' ),
       
  1662 					isMoveRight = moveBtn.is( '.menus-move-right' );
       
  1663 
       
  1664 				if ( isMoveUp ) {
       
  1665 					control.moveUp();
       
  1666 				} else if ( isMoveDown ) {
       
  1667 					control.moveDown();
       
  1668 				} else if ( isMoveLeft ) {
       
  1669 					control.moveLeft();
       
  1670 				} else if ( isMoveRight ) {
       
  1671 					control.moveRight();
       
  1672 				}
       
  1673 
       
  1674 				moveBtn.focus(); // Re-focus after the container was moved.
       
  1675 			} );
       
  1676 		},
       
  1677 
       
  1678 		/**
       
  1679 		 * Set up event handlers for menu item updating.
       
  1680 		 */
       
  1681 		_setupUpdateUI: function() {
       
  1682 			var control = this,
       
  1683 				settingValue = control.setting(),
       
  1684 				updateNotifications;
       
  1685 
       
  1686 			control.elements = {};
       
  1687 			control.elements.url = new api.Element( control.container.find( '.edit-menu-item-url' ) );
       
  1688 			control.elements.title = new api.Element( control.container.find( '.edit-menu-item-title' ) );
       
  1689 			control.elements.attr_title = new api.Element( control.container.find( '.edit-menu-item-attr-title' ) );
       
  1690 			control.elements.target = new api.Element( control.container.find( '.edit-menu-item-target' ) );
       
  1691 			control.elements.classes = new api.Element( control.container.find( '.edit-menu-item-classes' ) );
       
  1692 			control.elements.xfn = new api.Element( control.container.find( '.edit-menu-item-xfn' ) );
       
  1693 			control.elements.description = new api.Element( control.container.find( '.edit-menu-item-description' ) );
       
  1694 			// @todo allow other elements, added by plugins, to be automatically picked up here; allow additional values to be added to setting array.
       
  1695 
       
  1696 			_.each( control.elements, function( element, property ) {
       
  1697 				element.bind(function( value ) {
       
  1698 					if ( element.element.is( 'input[type=checkbox]' ) ) {
       
  1699 						value = ( value ) ? element.element.val() : '';
       
  1700 					}
       
  1701 
       
  1702 					var settingValue = control.setting();
       
  1703 					if ( settingValue && settingValue[ property ] !== value ) {
       
  1704 						settingValue = _.clone( settingValue );
       
  1705 						settingValue[ property ] = value;
       
  1706 						control.setting.set( settingValue );
       
  1707 					}
       
  1708 				});
       
  1709 				if ( settingValue ) {
       
  1710 					if ( ( property === 'classes' || property === 'xfn' ) && _.isArray( settingValue[ property ] ) ) {
       
  1711 						element.set( settingValue[ property ].join( ' ' ) );
       
  1712 					} else {
       
  1713 						element.set( settingValue[ property ] );
       
  1714 					}
       
  1715 				}
       
  1716 			});
       
  1717 
       
  1718 			control.setting.bind(function( to, from ) {
       
  1719 				var itemId = control.params.menu_item_id,
       
  1720 					followingSiblingItemControls = [],
       
  1721 					childrenItemControls = [],
       
  1722 					menuControl;
       
  1723 
       
  1724 				if ( false === to ) {
       
  1725 					menuControl = api.control( 'nav_menu[' + String( from.nav_menu_term_id ) + ']' );
       
  1726 					control.container.remove();
       
  1727 
       
  1728 					_.each( menuControl.getMenuItemControls(), function( otherControl ) {
       
  1729 						if ( from.menu_item_parent === otherControl.setting().menu_item_parent && otherControl.setting().position > from.position ) {
       
  1730 							followingSiblingItemControls.push( otherControl );
       
  1731 						} else if ( otherControl.setting().menu_item_parent === itemId ) {
       
  1732 							childrenItemControls.push( otherControl );
       
  1733 						}
       
  1734 					});
       
  1735 
       
  1736 					// Shift all following siblings by the number of children this item has.
       
  1737 					_.each( followingSiblingItemControls, function( followingSiblingItemControl ) {
       
  1738 						var value = _.clone( followingSiblingItemControl.setting() );
       
  1739 						value.position += childrenItemControls.length;
       
  1740 						followingSiblingItemControl.setting.set( value );
       
  1741 					});
       
  1742 
       
  1743 					// Now move the children up to be the new subsequent siblings.
       
  1744 					_.each( childrenItemControls, function( childrenItemControl, i ) {
       
  1745 						var value = _.clone( childrenItemControl.setting() );
       
  1746 						value.position = from.position + i;
       
  1747 						value.menu_item_parent = from.menu_item_parent;
       
  1748 						childrenItemControl.setting.set( value );
       
  1749 					});
       
  1750 
       
  1751 					menuControl.debouncedReflowMenuItems();
       
  1752 				} else {
       
  1753 					// Update the elements' values to match the new setting properties.
       
  1754 					_.each( to, function( value, key ) {
       
  1755 						if ( control.elements[ key] ) {
       
  1756 							control.elements[ key ].set( to[ key ] );
       
  1757 						}
       
  1758 					} );
       
  1759 					control.container.find( '.menu-item-data-parent-id' ).val( to.menu_item_parent );
       
  1760 
       
  1761 					// Handle UI updates when the position or depth (parent) change.
       
  1762 					if ( to.position !== from.position || to.menu_item_parent !== from.menu_item_parent ) {
       
  1763 						control.getMenuControl().debouncedReflowMenuItems();
       
  1764 					}
       
  1765 				}
       
  1766 			});
       
  1767 
       
  1768 			// Style the URL field as invalid when there is an invalid_url notification.
       
  1769 			updateNotifications = function() {
       
  1770 				control.elements.url.element.toggleClass( 'invalid', control.setting.notifications.has( 'invalid_url' ) );
       
  1771 			};
       
  1772 			control.setting.notifications.bind( 'add', updateNotifications );
       
  1773 			control.setting.notifications.bind( 'removed', updateNotifications );
       
  1774 		},
       
  1775 
       
  1776 		/**
       
  1777 		 * Set up event handlers for menu item deletion.
       
  1778 		 */
       
  1779 		_setupRemoveUI: function() {
       
  1780 			var control = this, $removeBtn;
       
  1781 
       
  1782 			// Configure delete button.
       
  1783 			$removeBtn = control.container.find( '.item-delete' );
       
  1784 
       
  1785 			$removeBtn.on( 'click', function() {
       
  1786 				// Find an adjacent element to add focus to when this menu item goes away
       
  1787 				var addingItems = true, $adjacentFocusTarget, $next, $prev;
       
  1788 
       
  1789 				if ( ! $( 'body' ).hasClass( 'adding-menu-items' ) ) {
       
  1790 					addingItems = false;
       
  1791 				}
       
  1792 
       
  1793 				$next = control.container.nextAll( '.customize-control-nav_menu_item:visible' ).first();
       
  1794 				$prev = control.container.prevAll( '.customize-control-nav_menu_item:visible' ).first();
       
  1795 
       
  1796 				if ( $next.length ) {
       
  1797 					$adjacentFocusTarget = $next.find( false === addingItems ? '.item-edit' : '.item-delete' ).first();
       
  1798 				} else if ( $prev.length ) {
       
  1799 					$adjacentFocusTarget = $prev.find( false === addingItems ? '.item-edit' : '.item-delete' ).first();
       
  1800 				} else {
       
  1801 					$adjacentFocusTarget = control.container.nextAll( '.customize-control-nav_menu' ).find( '.add-new-menu-item' ).first();
       
  1802 				}
       
  1803 
       
  1804 				control.container.slideUp( function() {
       
  1805 					control.setting.set( false );
       
  1806 					wp.a11y.speak( api.Menus.data.l10n.itemDeleted );
       
  1807 					$adjacentFocusTarget.focus(); // keyboard accessibility
       
  1808 				} );
       
  1809 
       
  1810 				control.setting.set( false );
       
  1811 			} );
       
  1812 		},
       
  1813 
       
  1814 		_setupLinksUI: function() {
       
  1815 			var $origBtn;
       
  1816 
       
  1817 			// Configure original link.
       
  1818 			$origBtn = this.container.find( 'a.original-link' );
       
  1819 
       
  1820 			$origBtn.on( 'click', function( e ) {
       
  1821 				e.preventDefault();
       
  1822 				api.previewer.previewUrl( e.target.toString() );
       
  1823 			} );
       
  1824 		},
       
  1825 
       
  1826 		/**
       
  1827 		 * Update item handle title when changed.
       
  1828 		 */
       
  1829 		_setupTitleUI: function() {
       
  1830 			var control = this, titleEl;
       
  1831 
       
  1832 			// Ensure that whitespace is trimmed on blur so placeholder can be shown.
       
  1833 			control.container.find( '.edit-menu-item-title' ).on( 'blur', function() {
       
  1834 				$( this ).val( $.trim( $( this ).val() ) );
       
  1835 			} );
       
  1836 
       
  1837 			titleEl = control.container.find( '.menu-item-title' );
       
  1838 			control.setting.bind( function( item ) {
       
  1839 				var trimmedTitle, titleText;
       
  1840 				if ( ! item ) {
       
  1841 					return;
       
  1842 				}
       
  1843 				trimmedTitle = $.trim( item.title );
       
  1844 
       
  1845 				titleText = trimmedTitle || item.original_title || api.Menus.data.l10n.untitled;
       
  1846 
       
  1847 				if ( item._invalid ) {
       
  1848 					titleText = api.Menus.data.l10n.invalidTitleTpl.replace( '%s', titleText );
       
  1849 				}
       
  1850 
       
  1851 				// Don't update to an empty title.
       
  1852 				if ( trimmedTitle || item.original_title ) {
       
  1853 					titleEl
       
  1854 						.text( titleText )
       
  1855 						.removeClass( 'no-title' );
       
  1856 				} else {
       
  1857 					titleEl
       
  1858 						.text( titleText )
       
  1859 						.addClass( 'no-title' );
       
  1860 				}
       
  1861 			} );
       
  1862 		},
       
  1863 
       
  1864 		/**
       
  1865 		 *
       
  1866 		 * @returns {number}
       
  1867 		 */
       
  1868 		getDepth: function() {
       
  1869 			var control = this, setting = control.setting(), depth = 0;
       
  1870 			if ( ! setting ) {
       
  1871 				return 0;
       
  1872 			}
       
  1873 			while ( setting && setting.menu_item_parent ) {
       
  1874 				depth += 1;
       
  1875 				control = api.control( 'nav_menu_item[' + setting.menu_item_parent + ']' );
       
  1876 				if ( ! control ) {
       
  1877 					break;
       
  1878 				}
       
  1879 				setting = control.setting();
       
  1880 			}
       
  1881 			return depth;
       
  1882 		},
       
  1883 
       
  1884 		/**
       
  1885 		 * Amend the control's params with the data necessary for the JS template just in time.
       
  1886 		 */
       
  1887 		renderContent: function() {
       
  1888 			var control = this,
       
  1889 				settingValue = control.setting(),
       
  1890 				containerClasses;
       
  1891 
       
  1892 			control.params.title = settingValue.title || '';
       
  1893 			control.params.depth = control.getDepth();
       
  1894 			control.container.data( 'item-depth', control.params.depth );
       
  1895 			containerClasses = [
       
  1896 				'menu-item',
       
  1897 				'menu-item-depth-' + String( control.params.depth ),
       
  1898 				'menu-item-' + settingValue.object,
       
  1899 				'menu-item-edit-inactive'
       
  1900 			];
       
  1901 
       
  1902 			if ( settingValue._invalid ) {
       
  1903 				containerClasses.push( 'menu-item-invalid' );
       
  1904 				control.params.title = api.Menus.data.l10n.invalidTitleTpl.replace( '%s', control.params.title );
       
  1905 			} else if ( 'draft' === settingValue.status ) {
       
  1906 				containerClasses.push( 'pending' );
       
  1907 				control.params.title = api.Menus.data.pendingTitleTpl.replace( '%s', control.params.title );
       
  1908 			}
       
  1909 
       
  1910 			control.params.el_classes = containerClasses.join( ' ' );
       
  1911 			control.params.item_type_label = settingValue.type_label;
       
  1912 			control.params.item_type = settingValue.type;
       
  1913 			control.params.url = settingValue.url;
       
  1914 			control.params.target = settingValue.target;
       
  1915 			control.params.attr_title = settingValue.attr_title;
       
  1916 			control.params.classes = _.isArray( settingValue.classes ) ? settingValue.classes.join( ' ' ) : settingValue.classes;
       
  1917 			control.params.attr_title = settingValue.attr_title;
       
  1918 			control.params.xfn = settingValue.xfn;
       
  1919 			control.params.description = settingValue.description;
       
  1920 			control.params.parent = settingValue.menu_item_parent;
       
  1921 			control.params.original_title = settingValue.original_title || '';
       
  1922 
       
  1923 			control.container.addClass( control.params.el_classes );
       
  1924 
       
  1925 			api.Control.prototype.renderContent.call( control );
       
  1926 		},
       
  1927 
       
  1928 		/***********************************************************************
       
  1929 		 * Begin public API methods
       
  1930 		 **********************************************************************/
       
  1931 
       
  1932 		/**
       
  1933 		 * @return {wp.customize.controlConstructor.nav_menu|null}
       
  1934 		 */
       
  1935 		getMenuControl: function() {
       
  1936 			var control = this, settingValue = control.setting();
       
  1937 			if ( settingValue && settingValue.nav_menu_term_id ) {
       
  1938 				return api.control( 'nav_menu[' + settingValue.nav_menu_term_id + ']' );
       
  1939 			} else {
       
  1940 				return null;
       
  1941 			}
       
  1942 		},
       
  1943 
       
  1944 		/**
       
  1945 		 * Expand the accordion section containing a control
       
  1946 		 */
       
  1947 		expandControlSection: function() {
       
  1948 			var $section = this.container.closest( '.accordion-section' );
       
  1949 			if ( ! $section.hasClass( 'open' ) ) {
       
  1950 				$section.find( '.accordion-section-title:first' ).trigger( 'click' );
       
  1951 			}
       
  1952 		},
       
  1953 
       
  1954 		/**
       
  1955 		 * @since 4.6.0
       
  1956 		 *
       
  1957 		 * @param {Boolean} expanded
       
  1958 		 * @param {Object} [params]
       
  1959 		 * @returns {Boolean} false if state already applied
       
  1960 		 */
       
  1961 		_toggleExpanded: api.Section.prototype._toggleExpanded,
       
  1962 
       
  1963 		/**
       
  1964 		 * @since 4.6.0
       
  1965 		 *
       
  1966 		 * @param {Object} [params]
       
  1967 		 * @returns {Boolean} false if already expanded
       
  1968 		 */
       
  1969 		expand: api.Section.prototype.expand,
       
  1970 
       
  1971 		/**
       
  1972 		 * Expand the menu item form control.
       
  1973 		 *
       
  1974 		 * @since 4.5.0 Added params.completeCallback.
       
  1975 		 *
       
  1976 		 * @param {Object}   [params] - Optional params.
       
  1977 		 * @param {Function} [params.completeCallback] - Function to call when the form toggle has finished animating.
       
  1978 		 */
       
  1979 		expandForm: function( params ) {
       
  1980 			this.expand( params );
       
  1981 		},
       
  1982 
       
  1983 		/**
       
  1984 		 * @since 4.6.0
       
  1985 		 *
       
  1986 		 * @param {Object} [params]
       
  1987 		 * @returns {Boolean} false if already collapsed
       
  1988 		 */
       
  1989 		collapse: api.Section.prototype.collapse,
       
  1990 
       
  1991 		/**
       
  1992 		 * Collapse the menu item form control.
       
  1993 		 *
       
  1994 		 * @since 4.5.0 Added params.completeCallback.
       
  1995 		 *
       
  1996 		 * @param {Object}   [params] - Optional params.
       
  1997 		 * @param {Function} [params.completeCallback] - Function to call when the form toggle has finished animating.
       
  1998 		 */
       
  1999 		collapseForm: function( params ) {
       
  2000 			this.collapse( params );
       
  2001 		},
       
  2002 
       
  2003 		/**
       
  2004 		 * Expand or collapse the menu item control.
       
  2005 		 *
       
  2006 		 * @deprecated this is poor naming, and it is better to directly set control.expanded( showOrHide )
       
  2007 		 * @since 4.5.0 Added params.completeCallback.
       
  2008 		 *
       
  2009 		 * @param {boolean}  [showOrHide] - If not supplied, will be inverse of current visibility
       
  2010 		 * @param {Object}   [params] - Optional params.
       
  2011 		 * @param {Function} [params.completeCallback] - Function to call when the form toggle has finished animating.
       
  2012 		 */
       
  2013 		toggleForm: function( showOrHide, params ) {
       
  2014 			if ( typeof showOrHide === 'undefined' ) {
       
  2015 				showOrHide = ! this.expanded();
       
  2016 			}
       
  2017 			if ( showOrHide ) {
       
  2018 				this.expand( params );
       
  2019 			} else {
       
  2020 				this.collapse( params );
       
  2021 			}
       
  2022 		},
       
  2023 
       
  2024 		/**
       
  2025 		 * Expand or collapse the menu item control.
       
  2026 		 *
       
  2027 		 * @since 4.6.0
       
  2028 		 * @param {boolean}  [showOrHide] - If not supplied, will be inverse of current visibility
       
  2029 		 * @param {Object}   [params] - Optional params.
       
  2030 		 * @param {Function} [params.completeCallback] - Function to call when the form toggle has finished animating.
       
  2031 		 */
       
  2032 		onChangeExpanded: function( showOrHide, params ) {
       
  2033 			var self = this, $menuitem, $inside, complete;
       
  2034 
       
  2035 			$menuitem = this.container;
       
  2036 			$inside = $menuitem.find( '.menu-item-settings:first' );
       
  2037 			if ( 'undefined' === typeof showOrHide ) {
       
  2038 				showOrHide = ! $inside.is( ':visible' );
       
  2039 			}
       
  2040 
       
  2041 			// Already expanded or collapsed.
       
  2042 			if ( $inside.is( ':visible' ) === showOrHide ) {
       
  2043 				if ( params && params.completeCallback ) {
       
  2044 					params.completeCallback();
       
  2045 				}
       
  2046 				return;
       
  2047 			}
       
  2048 
       
  2049 			if ( showOrHide ) {
       
  2050 				// Close all other menu item controls before expanding this one.
       
  2051 				api.control.each( function( otherControl ) {
       
  2052 					if ( self.params.type === otherControl.params.type && self !== otherControl ) {
       
  2053 						otherControl.collapseForm();
       
  2054 					}
       
  2055 				} );
       
  2056 
       
  2057 				complete = function() {
       
  2058 					$menuitem
       
  2059 						.removeClass( 'menu-item-edit-inactive' )
       
  2060 						.addClass( 'menu-item-edit-active' );
       
  2061 					self.container.trigger( 'expanded' );
       
  2062 
       
  2063 					if ( params && params.completeCallback ) {
       
  2064 						params.completeCallback();
       
  2065 					}
       
  2066 				};
       
  2067 
       
  2068 				$menuitem.find( '.item-edit' ).attr( 'aria-expanded', 'true' );
       
  2069 				$inside.slideDown( 'fast', complete );
       
  2070 
       
  2071 				self.container.trigger( 'expand' );
       
  2072 			} else {
       
  2073 				complete = function() {
       
  2074 					$menuitem
       
  2075 						.addClass( 'menu-item-edit-inactive' )
       
  2076 						.removeClass( 'menu-item-edit-active' );
       
  2077 					self.container.trigger( 'collapsed' );
       
  2078 
       
  2079 					if ( params && params.completeCallback ) {
       
  2080 						params.completeCallback();
       
  2081 					}
       
  2082 				};
       
  2083 
       
  2084 				self.container.trigger( 'collapse' );
       
  2085 
       
  2086 				$menuitem.find( '.item-edit' ).attr( 'aria-expanded', 'false' );
       
  2087 				$inside.slideUp( 'fast', complete );
       
  2088 			}
       
  2089 		},
       
  2090 
       
  2091 		/**
       
  2092 		 * Expand the containing menu section, expand the form, and focus on
       
  2093 		 * the first input in the control.
       
  2094 		 *
       
  2095 		 * @since 4.5.0 Added params.completeCallback.
       
  2096 		 *
       
  2097 		 * @param {Object}   [params] - Params object.
       
  2098 		 * @param {Function} [params.completeCallback] - Optional callback function when focus has completed.
       
  2099 		 */
       
  2100 		focus: function( params ) {
       
  2101 			params = params || {};
       
  2102 			var control = this, originalCompleteCallback = params.completeCallback, focusControl;
       
  2103 
       
  2104 			focusControl = function() {
       
  2105 				control.expandControlSection();
       
  2106 
       
  2107 				params.completeCallback = function() {
       
  2108 					var focusable;
       
  2109 
       
  2110 					// Note that we can't use :focusable due to a jQuery UI issue. See: https://github.com/jquery/jquery-ui/pull/1583
       
  2111 					focusable = control.container.find( '.menu-item-settings' ).find( 'input, select, textarea, button, object, a[href], [tabindex]' ).filter( ':visible' );
       
  2112 					focusable.first().focus();
       
  2113 
       
  2114 					if ( originalCompleteCallback ) {
       
  2115 						originalCompleteCallback();
       
  2116 					}
       
  2117 				};
       
  2118 
       
  2119 				control.expandForm( params );
       
  2120 			};
       
  2121 
       
  2122 			if ( api.section.has( control.section() ) ) {
       
  2123 				api.section( control.section() ).expand( {
       
  2124 					completeCallback: focusControl
       
  2125 				} );
       
  2126 			} else {
       
  2127 				focusControl();
       
  2128 			}
       
  2129 		},
       
  2130 
       
  2131 		/**
       
  2132 		 * Move menu item up one in the menu.
       
  2133 		 */
       
  2134 		moveUp: function() {
       
  2135 			this._changePosition( -1 );
       
  2136 			wp.a11y.speak( api.Menus.data.l10n.movedUp );
       
  2137 		},
       
  2138 
       
  2139 		/**
       
  2140 		 * Move menu item up one in the menu.
       
  2141 		 */
       
  2142 		moveDown: function() {
       
  2143 			this._changePosition( 1 );
       
  2144 			wp.a11y.speak( api.Menus.data.l10n.movedDown );
       
  2145 		},
       
  2146 		/**
       
  2147 		 * Move menu item and all children up one level of depth.
       
  2148 		 */
       
  2149 		moveLeft: function() {
       
  2150 			this._changeDepth( -1 );
       
  2151 			wp.a11y.speak( api.Menus.data.l10n.movedLeft );
       
  2152 		},
       
  2153 
       
  2154 		/**
       
  2155 		 * Move menu item and children one level deeper, as a submenu of the previous item.
       
  2156 		 */
       
  2157 		moveRight: function() {
       
  2158 			this._changeDepth( 1 );
       
  2159 			wp.a11y.speak( api.Menus.data.l10n.movedRight );
       
  2160 		},
       
  2161 
       
  2162 		/**
       
  2163 		 * Note that this will trigger a UI update, causing child items to
       
  2164 		 * move as well and cardinal order class names to be updated.
       
  2165 		 *
       
  2166 		 * @private
       
  2167 		 *
       
  2168 		 * @param {Number} offset 1|-1
       
  2169 		 */
       
  2170 		_changePosition: function( offset ) {
       
  2171 			var control = this,
       
  2172 				adjacentSetting,
       
  2173 				settingValue = _.clone( control.setting() ),
       
  2174 				siblingSettings = [],
       
  2175 				realPosition;
       
  2176 
       
  2177 			if ( 1 !== offset && -1 !== offset ) {
       
  2178 				throw new Error( 'Offset changes by 1 are only supported.' );
       
  2179 			}
       
  2180 
       
  2181 			// Skip moving deleted items.
       
  2182 			if ( ! control.setting() ) {
       
  2183 				return;
       
  2184 			}
       
  2185 
       
  2186 			// Locate the other items under the same parent (siblings).
       
  2187 			_( control.getMenuControl().getMenuItemControls() ).each(function( otherControl ) {
       
  2188 				if ( otherControl.setting().menu_item_parent === settingValue.menu_item_parent ) {
       
  2189 					siblingSettings.push( otherControl.setting );
       
  2190 				}
       
  2191 			});
       
  2192 			siblingSettings.sort(function( a, b ) {
       
  2193 				return a().position - b().position;
       
  2194 			});
       
  2195 
       
  2196 			realPosition = _.indexOf( siblingSettings, control.setting );
       
  2197 			if ( -1 === realPosition ) {
       
  2198 				throw new Error( 'Expected setting to be among siblings.' );
       
  2199 			}
       
  2200 
       
  2201 			// Skip doing anything if the item is already at the edge in the desired direction.
       
  2202 			if ( ( realPosition === 0 && offset < 0 ) || ( realPosition === siblingSettings.length - 1 && offset > 0 ) ) {
       
  2203 				// @todo Should we allow a menu item to be moved up to break it out of a parent? Adopt with previous or following parent?
       
  2204 				return;
       
  2205 			}
       
  2206 
       
  2207 			// Update any adjacent menu item setting to take on this item's position.
       
  2208 			adjacentSetting = siblingSettings[ realPosition + offset ];
       
  2209 			if ( adjacentSetting ) {
       
  2210 				adjacentSetting.set( $.extend(
       
  2211 					_.clone( adjacentSetting() ),
       
  2212 					{
       
  2213 						position: settingValue.position
       
  2214 					}
       
  2215 				) );
       
  2216 			}
       
  2217 
       
  2218 			settingValue.position += offset;
       
  2219 			control.setting.set( settingValue );
       
  2220 		},
       
  2221 
       
  2222 		/**
       
  2223 		 * Note that this will trigger a UI update, causing child items to
       
  2224 		 * move as well and cardinal order class names to be updated.
       
  2225 		 *
       
  2226 		 * @private
       
  2227 		 *
       
  2228 		 * @param {Number} offset 1|-1
       
  2229 		 */
       
  2230 		_changeDepth: function( offset ) {
       
  2231 			if ( 1 !== offset && -1 !== offset ) {
       
  2232 				throw new Error( 'Offset changes by 1 are only supported.' );
       
  2233 			}
       
  2234 			var control = this,
       
  2235 				settingValue = _.clone( control.setting() ),
       
  2236 				siblingControls = [],
       
  2237 				realPosition,
       
  2238 				siblingControl,
       
  2239 				parentControl;
       
  2240 
       
  2241 			// Locate the other items under the same parent (siblings).
       
  2242 			_( control.getMenuControl().getMenuItemControls() ).each(function( otherControl ) {
       
  2243 				if ( otherControl.setting().menu_item_parent === settingValue.menu_item_parent ) {
       
  2244 					siblingControls.push( otherControl );
       
  2245 				}
       
  2246 			});
       
  2247 			siblingControls.sort(function( a, b ) {
       
  2248 				return a.setting().position - b.setting().position;
       
  2249 			});
       
  2250 
       
  2251 			realPosition = _.indexOf( siblingControls, control );
       
  2252 			if ( -1 === realPosition ) {
       
  2253 				throw new Error( 'Expected control to be among siblings.' );
       
  2254 			}
       
  2255 
       
  2256 			if ( -1 === offset ) {
       
  2257 				// Skip moving left an item that is already at the top level.
       
  2258 				if ( ! settingValue.menu_item_parent ) {
       
  2259 					return;
       
  2260 				}
       
  2261 
       
  2262 				parentControl = api.control( 'nav_menu_item[' + settingValue.menu_item_parent + ']' );
       
  2263 
       
  2264 				// Make this control the parent of all the following siblings.
       
  2265 				_( siblingControls ).chain().slice( realPosition ).each(function( siblingControl, i ) {
       
  2266 					siblingControl.setting.set(
       
  2267 						$.extend(
       
  2268 							{},
       
  2269 							siblingControl.setting(),
       
  2270 							{
       
  2271 								menu_item_parent: control.params.menu_item_id,
       
  2272 								position: i
       
  2273 							}
       
  2274 						)
       
  2275 					);
       
  2276 				});
       
  2277 
       
  2278 				// Increase the positions of the parent item's subsequent children to make room for this one.
       
  2279 				_( control.getMenuControl().getMenuItemControls() ).each(function( otherControl ) {
       
  2280 					var otherControlSettingValue, isControlToBeShifted;
       
  2281 					isControlToBeShifted = (
       
  2282 						otherControl.setting().menu_item_parent === parentControl.setting().menu_item_parent &&
       
  2283 						otherControl.setting().position > parentControl.setting().position
       
  2284 					);
       
  2285 					if ( isControlToBeShifted ) {
       
  2286 						otherControlSettingValue = _.clone( otherControl.setting() );
       
  2287 						otherControl.setting.set(
       
  2288 							$.extend(
       
  2289 								otherControlSettingValue,
       
  2290 								{ position: otherControlSettingValue.position + 1 }
       
  2291 							)
       
  2292 						);
       
  2293 					}
       
  2294 				});
       
  2295 
       
  2296 				// Make this control the following sibling of its parent item.
       
  2297 				settingValue.position = parentControl.setting().position + 1;
       
  2298 				settingValue.menu_item_parent = parentControl.setting().menu_item_parent;
       
  2299 				control.setting.set( settingValue );
       
  2300 
       
  2301 			} else if ( 1 === offset ) {
       
  2302 				// Skip moving right an item that doesn't have a previous sibling.
       
  2303 				if ( realPosition === 0 ) {
       
  2304 					return;
       
  2305 				}
       
  2306 
       
  2307 				// Make the control the last child of the previous sibling.
       
  2308 				siblingControl = siblingControls[ realPosition - 1 ];
       
  2309 				settingValue.menu_item_parent = siblingControl.params.menu_item_id;
       
  2310 				settingValue.position = 0;
       
  2311 				_( control.getMenuControl().getMenuItemControls() ).each(function( otherControl ) {
       
  2312 					if ( otherControl.setting().menu_item_parent === settingValue.menu_item_parent ) {
       
  2313 						settingValue.position = Math.max( settingValue.position, otherControl.setting().position );
       
  2314 					}
       
  2315 				});
       
  2316 				settingValue.position += 1;
       
  2317 				control.setting.set( settingValue );
       
  2318 			}
       
  2319 		}
       
  2320 	} );
       
  2321 
       
  2322 	/**
       
  2323 	 * wp.customize.Menus.MenuNameControl
       
  2324 	 *
       
  2325 	 * Customizer control for a nav menu's name.
       
  2326 	 *
       
  2327 	 * @constructor
       
  2328 	 * @augments wp.customize.Control
       
  2329 	 */
       
  2330 	api.Menus.MenuNameControl = api.Control.extend({
       
  2331 
       
  2332 		ready: function() {
       
  2333 			var control = this;
       
  2334 
       
  2335 			if ( control.setting ) {
       
  2336 				var settingValue = control.setting();
       
  2337 
       
  2338 				control.nameElement = new api.Element( control.container.find( '.menu-name-field' ) );
       
  2339 
       
  2340 				control.nameElement.bind(function( value ) {
       
  2341 					var settingValue = control.setting();
       
  2342 					if ( settingValue && settingValue.name !== value ) {
       
  2343 						settingValue = _.clone( settingValue );
       
  2344 						settingValue.name = value;
       
  2345 						control.setting.set( settingValue );
       
  2346 					}
       
  2347 				});
       
  2348 				if ( settingValue ) {
       
  2349 					control.nameElement.set( settingValue.name );
       
  2350 				}
       
  2351 
       
  2352 				control.setting.bind(function( object ) {
       
  2353 					if ( object ) {
       
  2354 						control.nameElement.set( object.name );
       
  2355 					}
       
  2356 				});
       
  2357 			}
       
  2358 		}
       
  2359 	});
       
  2360 
       
  2361 	/**
       
  2362 	 * wp.customize.Menus.MenuLocationsControl
       
  2363 	 *
       
  2364 	 * Customizer control for a nav menu's locations.
       
  2365 	 *
       
  2366 	 * @since 4.9.0
       
  2367 	 * @constructor
       
  2368 	 * @augments wp.customize.Control
       
  2369 	 */
       
  2370 	api.Menus.MenuLocationsControl = api.Control.extend({
       
  2371 
       
  2372 		/**
       
  2373 		 * Set up the control.
       
  2374 		 *
       
  2375 		 * @since 4.9.0
       
  2376 		 */
       
  2377 		ready: function () {
       
  2378 			var control = this;
       
  2379 
       
  2380 			control.container.find( '.assigned-menu-location' ).each(function() {
       
  2381 				var container = $( this ),
       
  2382 					checkbox = container.find( 'input[type=checkbox]' ),
       
  2383 					element = new api.Element( checkbox ),
       
  2384 					navMenuLocationSetting = api( 'nav_menu_locations[' + checkbox.data( 'location-id' ) + ']' ),
       
  2385 					isNewMenu = control.params.menu_id === '',
       
  2386 					updateCheckbox = isNewMenu ? _.noop : function( checked ) {
       
  2387 						element.set( checked );
       
  2388 					},
       
  2389 					updateSetting = isNewMenu ? _.noop : function( checked ) {
       
  2390 						navMenuLocationSetting.set( checked ? control.params.menu_id : 0 );
       
  2391 					},
       
  2392 					updateSelectedMenuLabel = function( selectedMenuId ) {
       
  2393 						var menuSetting = api( 'nav_menu[' + String( selectedMenuId ) + ']' );
       
  2394 						if ( ! selectedMenuId || ! menuSetting || ! menuSetting() ) {
       
  2395 							container.find( '.theme-location-set' ).hide();
       
  2396 						} else {
       
  2397 							container.find( '.theme-location-set' ).show().find( 'span' ).text( displayNavMenuName( menuSetting().name ) );
       
  2398 						}
       
  2399 					};
       
  2400 
       
  2401 				updateCheckbox( navMenuLocationSetting.get() === control.params.menu_id );
       
  2402 
       
  2403 				checkbox.on( 'change', function() {
       
  2404 					// Note: We can't use element.bind( function( checked ){ ... } ) here because it will trigger a change as well.
       
  2405 					updateSetting( this.checked );
       
  2406 				} );
       
  2407 
       
  2408 				navMenuLocationSetting.bind( function( selectedMenuId ) {
       
  2409 					updateCheckbox( selectedMenuId === control.params.menu_id );
       
  2410 					updateSelectedMenuLabel( selectedMenuId );
       
  2411 				} );
       
  2412 				updateSelectedMenuLabel( navMenuLocationSetting.get() );
       
  2413 			});
       
  2414 		},
       
  2415 
       
  2416 		/**
       
  2417 		 * Set the selected locations.
       
  2418 		 *
       
  2419 		 * This method sets the selected locations and allows us to do things like
       
  2420 		 * set the default location for a new menu.
       
  2421 		 *
       
  2422 		 * @since 4.9.0
       
  2423 		 *
       
  2424 		 * @param {Object.<string,boolean>} selections - A map of location selections.
       
  2425 		 * @returns {void}
       
  2426 		 */
       
  2427 		setSelections: function( selections ) {
       
  2428 			this.container.find( '.menu-location' ).each( function( i, checkboxNode ) {
       
  2429 				var locationId = checkboxNode.dataset.locationId;
       
  2430 				checkboxNode.checked = locationId in selections ? selections[ locationId ] : false;
       
  2431 			} );
       
  2432 		}
       
  2433 	});
       
  2434 
       
  2435 	/**
       
  2436 	 * wp.customize.Menus.MenuAutoAddControl
       
  2437 	 *
       
  2438 	 * Customizer control for a nav menu's auto add.
       
  2439 	 *
       
  2440 	 * @constructor
       
  2441 	 * @augments wp.customize.Control
       
  2442 	 */
       
  2443 	api.Menus.MenuAutoAddControl = api.Control.extend({
       
  2444 
       
  2445 		ready: function() {
       
  2446 			var control = this,
       
  2447 				settingValue = control.setting();
       
  2448 
       
  2449 			/*
       
  2450 			 * Since the control is not registered in PHP, we need to prevent the
       
  2451 			 * preview's sending of the activeControls to result in this control
       
  2452 			 * being deactivated.
       
  2453 			 */
       
  2454 			control.active.validate = function() {
       
  2455 				var value, section = api.section( control.section() );
       
  2456 				if ( section ) {
       
  2457 					value = section.active();
       
  2458 				} else {
       
  2459 					value = false;
       
  2460 				}
       
  2461 				return value;
       
  2462 			};
       
  2463 
       
  2464 			control.autoAddElement = new api.Element( control.container.find( 'input[type=checkbox].auto_add' ) );
       
  2465 
       
  2466 			control.autoAddElement.bind(function( value ) {
       
  2467 				var settingValue = control.setting();
       
  2468 				if ( settingValue && settingValue.name !== value ) {
       
  2469 					settingValue = _.clone( settingValue );
       
  2470 					settingValue.auto_add = value;
       
  2471 					control.setting.set( settingValue );
       
  2472 				}
       
  2473 			});
       
  2474 			if ( settingValue ) {
       
  2475 				control.autoAddElement.set( settingValue.auto_add );
       
  2476 			}
       
  2477 
       
  2478 			control.setting.bind(function( object ) {
       
  2479 				if ( object ) {
       
  2480 					control.autoAddElement.set( object.auto_add );
       
  2481 				}
       
  2482 			});
       
  2483 		}
       
  2484 
       
  2485 	});
       
  2486 
       
  2487 	/**
       
  2488 	 * wp.customize.Menus.MenuControl
       
  2489 	 *
       
  2490 	 * Customizer control for menus.
       
  2491 	 * Note that 'nav_menu' must match the WP_Menu_Customize_Control::$type
       
  2492 	 *
       
  2493 	 * @constructor
       
  2494 	 * @augments wp.customize.Control
       
  2495 	 */
       
  2496 	api.Menus.MenuControl = api.Control.extend({
       
  2497 		/**
       
  2498 		 * Set up the control.
       
  2499 		 */
       
  2500 		ready: function() {
       
  2501 			var control = this,
       
  2502 				section = api.section( control.section() ),
       
  2503 				menuId = control.params.menu_id,
       
  2504 				menu = control.setting(),
       
  2505 				name,
       
  2506 				widgetTemplate,
       
  2507 				select;
       
  2508 
       
  2509 			if ( 'undefined' === typeof this.params.menu_id ) {
       
  2510 				throw new Error( 'params.menu_id was not defined' );
       
  2511 			}
       
  2512 
       
  2513 			/*
       
  2514 			 * Since the control is not registered in PHP, we need to prevent the
       
  2515 			 * preview's sending of the activeControls to result in this control
       
  2516 			 * being deactivated.
       
  2517 			 */
       
  2518 			control.active.validate = function() {
       
  2519 				var value;
       
  2520 				if ( section ) {
       
  2521 					value = section.active();
       
  2522 				} else {
       
  2523 					value = false;
       
  2524 				}
       
  2525 				return value;
       
  2526 			};
       
  2527 
       
  2528 			control.$controlSection = section.headContainer;
       
  2529 			control.$sectionContent = control.container.closest( '.accordion-section-content' );
       
  2530 
       
  2531 			this._setupModel();
       
  2532 
       
  2533 			api.section( control.section(), function( section ) {
       
  2534 				section.deferred.initSortables.done(function( menuList ) {
       
  2535 					control._setupSortable( menuList );
       
  2536 				});
       
  2537 			} );
       
  2538 
       
  2539 			this._setupAddition();
       
  2540 			this._setupTitle();
       
  2541 
       
  2542 			// Add menu to Navigation Menu widgets.
       
  2543 			if ( menu ) {
       
  2544 				name = displayNavMenuName( menu.name );
       
  2545 
       
  2546 				// Add the menu to the existing controls.
       
  2547 				api.control.each( function( widgetControl ) {
       
  2548 					if ( ! widgetControl.extended( api.controlConstructor.widget_form ) || 'nav_menu' !== widgetControl.params.widget_id_base ) {
       
  2549 						return;
       
  2550 					}
       
  2551 					widgetControl.container.find( '.nav-menu-widget-form-controls:first' ).show();
       
  2552 					widgetControl.container.find( '.nav-menu-widget-no-menus-message:first' ).hide();
       
  2553 
       
  2554 					select = widgetControl.container.find( 'select' );
       
  2555 					if ( 0 === select.find( 'option[value=' + String( menuId ) + ']' ).length ) {
       
  2556 						select.append( new Option( name, menuId ) );
       
  2557 					}
       
  2558 				} );
       
  2559 
       
  2560 				// Add the menu to the widget template.
       
  2561 				widgetTemplate = $( '#available-widgets-list .widget-tpl:has( input.id_base[ value=nav_menu ] )' );
       
  2562 				widgetTemplate.find( '.nav-menu-widget-form-controls:first' ).show();
       
  2563 				widgetTemplate.find( '.nav-menu-widget-no-menus-message:first' ).hide();
       
  2564 				select = widgetTemplate.find( '.widget-inside select:first' );
       
  2565 				if ( 0 === select.find( 'option[value=' + String( menuId ) + ']' ).length ) {
       
  2566 					select.append( new Option( name, menuId ) );
       
  2567 				}
       
  2568 			}
       
  2569 
       
  2570 			/*
       
  2571 			 * Wait for menu items to be added.
       
  2572 			 * Ideally, we'd bind to an event indicating construction is complete,
       
  2573 			 * but deferring appears to be the best option today.
       
  2574 			 */
       
  2575 			_.defer( function () {
       
  2576 				control.updateInvitationVisibility();
       
  2577 			} );
       
  2578 		},
       
  2579 
       
  2580 		/**
       
  2581 		 * Update ordering of menu item controls when the setting is updated.
       
  2582 		 */
       
  2583 		_setupModel: function() {
       
  2584 			var control = this,
       
  2585 				menuId = control.params.menu_id;
       
  2586 
       
  2587 			control.setting.bind( function( to ) {
       
  2588 				var name;
       
  2589 				if ( false === to ) {
       
  2590 					control._handleDeletion();
       
  2591 				} else {
       
  2592 					// Update names in the Navigation Menu widgets.
       
  2593 					name = displayNavMenuName( to.name );
       
  2594 					api.control.each( function( widgetControl ) {
       
  2595 						if ( ! widgetControl.extended( api.controlConstructor.widget_form ) || 'nav_menu' !== widgetControl.params.widget_id_base ) {
       
  2596 							return;
       
  2597 						}
       
  2598 						var select = widgetControl.container.find( 'select' );
       
  2599 						select.find( 'option[value=' + String( menuId ) + ']' ).text( name );
       
  2600 					});
       
  2601 				}
       
  2602 			} );
       
  2603 		},
       
  2604 
       
  2605 		/**
       
  2606 		 * Allow items in each menu to be re-ordered, and for the order to be previewed.
       
  2607 		 *
       
  2608 		 * Notice that the UI aspects here are handled by wpNavMenu.initSortables()
       
  2609 		 * which is called in MenuSection.onChangeExpanded()
       
  2610 		 *
       
  2611 		 * @param {object} menuList - The element that has sortable().
       
  2612 		 */
       
  2613 		_setupSortable: function( menuList ) {
       
  2614 			var control = this;
       
  2615 
       
  2616 			if ( ! menuList.is( control.$sectionContent ) ) {
       
  2617 				throw new Error( 'Unexpected menuList.' );
       
  2618 			}
       
  2619 
       
  2620 			menuList.on( 'sortstart', function() {
       
  2621 				control.isSorting = true;
       
  2622 			});
       
  2623 
       
  2624 			menuList.on( 'sortstop', function() {
       
  2625 				setTimeout( function() { // Next tick.
       
  2626 					var menuItemContainerIds = control.$sectionContent.sortable( 'toArray' ),
       
  2627 						menuItemControls = [],
       
  2628 						position = 0,
       
  2629 						priority = 10;
       
  2630 
       
  2631 					control.isSorting = false;
       
  2632 
       
  2633 					// Reset horizontal scroll position when done dragging.
       
  2634 					control.$sectionContent.scrollLeft( 0 );
       
  2635 
       
  2636 					_.each( menuItemContainerIds, function( menuItemContainerId ) {
       
  2637 						var menuItemId, menuItemControl, matches;
       
  2638 						matches = menuItemContainerId.match( /^customize-control-nav_menu_item-(-?\d+)$/, '' );
       
  2639 						if ( ! matches ) {
       
  2640 							return;
       
  2641 						}
       
  2642 						menuItemId = parseInt( matches[1], 10 );
       
  2643 						menuItemControl = api.control( 'nav_menu_item[' + String( menuItemId ) + ']' );
       
  2644 						if ( menuItemControl ) {
       
  2645 							menuItemControls.push( menuItemControl );
       
  2646 						}
       
  2647 					} );
       
  2648 
       
  2649 					_.each( menuItemControls, function( menuItemControl ) {
       
  2650 						if ( false === menuItemControl.setting() ) {
       
  2651 							// Skip deleted items.
       
  2652 							return;
       
  2653 						}
       
  2654 						var setting = _.clone( menuItemControl.setting() );
       
  2655 						position += 1;
       
  2656 						priority += 1;
       
  2657 						setting.position = position;
       
  2658 						menuItemControl.priority( priority );
       
  2659 
       
  2660 						// Note that wpNavMenu will be setting this .menu-item-data-parent-id input's value.
       
  2661 						setting.menu_item_parent = parseInt( menuItemControl.container.find( '.menu-item-data-parent-id' ).val(), 10 );
       
  2662 						if ( ! setting.menu_item_parent ) {
       
  2663 							setting.menu_item_parent = 0;
       
  2664 						}
       
  2665 
       
  2666 						menuItemControl.setting.set( setting );
       
  2667 					});
       
  2668 				});
       
  2669 
       
  2670 			});
       
  2671 			control.isReordering = false;
       
  2672 
       
  2673 			/**
       
  2674 			 * Keyboard-accessible reordering.
       
  2675 			 */
       
  2676 			this.container.find( '.reorder-toggle' ).on( 'click', function() {
       
  2677 				control.toggleReordering( ! control.isReordering );
       
  2678 			} );
       
  2679 		},
       
  2680 
       
  2681 		/**
       
  2682 		 * Set up UI for adding a new menu item.
       
  2683 		 */
       
  2684 		_setupAddition: function() {
       
  2685 			var self = this;
       
  2686 
       
  2687 			this.container.find( '.add-new-menu-item' ).on( 'click', function( event ) {
       
  2688 				if ( self.$sectionContent.hasClass( 'reordering' ) ) {
       
  2689 					return;
       
  2690 				}
       
  2691 
       
  2692 				if ( ! $( 'body' ).hasClass( 'adding-menu-items' ) ) {
       
  2693 					$( this ).attr( 'aria-expanded', 'true' );
       
  2694 					api.Menus.availableMenuItemsPanel.open( self );
       
  2695 				} else {
       
  2696 					$( this ).attr( 'aria-expanded', 'false' );
       
  2697 					api.Menus.availableMenuItemsPanel.close();
       
  2698 					event.stopPropagation();
       
  2699 				}
       
  2700 			} );
       
  2701 		},
       
  2702 
       
  2703 		_handleDeletion: function() {
       
  2704 			var control = this,
       
  2705 				section,
       
  2706 				menuId = control.params.menu_id,
       
  2707 				removeSection,
       
  2708 				widgetTemplate,
       
  2709 				navMenuCount = 0;
       
  2710 			section = api.section( control.section() );
       
  2711 			removeSection = function() {
       
  2712 				section.container.remove();
       
  2713 				api.section.remove( section.id );
       
  2714 			};
       
  2715 
       
  2716 			if ( section && section.expanded() ) {
       
  2717 				section.collapse({
       
  2718 					completeCallback: function() {
       
  2719 						removeSection();
       
  2720 						wp.a11y.speak( api.Menus.data.l10n.menuDeleted );
       
  2721 						api.panel( 'nav_menus' ).focus();
       
  2722 					}
       
  2723 				});
       
  2724 			} else {
       
  2725 				removeSection();
       
  2726 			}
       
  2727 
       
  2728 			api.each(function( setting ) {
       
  2729 				if ( /^nav_menu\[/.test( setting.id ) && false !== setting() ) {
       
  2730 					navMenuCount += 1;
       
  2731 				}
       
  2732 			});
       
  2733 
       
  2734 			// Remove the menu from any Navigation Menu widgets.
       
  2735 			api.control.each(function( widgetControl ) {
       
  2736 				if ( ! widgetControl.extended( api.controlConstructor.widget_form ) || 'nav_menu' !== widgetControl.params.widget_id_base ) {
       
  2737 					return;
       
  2738 				}
       
  2739 				var select = widgetControl.container.find( 'select' );
       
  2740 				if ( select.val() === String( menuId ) ) {
       
  2741 					select.prop( 'selectedIndex', 0 ).trigger( 'change' );
       
  2742 				}
       
  2743 
       
  2744 				widgetControl.container.find( '.nav-menu-widget-form-controls:first' ).toggle( 0 !== navMenuCount );
       
  2745 				widgetControl.container.find( '.nav-menu-widget-no-menus-message:first' ).toggle( 0 === navMenuCount );
       
  2746 				widgetControl.container.find( 'option[value=' + String( menuId ) + ']' ).remove();
       
  2747 			});
       
  2748 
       
  2749 			// Remove the menu to the nav menu widget template.
       
  2750 			widgetTemplate = $( '#available-widgets-list .widget-tpl:has( input.id_base[ value=nav_menu ] )' );
       
  2751 			widgetTemplate.find( '.nav-menu-widget-form-controls:first' ).toggle( 0 !== navMenuCount );
       
  2752 			widgetTemplate.find( '.nav-menu-widget-no-menus-message:first' ).toggle( 0 === navMenuCount );
       
  2753 			widgetTemplate.find( 'option[value=' + String( menuId ) + ']' ).remove();
       
  2754 		},
       
  2755 
       
  2756 		/**
       
  2757 		 * Update Section Title as menu name is changed.
       
  2758 		 */
       
  2759 		_setupTitle: function() {
       
  2760 			var control = this;
       
  2761 
       
  2762 			control.setting.bind( function( menu ) {
       
  2763 				if ( ! menu ) {
       
  2764 					return;
       
  2765 				}
       
  2766 
       
  2767 				var section = api.section( control.section() ),
       
  2768 					menuId = control.params.menu_id,
       
  2769 					controlTitle = section.headContainer.find( '.accordion-section-title' ),
       
  2770 					sectionTitle = section.contentContainer.find( '.customize-section-title h3' ),
       
  2771 					location = section.headContainer.find( '.menu-in-location' ),
       
  2772 					action = sectionTitle.find( '.customize-action' ),
       
  2773 					name = displayNavMenuName( menu.name );
       
  2774 
       
  2775 				// Update the control title
       
  2776 				controlTitle.text( name );
       
  2777 				if ( location.length ) {
       
  2778 					location.appendTo( controlTitle );
       
  2779 				}
       
  2780 
       
  2781 				// Update the section title
       
  2782 				sectionTitle.text( name );
       
  2783 				if ( action.length ) {
       
  2784 					action.prependTo( sectionTitle );
       
  2785 				}
       
  2786 
       
  2787 				// Update the nav menu name in location selects.
       
  2788 				api.control.each( function( control ) {
       
  2789 					if ( /^nav_menu_locations\[/.test( control.id ) ) {
       
  2790 						control.container.find( 'option[value=' + menuId + ']' ).text( name );
       
  2791 					}
       
  2792 				} );
       
  2793 
       
  2794 				// Update the nav menu name in all location checkboxes.
       
  2795 				section.contentContainer.find( '.customize-control-checkbox input' ).each( function() {
       
  2796 					if ( $( this ).prop( 'checked' ) ) {
       
  2797 						$( '.current-menu-location-name-' + $( this ).data( 'location-id' ) ).text( name );
       
  2798 					}
       
  2799 				} );
       
  2800 			} );
       
  2801 		},
       
  2802 
       
  2803 		/***********************************************************************
       
  2804 		 * Begin public API methods
       
  2805 		 **********************************************************************/
       
  2806 
       
  2807 		/**
       
  2808 		 * Enable/disable the reordering UI
       
  2809 		 *
       
  2810 		 * @param {Boolean} showOrHide to enable/disable reordering
       
  2811 		 */
       
  2812 		toggleReordering: function( showOrHide ) {
       
  2813 			var addNewItemBtn = this.container.find( '.add-new-menu-item' ),
       
  2814 				reorderBtn = this.container.find( '.reorder-toggle' ),
       
  2815 				itemsTitle = this.$sectionContent.find( '.item-title' );
       
  2816 
       
  2817 			showOrHide = Boolean( showOrHide );
       
  2818 
       
  2819 			if ( showOrHide === this.$sectionContent.hasClass( 'reordering' ) ) {
       
  2820 				return;
       
  2821 			}
       
  2822 
       
  2823 			this.isReordering = showOrHide;
       
  2824 			this.$sectionContent.toggleClass( 'reordering', showOrHide );
       
  2825 			this.$sectionContent.sortable( this.isReordering ? 'disable' : 'enable' );
       
  2826 			if ( this.isReordering ) {
       
  2827 				addNewItemBtn.attr({ 'tabindex': '-1', 'aria-hidden': 'true' });
       
  2828 				reorderBtn.attr( 'aria-label', api.Menus.data.l10n.reorderLabelOff );
       
  2829 				wp.a11y.speak( api.Menus.data.l10n.reorderModeOn );
       
  2830 				itemsTitle.attr( 'aria-hidden', 'false' );
       
  2831 			} else {
       
  2832 				addNewItemBtn.removeAttr( 'tabindex aria-hidden' );
       
  2833 				reorderBtn.attr( 'aria-label', api.Menus.data.l10n.reorderLabelOn );
       
  2834 				wp.a11y.speak( api.Menus.data.l10n.reorderModeOff );
       
  2835 				itemsTitle.attr( 'aria-hidden', 'true' );
       
  2836 			}
       
  2837 
       
  2838 			if ( showOrHide ) {
       
  2839 				_( this.getMenuItemControls() ).each( function( formControl ) {
       
  2840 					formControl.collapseForm();
       
  2841 				} );
       
  2842 			}
       
  2843 		},
       
  2844 
       
  2845 		/**
       
  2846 		 * @return {wp.customize.controlConstructor.nav_menu_item[]}
       
  2847 		 */
       
  2848 		getMenuItemControls: function() {
       
  2849 			var menuControl = this,
       
  2850 				menuItemControls = [],
       
  2851 				menuTermId = menuControl.params.menu_id;
       
  2852 
       
  2853 			api.control.each(function( control ) {
       
  2854 				if ( 'nav_menu_item' === control.params.type && control.setting() && menuTermId === control.setting().nav_menu_term_id ) {
       
  2855 					menuItemControls.push( control );
       
  2856 				}
       
  2857 			});
       
  2858 
       
  2859 			return menuItemControls;
       
  2860 		},
       
  2861 
       
  2862 		/**
       
  2863 		 * Make sure that each menu item control has the proper depth.
       
  2864 		 */
       
  2865 		reflowMenuItems: function() {
       
  2866 			var menuControl = this,
       
  2867 				menuItemControls = menuControl.getMenuItemControls(),
       
  2868 				reflowRecursively;
       
  2869 
       
  2870 			reflowRecursively = function( context ) {
       
  2871 				var currentMenuItemControls = [],
       
  2872 					thisParent = context.currentParent;
       
  2873 				_.each( context.menuItemControls, function( menuItemControl ) {
       
  2874 					if ( thisParent === menuItemControl.setting().menu_item_parent ) {
       
  2875 						currentMenuItemControls.push( menuItemControl );
       
  2876 						// @todo We could remove this item from menuItemControls now, for efficiency.
       
  2877 					}
       
  2878 				});
       
  2879 				currentMenuItemControls.sort( function( a, b ) {
       
  2880 					return a.setting().position - b.setting().position;
       
  2881 				});
       
  2882 
       
  2883 				_.each( currentMenuItemControls, function( menuItemControl ) {
       
  2884 					// Update position.
       
  2885 					context.currentAbsolutePosition += 1;
       
  2886 					menuItemControl.priority.set( context.currentAbsolutePosition ); // This will change the sort order.
       
  2887 
       
  2888 					// Update depth.
       
  2889 					if ( ! menuItemControl.container.hasClass( 'menu-item-depth-' + String( context.currentDepth ) ) ) {
       
  2890 						_.each( menuItemControl.container.prop( 'className' ).match( /menu-item-depth-\d+/g ), function( className ) {
       
  2891 							menuItemControl.container.removeClass( className );
       
  2892 						});
       
  2893 						menuItemControl.container.addClass( 'menu-item-depth-' + String( context.currentDepth ) );
       
  2894 					}
       
  2895 					menuItemControl.container.data( 'item-depth', context.currentDepth );
       
  2896 
       
  2897 					// Process any children items.
       
  2898 					context.currentDepth += 1;
       
  2899 					context.currentParent = menuItemControl.params.menu_item_id;
       
  2900 					reflowRecursively( context );
       
  2901 					context.currentDepth -= 1;
       
  2902 					context.currentParent = thisParent;
       
  2903 				});
       
  2904 
       
  2905 				// Update class names for reordering controls.
       
  2906 				if ( currentMenuItemControls.length ) {
       
  2907 					_( currentMenuItemControls ).each(function( menuItemControl ) {
       
  2908 						menuItemControl.container.removeClass( 'move-up-disabled move-down-disabled move-left-disabled move-right-disabled' );
       
  2909 						if ( 0 === context.currentDepth ) {
       
  2910 							menuItemControl.container.addClass( 'move-left-disabled' );
       
  2911 						} else if ( 10 === context.currentDepth ) {
       
  2912 							menuItemControl.container.addClass( 'move-right-disabled' );
       
  2913 						}
       
  2914 					});
       
  2915 
       
  2916 					currentMenuItemControls[0].container
       
  2917 						.addClass( 'move-up-disabled' )
       
  2918 						.addClass( 'move-right-disabled' )
       
  2919 						.toggleClass( 'move-down-disabled', 1 === currentMenuItemControls.length );
       
  2920 					currentMenuItemControls[ currentMenuItemControls.length - 1 ].container
       
  2921 						.addClass( 'move-down-disabled' )
       
  2922 						.toggleClass( 'move-up-disabled', 1 === currentMenuItemControls.length );
       
  2923 				}
       
  2924 			};
       
  2925 
       
  2926 			reflowRecursively( {
       
  2927 				menuItemControls: menuItemControls,
       
  2928 				currentParent: 0,
       
  2929 				currentDepth: 0,
       
  2930 				currentAbsolutePosition: 0
       
  2931 			} );
       
  2932 
       
  2933 			menuControl.updateInvitationVisibility( menuItemControls );
       
  2934 			menuControl.container.find( '.reorder-toggle' ).toggle( menuItemControls.length > 1 );
       
  2935 		},
       
  2936 
       
  2937 		/**
       
  2938 		 * Note that this function gets debounced so that when a lot of setting
       
  2939 		 * changes are made at once, for instance when moving a menu item that
       
  2940 		 * has child items, this function will only be called once all of the
       
  2941 		 * settings have been updated.
       
  2942 		 */
       
  2943 		debouncedReflowMenuItems: _.debounce( function() {
       
  2944 			this.reflowMenuItems.apply( this, arguments );
       
  2945 		}, 0 ),
       
  2946 
       
  2947 		/**
       
  2948 		 * Add a new item to this menu.
       
  2949 		 *
       
  2950 		 * @param {object} item - Value for the nav_menu_item setting to be created.
       
  2951 		 * @returns {wp.customize.Menus.controlConstructor.nav_menu_item} The newly-created nav_menu_item control instance.
       
  2952 		 */
       
  2953 		addItemToMenu: function( item ) {
       
  2954 			var menuControl = this, customizeId, settingArgs, setting, menuItemControl, placeholderId, position = 0, priority = 10;
       
  2955 
       
  2956 			_.each( menuControl.getMenuItemControls(), function( control ) {
       
  2957 				if ( false === control.setting() ) {
       
  2958 					return;
       
  2959 				}
       
  2960 				priority = Math.max( priority, control.priority() );
       
  2961 				if ( 0 === control.setting().menu_item_parent ) {
       
  2962 					position = Math.max( position, control.setting().position );
       
  2963 				}
       
  2964 			});
       
  2965 			position += 1;
       
  2966 			priority += 1;
       
  2967 
       
  2968 			item = $.extend(
       
  2969 				{},
       
  2970 				api.Menus.data.defaultSettingValues.nav_menu_item,
       
  2971 				item,
       
  2972 				{
       
  2973 					nav_menu_term_id: menuControl.params.menu_id,
       
  2974 					original_title: item.title,
       
  2975 					position: position
       
  2976 				}
       
  2977 			);
       
  2978 			delete item.id; // only used by Backbone
       
  2979 
       
  2980 			placeholderId = api.Menus.generatePlaceholderAutoIncrementId();
       
  2981 			customizeId = 'nav_menu_item[' + String( placeholderId ) + ']';
       
  2982 			settingArgs = {
       
  2983 				type: 'nav_menu_item',
       
  2984 				transport: api.Menus.data.settingTransport,
       
  2985 				previewer: api.previewer
       
  2986 			};
       
  2987 			setting = api.create( customizeId, customizeId, {}, settingArgs );
       
  2988 			setting.set( item ); // Change from initial empty object to actual item to mark as dirty.
       
  2989 
       
  2990 			// Add the menu item control.
       
  2991 			menuItemControl = new api.controlConstructor.nav_menu_item( customizeId, {
       
  2992 				type: 'nav_menu_item',
       
  2993 				section: menuControl.id,
       
  2994 				priority: priority,
       
  2995 				settings: {
       
  2996 					'default': customizeId
       
  2997 				},
       
  2998 				menu_item_id: placeholderId
       
  2999 			} );
       
  3000 
       
  3001 			api.control.add( menuItemControl );
       
  3002 			setting.preview();
       
  3003 			menuControl.debouncedReflowMenuItems();
       
  3004 
       
  3005 			wp.a11y.speak( api.Menus.data.l10n.itemAdded );
       
  3006 
       
  3007 			return menuItemControl;
       
  3008 		},
       
  3009 
       
  3010 		/**
       
  3011 		 * Show an invitation to add new menu items when there are no menu items.
       
  3012 		 *
       
  3013 		 * @since 4.9.0
       
  3014 		 *
       
  3015 		 * @param {wp.customize.controlConstructor.nav_menu_item[]} optionalMenuItemControls
       
  3016 		 */
       
  3017 		updateInvitationVisibility: function ( optionalMenuItemControls ) {
       
  3018 			var menuItemControls = optionalMenuItemControls || this.getMenuItemControls();
       
  3019 
       
  3020 			this.container.find( '.new-menu-item-invitation' ).toggle( menuItemControls.length === 0 );
       
  3021 		}
       
  3022 	} );
       
  3023 
       
  3024 	/**
       
  3025 	 * wp.customize.Menus.NewMenuControl
       
  3026 	 *
       
  3027 	 * Customizer control for creating new menus and handling deletion of existing menus.
       
  3028 	 * Note that 'new_menu' must match the WP_Customize_New_Menu_Control::$type.
       
  3029 	 *
       
  3030 	 * @constructor
       
  3031 	 * @augments wp.customize.Control
       
  3032 	 * @deprecated 4.9.0 This class is no longer used due to new menu creation UX.
       
  3033 	 */
       
  3034 	api.Menus.NewMenuControl = api.Control.extend({
       
  3035 
       
  3036 		/**
       
  3037 		 * Initialize.
       
  3038 		 *
       
  3039 		 * @deprecated 4.9.0
       
  3040 		 */
       
  3041 		initialize: function() {
       
  3042 			if ( 'undefined' !== typeof console && console.warn ) {
       
  3043 				console.warn( '[DEPRECATED] wp.customize.NewMenuControl will be removed. Please use wp.customize.Menus.createNavMenu() instead.' );
       
  3044 			}
       
  3045 			api.Control.prototype.initialize.apply( this, arguments );
       
  3046 		},
       
  3047 
       
  3048 		/**
       
  3049 		 * Set up the control.
       
  3050 		 *
       
  3051 		 * @deprecated 4.9.0
       
  3052 		 */
       
  3053 		ready: function() {
       
  3054 			this._bindHandlers();
       
  3055 		},
       
  3056 
       
  3057 		_bindHandlers: function() {
       
  3058 			var self = this,
       
  3059 				name = $( '#customize-control-new_menu_name input' ),
       
  3060 				submit = $( '#create-new-menu-submit' );
       
  3061 			name.on( 'keydown', function( event ) {
       
  3062 				if ( 13 === event.which ) { // Enter.
       
  3063 					self.submit();
       
  3064 				}
       
  3065 			} );
       
  3066 			submit.on( 'click', function( event ) {
       
  3067 				self.submit();
       
  3068 				event.stopPropagation();
       
  3069 				event.preventDefault();
       
  3070 			} );
       
  3071 		},
       
  3072 
       
  3073 		/**
       
  3074 		 * Create the new menu with the name supplied.
       
  3075 		 *
       
  3076 		 * @deprecated 4.9.0
       
  3077 		 */
       
  3078 		submit: function() {
       
  3079 
       
  3080 			var control = this,
       
  3081 				container = control.container.closest( '.accordion-section-new-menu' ),
       
  3082 				nameInput = container.find( '.menu-name-field' ).first(),
       
  3083 				name = nameInput.val(),
       
  3084 				menuSection;
       
  3085 
       
  3086 			if ( ! name ) {
       
  3087 				nameInput.addClass( 'invalid' );
       
  3088 				nameInput.focus();
       
  3089 				return;
       
  3090 			}
       
  3091 
       
  3092 			menuSection = api.Menus.createNavMenu( name );
       
  3093 
       
  3094 			// Clear name field.
       
  3095 			nameInput.val( '' );
       
  3096 			nameInput.removeClass( 'invalid' );
       
  3097 
       
  3098 			wp.a11y.speak( api.Menus.data.l10n.menuAdded );
       
  3099 
       
  3100 			// Focus on the new menu section.
       
  3101 			menuSection.focus();
       
  3102 		}
       
  3103 	});
       
  3104 
       
  3105 	/**
       
  3106 	 * Extends wp.customize.controlConstructor with control constructor for
       
  3107 	 * menu_location, menu_item, nav_menu, and new_menu.
       
  3108 	 */
       
  3109 	$.extend( api.controlConstructor, {
       
  3110 		nav_menu_location: api.Menus.MenuLocationControl,
       
  3111 		nav_menu_item: api.Menus.MenuItemControl,
       
  3112 		nav_menu: api.Menus.MenuControl,
       
  3113 		nav_menu_name: api.Menus.MenuNameControl,
       
  3114 		new_menu: api.Menus.NewMenuControl, // @todo Remove in 5.0. See #42364.
       
  3115 		nav_menu_locations: api.Menus.MenuLocationsControl,
       
  3116 		nav_menu_auto_add: api.Menus.MenuAutoAddControl
       
  3117 	});
       
  3118 
       
  3119 	/**
       
  3120 	 * Extends wp.customize.panelConstructor with section constructor for menus.
       
  3121 	 */
       
  3122 	$.extend( api.panelConstructor, {
       
  3123 		nav_menus: api.Menus.MenusPanel
       
  3124 	});
       
  3125 
       
  3126 	/**
       
  3127 	 * Extends wp.customize.sectionConstructor with section constructor for menu.
       
  3128 	 */
       
  3129 	$.extend( api.sectionConstructor, {
       
  3130 		nav_menu: api.Menus.MenuSection,
       
  3131 		new_menu: api.Menus.NewMenuSection
       
  3132 	});
       
  3133 
       
  3134 	/**
       
  3135 	 * Init Customizer for menus.
       
  3136 	 */
       
  3137 	api.bind( 'ready', function() {
       
  3138 
       
  3139 		// Set up the menu items panel.
       
  3140 		api.Menus.availableMenuItemsPanel = new api.Menus.AvailableMenuItemsPanelView({
       
  3141 			collection: api.Menus.availableMenuItems
       
  3142 		});
       
  3143 
       
  3144 		api.bind( 'saved', function( data ) {
       
  3145 			if ( data.nav_menu_updates || data.nav_menu_item_updates ) {
       
  3146 				api.Menus.applySavedData( data );
       
  3147 			}
       
  3148 		} );
       
  3149 
       
  3150 		/*
       
  3151 		 * Reset the list of posts created in the customizer once published.
       
  3152 		 * The setting is updated quietly (bypassing events being triggered)
       
  3153 		 * so that the customized state doesn't become immediately dirty.
       
  3154 		 */
       
  3155 		api.state( 'changesetStatus' ).bind( function( status ) {
       
  3156 			if ( 'publish' === status ) {
       
  3157 				api( 'nav_menus_created_posts' )._value = [];
       
  3158 			}
       
  3159 		} );
       
  3160 
       
  3161 		// Open and focus menu control.
       
  3162 		api.previewer.bind( 'focus-nav-menu-item-control', api.Menus.focusMenuItemControl );
       
  3163 	} );
       
  3164 
       
  3165 	/**
       
  3166 	 * When customize_save comes back with a success, make sure any inserted
       
  3167 	 * nav menus and items are properly re-added with their newly-assigned IDs.
       
  3168 	 *
       
  3169 	 * @param {object} data
       
  3170 	 * @param {array} data.nav_menu_updates
       
  3171 	 * @param {array} data.nav_menu_item_updates
       
  3172 	 */
       
  3173 	api.Menus.applySavedData = function( data ) {
       
  3174 
       
  3175 		var insertedMenuIdMapping = {}, insertedMenuItemIdMapping = {};
       
  3176 
       
  3177 		_( data.nav_menu_updates ).each(function( update ) {
       
  3178 			var oldCustomizeId, newCustomizeId, customizeId, oldSetting, newSetting, setting, settingValue, oldSection, newSection, wasSaved, widgetTemplate, navMenuCount, shouldExpandNewSection;
       
  3179 			if ( 'inserted' === update.status ) {
       
  3180 				if ( ! update.previous_term_id ) {
       
  3181 					throw new Error( 'Expected previous_term_id' );
       
  3182 				}
       
  3183 				if ( ! update.term_id ) {
       
  3184 					throw new Error( 'Expected term_id' );
       
  3185 				}
       
  3186 				oldCustomizeId = 'nav_menu[' + String( update.previous_term_id ) + ']';
       
  3187 				if ( ! api.has( oldCustomizeId ) ) {
       
  3188 					throw new Error( 'Expected setting to exist: ' + oldCustomizeId );
       
  3189 				}
       
  3190 				oldSetting = api( oldCustomizeId );
       
  3191 				if ( ! api.section.has( oldCustomizeId ) ) {
       
  3192 					throw new Error( 'Expected control to exist: ' + oldCustomizeId );
       
  3193 				}
       
  3194 				oldSection = api.section( oldCustomizeId );
       
  3195 
       
  3196 				settingValue = oldSetting.get();
       
  3197 				if ( ! settingValue ) {
       
  3198 					throw new Error( 'Did not expect setting to be empty (deleted).' );
       
  3199 				}
       
  3200 				settingValue = $.extend( _.clone( settingValue ), update.saved_value );
       
  3201 
       
  3202 				insertedMenuIdMapping[ update.previous_term_id ] = update.term_id;
       
  3203 				newCustomizeId = 'nav_menu[' + String( update.term_id ) + ']';
       
  3204 				newSetting = api.create( newCustomizeId, newCustomizeId, settingValue, {
       
  3205 					type: 'nav_menu',
       
  3206 					transport: api.Menus.data.settingTransport,
       
  3207 					previewer: api.previewer
       
  3208 				} );
       
  3209 
       
  3210 				shouldExpandNewSection = oldSection.expanded();
       
  3211 				if ( shouldExpandNewSection ) {
       
  3212 					oldSection.collapse();
       
  3213 				}
       
  3214 
       
  3215 				// Add the menu section.
       
  3216 				newSection = new api.Menus.MenuSection( newCustomizeId, {
       
  3217 					panel: 'nav_menus',
       
  3218 					title: settingValue.name,
       
  3219 					customizeAction: api.Menus.data.l10n.customizingMenus,
       
  3220 					type: 'nav_menu',
       
  3221 					priority: oldSection.priority.get(),
       
  3222 					menu_id: update.term_id
       
  3223 				} );
       
  3224 
       
  3225 				// Add new control for the new menu.
       
  3226 				api.section.add( newSection );
       
  3227 
       
  3228 				// Update the values for nav menus in Navigation Menu controls.
       
  3229 				api.control.each( function( setting ) {
       
  3230 					if ( ! setting.extended( api.controlConstructor.widget_form ) || 'nav_menu' !== setting.params.widget_id_base ) {
       
  3231 						return;
       
  3232 					}
       
  3233 					var select, oldMenuOption, newMenuOption;
       
  3234 					select = setting.container.find( 'select' );
       
  3235 					oldMenuOption = select.find( 'option[value=' + String( update.previous_term_id ) + ']' );
       
  3236 					newMenuOption = select.find( 'option[value=' + String( update.term_id ) + ']' );
       
  3237 					newMenuOption.prop( 'selected', oldMenuOption.prop( 'selected' ) );
       
  3238 					oldMenuOption.remove();
       
  3239 				} );
       
  3240 
       
  3241 				// Delete the old placeholder nav_menu.
       
  3242 				oldSetting.callbacks.disable(); // Prevent setting triggering Customizer dirty state when set.
       
  3243 				oldSetting.set( false );
       
  3244 				oldSetting.preview();
       
  3245 				newSetting.preview();
       
  3246 				oldSetting._dirty = false;
       
  3247 
       
  3248 				// Remove nav_menu section.
       
  3249 				oldSection.container.remove();
       
  3250 				api.section.remove( oldCustomizeId );
       
  3251 
       
  3252 				// Update the nav_menu widget to reflect removed placeholder menu.
       
  3253 				navMenuCount = 0;
       
  3254 				api.each(function( setting ) {
       
  3255 					if ( /^nav_menu\[/.test( setting.id ) && false !== setting() ) {
       
  3256 						navMenuCount += 1;
       
  3257 					}
       
  3258 				});
       
  3259 				widgetTemplate = $( '#available-widgets-list .widget-tpl:has( input.id_base[ value=nav_menu ] )' );
       
  3260 				widgetTemplate.find( '.nav-menu-widget-form-controls:first' ).toggle( 0 !== navMenuCount );
       
  3261 				widgetTemplate.find( '.nav-menu-widget-no-menus-message:first' ).toggle( 0 === navMenuCount );
       
  3262 				widgetTemplate.find( 'option[value=' + String( update.previous_term_id ) + ']' ).remove();
       
  3263 
       
  3264 				// Update the nav_menu_locations[...] controls to remove the placeholder menus from the dropdown options.
       
  3265 				wp.customize.control.each(function( control ){
       
  3266 					if ( /^nav_menu_locations\[/.test( control.id ) ) {
       
  3267 						control.container.find( 'option[value=' + String( update.previous_term_id ) + ']' ).remove();
       
  3268 					}
       
  3269 				});
       
  3270 
       
  3271 				// Update nav_menu_locations to reference the new ID.
       
  3272 				api.each( function( setting ) {
       
  3273 					var wasSaved = api.state( 'saved' ).get();
       
  3274 					if ( /^nav_menu_locations\[/.test( setting.id ) && setting.get() === update.previous_term_id ) {
       
  3275 						setting.set( update.term_id );
       
  3276 						setting._dirty = false; // Not dirty because this is has also just been done on server in WP_Customize_Nav_Menu_Setting::update().
       
  3277 						api.state( 'saved' ).set( wasSaved );
       
  3278 						setting.preview();
       
  3279 					}
       
  3280 				} );
       
  3281 
       
  3282 				if ( shouldExpandNewSection ) {
       
  3283 					newSection.expand();
       
  3284 				}
       
  3285 			} else if ( 'updated' === update.status ) {
       
  3286 				customizeId = 'nav_menu[' + String( update.term_id ) + ']';
       
  3287 				if ( ! api.has( customizeId ) ) {
       
  3288 					throw new Error( 'Expected setting to exist: ' + customizeId );
       
  3289 				}
       
  3290 
       
  3291 				// Make sure the setting gets updated with its sanitized server value (specifically the conflict-resolved name).
       
  3292 				setting = api( customizeId );
       
  3293 				if ( ! _.isEqual( update.saved_value, setting.get() ) ) {
       
  3294 					wasSaved = api.state( 'saved' ).get();
       
  3295 					setting.set( update.saved_value );
       
  3296 					setting._dirty = false;
       
  3297 					api.state( 'saved' ).set( wasSaved );
       
  3298 				}
       
  3299 			}
       
  3300 		} );
       
  3301 
       
  3302 		// Build up mapping of nav_menu_item placeholder IDs to inserted IDs.
       
  3303 		_( data.nav_menu_item_updates ).each(function( update ) {
       
  3304 			if ( update.previous_post_id ) {
       
  3305 				insertedMenuItemIdMapping[ update.previous_post_id ] = update.post_id;
       
  3306 			}
       
  3307 		});
       
  3308 
       
  3309 		_( data.nav_menu_item_updates ).each(function( update ) {
       
  3310 			var oldCustomizeId, newCustomizeId, oldSetting, newSetting, settingValue, oldControl, newControl;
       
  3311 			if ( 'inserted' === update.status ) {
       
  3312 				if ( ! update.previous_post_id ) {
       
  3313 					throw new Error( 'Expected previous_post_id' );
       
  3314 				}
       
  3315 				if ( ! update.post_id ) {
       
  3316 					throw new Error( 'Expected post_id' );
       
  3317 				}
       
  3318 				oldCustomizeId = 'nav_menu_item[' + String( update.previous_post_id ) + ']';
       
  3319 				if ( ! api.has( oldCustomizeId ) ) {
       
  3320 					throw new Error( 'Expected setting to exist: ' + oldCustomizeId );
       
  3321 				}
       
  3322 				oldSetting = api( oldCustomizeId );
       
  3323 				if ( ! api.control.has( oldCustomizeId ) ) {
       
  3324 					throw new Error( 'Expected control to exist: ' + oldCustomizeId );
       
  3325 				}
       
  3326 				oldControl = api.control( oldCustomizeId );
       
  3327 
       
  3328 				settingValue = oldSetting.get();
       
  3329 				if ( ! settingValue ) {
       
  3330 					throw new Error( 'Did not expect setting to be empty (deleted).' );
       
  3331 				}
       
  3332 				settingValue = _.clone( settingValue );
       
  3333 
       
  3334 				// If the parent menu item was also inserted, update the menu_item_parent to the new ID.
       
  3335 				if ( settingValue.menu_item_parent < 0 ) {
       
  3336 					if ( ! insertedMenuItemIdMapping[ settingValue.menu_item_parent ] ) {
       
  3337 						throw new Error( 'inserted ID for menu_item_parent not available' );
       
  3338 					}
       
  3339 					settingValue.menu_item_parent = insertedMenuItemIdMapping[ settingValue.menu_item_parent ];
       
  3340 				}
       
  3341 
       
  3342 				// If the menu was also inserted, then make sure it uses the new menu ID for nav_menu_term_id.
       
  3343 				if ( insertedMenuIdMapping[ settingValue.nav_menu_term_id ] ) {
       
  3344 					settingValue.nav_menu_term_id = insertedMenuIdMapping[ settingValue.nav_menu_term_id ];
       
  3345 				}
       
  3346 
       
  3347 				newCustomizeId = 'nav_menu_item[' + String( update.post_id ) + ']';
       
  3348 				newSetting = api.create( newCustomizeId, newCustomizeId, settingValue, {
       
  3349 					type: 'nav_menu_item',
       
  3350 					transport: api.Menus.data.settingTransport,
       
  3351 					previewer: api.previewer
       
  3352 				} );
       
  3353 
       
  3354 				// Add the menu control.
       
  3355 				newControl = new api.controlConstructor.nav_menu_item( newCustomizeId, {
       
  3356 					type: 'nav_menu_item',
       
  3357 					menu_id: update.post_id,
       
  3358 					section: 'nav_menu[' + String( settingValue.nav_menu_term_id ) + ']',
       
  3359 					priority: oldControl.priority.get(),
       
  3360 					settings: {
       
  3361 						'default': newCustomizeId
       
  3362 					},
       
  3363 					menu_item_id: update.post_id
       
  3364 				} );
       
  3365 
       
  3366 				// Remove old control.
       
  3367 				oldControl.container.remove();
       
  3368 				api.control.remove( oldCustomizeId );
       
  3369 
       
  3370 				// Add new control to take its place.
       
  3371 				api.control.add( newControl );
       
  3372 
       
  3373 				// Delete the placeholder and preview the new setting.
       
  3374 				oldSetting.callbacks.disable(); // Prevent setting triggering Customizer dirty state when set.
       
  3375 				oldSetting.set( false );
       
  3376 				oldSetting.preview();
       
  3377 				newSetting.preview();
       
  3378 				oldSetting._dirty = false;
       
  3379 
       
  3380 				newControl.container.toggleClass( 'menu-item-edit-inactive', oldControl.container.hasClass( 'menu-item-edit-inactive' ) );
       
  3381 			}
       
  3382 		});
       
  3383 
       
  3384 		/*
       
  3385 		 * Update the settings for any nav_menu widgets that had selected a placeholder ID.
       
  3386 		 */
       
  3387 		_.each( data.widget_nav_menu_updates, function( widgetSettingValue, widgetSettingId ) {
       
  3388 			var setting = api( widgetSettingId );
       
  3389 			if ( setting ) {
       
  3390 				setting._value = widgetSettingValue;
       
  3391 				setting.preview(); // Send to the preview now so that menu refresh will use the inserted menu.
       
  3392 			}
       
  3393 		});
       
  3394 	};
       
  3395 
       
  3396 	/**
       
  3397 	 * Focus a menu item control.
       
  3398 	 *
       
  3399 	 * @param {string} menuItemId
       
  3400 	 */
       
  3401 	api.Menus.focusMenuItemControl = function( menuItemId ) {
       
  3402 		var control = api.Menus.getMenuItemControl( menuItemId );
       
  3403 		if ( control ) {
       
  3404 			control.focus();
       
  3405 		}
       
  3406 	};
       
  3407 
       
  3408 	/**
       
  3409 	 * Get the control for a given menu.
       
  3410 	 *
       
  3411 	 * @param menuId
       
  3412 	 * @return {wp.customize.controlConstructor.menus[]}
       
  3413 	 */
       
  3414 	api.Menus.getMenuControl = function( menuId ) {
       
  3415 		return api.control( 'nav_menu[' + menuId + ']' );
       
  3416 	};
       
  3417 
       
  3418 	/**
       
  3419 	 * Given a menu item ID, get the control associated with it.
       
  3420 	 *
       
  3421 	 * @param {string} menuItemId
       
  3422 	 * @return {object|null}
       
  3423 	 */
       
  3424 	api.Menus.getMenuItemControl = function( menuItemId ) {
       
  3425 		return api.control( menuItemIdToSettingId( menuItemId ) );
       
  3426 	};
       
  3427 
       
  3428 	/**
       
  3429 	 * @param {String} menuItemId
       
  3430 	 */
       
  3431 	function menuItemIdToSettingId( menuItemId ) {
       
  3432 		return 'nav_menu_item[' + menuItemId + ']';
       
  3433 	}
       
  3434 
       
  3435 	/**
       
  3436 	 * Apply sanitize_text_field()-like logic to the supplied name, returning a
       
  3437 	 * "unnammed" fallback string if the name is then empty.
       
  3438 	 *
       
  3439 	 * @param {string} name
       
  3440 	 * @returns {string}
       
  3441 	 */
       
  3442 	function displayNavMenuName( name ) {
       
  3443 		name = name || '';
       
  3444 		name = $( '<div>' ).text( name ).html(); // Emulate esc_html() which is used in wp-admin/nav-menus.php.
       
  3445 		name = $.trim( name );
       
  3446 		return name || api.Menus.data.l10n.unnamed;
       
  3447 	}
       
  3448 
       
  3449 })( wp.customize, wp, jQuery );