wp/wp-admin/js/nav-menu.js
changeset 7 cf61fcea0001
parent 5 5e2f62d02dcd
child 9 177826044cd9
equal deleted inserted replaced
6:490d5cc509ed 7:cf61fcea0001
    18 
    18 
    19 	api = wpNavMenu = {
    19 	api = wpNavMenu = {
    20 
    20 
    21 		options : {
    21 		options : {
    22 			menuItemDepthPerLevel : 30, // Do not use directly. Use depthToPx and pxToDepth instead.
    22 			menuItemDepthPerLevel : 30, // Do not use directly. Use depthToPx and pxToDepth instead.
    23 			globalMaxDepth : 11
    23 			globalMaxDepth:  11,
       
    24 			sortableItems:   '> *',
       
    25 			targetTolerance: 0
    24 		},
    26 		},
    25 
    27 
    26 		menuList : undefined,	// Set in init.
    28 		menuList : undefined,	// Set in init.
    27 		targetList : undefined, // Set in init.
    29 		targetList : undefined, // Set in init.
    28 		menusChanged : false,
    30 		menusChanged : false,
    29 		isRTL: !! ( 'undefined' != typeof isRtl && isRtl ),
    31 		isRTL: !! ( 'undefined' != typeof isRtl && isRtl ),
    30 		negateIfRTL: ( 'undefined' != typeof isRtl && isRtl ) ? -1 : 1,
    32 		negateIfRTL: ( 'undefined' != typeof isRtl && isRtl ) ? -1 : 1,
       
    33 		lastSearch: '',
    31 
    34 
    32 		// Functions that run on init.
    35 		// Functions that run on init.
    33 		init : function() {
    36 		init : function() {
    34 			api.menuList = $('#menu-to-edit');
    37 			api.menuList = $('#menu-to-edit');
    35 			api.targetList = api.menuList;
    38 			api.targetList = api.menuList;
    36 
    39 
    37 			this.jQueryExtensions();
    40 			this.jQueryExtensions();
    38 
    41 
    39 			this.attachMenuEditListeners();
    42 			this.attachMenuEditListeners();
    40 
    43 
    41 			this.setupInputWithDefaultTitle();
       
    42 			this.attachQuickSearchListeners();
    44 			this.attachQuickSearchListeners();
    43 			this.attachThemeLocationsListeners();
    45 			this.attachThemeLocationsListeners();
       
    46 			this.attachMenuSaveSubmitListeners();
    44 
    47 
    45 			this.attachTabsPanelListeners();
    48 			this.attachTabsPanelListeners();
    46 
    49 
    47 			this.attachUnsavedChangesListener();
    50 			this.attachUnsavedChangesListener();
    48 
    51 
    77 					});
    80 					});
    78 				},
    81 				},
    79 				shiftDepthClass : function(change) {
    82 				shiftDepthClass : function(change) {
    80 					return this.each(function(){
    83 					return this.each(function(){
    81 						var t = $(this),
    84 						var t = $(this),
    82 							depth = t.menuItemDepth();
    85 							depth = t.menuItemDepth(),
    83 						$(this).removeClass('menu-item-depth-'+ depth )
    86 							newDepth = depth + change;
    84 							.addClass('menu-item-depth-'+ (depth + change) );
    87 
       
    88 						t.removeClass( 'menu-item-depth-'+ depth )
       
    89 							.addClass( 'menu-item-depth-'+ ( newDepth ) );
       
    90 
       
    91 						if ( 0 === newDepth ) {
       
    92 							t.find( '.is-submenu' ).hide();
       
    93 						}
    85 					});
    94 					});
    86 				},
    95 				},
    87 				childMenuItems : function() {
    96 				childMenuItems : function() {
    88 					var result = $();
    97 					var result = $();
    89 					this.each(function(){
    98 					this.each(function(){
    90 						var t = $(this), depth = t.menuItemDepth(), next = t.next();
    99 						var t = $(this), depth = t.menuItemDepth(), next = t.next( '.menu-item' );
    91 						while( next.length && next.menuItemDepth() > depth ) {
   100 						while( next.length && next.menuItemDepth() > depth ) {
    92 							result = result.add( next );
   101 							result = result.add( next );
    93 							next = next.next();
   102 							next = next.next( '.menu-item' );
    94 						}
   103 						}
    95 					});
   104 					});
    96 					return result;
   105 					return result;
    97 				},
   106 				},
    98 				shiftHorizontally : function( dir ) {
   107 				shiftHorizontally : function( dir ) {
   175 						// If no items are checked, bail.
   184 						// If no items are checked, bail.
   176 						if ( !checkboxes.length )
   185 						if ( !checkboxes.length )
   177 							return false;
   186 							return false;
   178 
   187 
   179 						// Show the ajax spinner
   188 						// Show the ajax spinner
   180 						t.find( '.spinner' ).addClass( 'is-active' );
   189 						t.find( '.button-controls .spinner' ).addClass( 'is-active' );
   181 
   190 
   182 						// Retrieve menu item data
   191 						// Retrieve menu item data
   183 						$(checkboxes).each(function(){
   192 						$(checkboxes).each(function(){
   184 							var t = $(this),
   193 							var t = $(this),
   185 								listItemDBIDMatch = re.exec( t.attr('name') ),
   194 								listItemDBIDMatch = re.exec( t.attr('name') ),
   192 
   201 
   193 						// Add the items
   202 						// Add the items
   194 						api.addItemToMenu(menuItems, processMethod, function(){
   203 						api.addItemToMenu(menuItems, processMethod, function(){
   195 							// Deselect the items and hide the ajax spinner
   204 							// Deselect the items and hide the ajax spinner
   196 							checkboxes.removeAttr('checked');
   205 							checkboxes.removeAttr('checked');
   197 							t.find( '.spinner' ).removeClass( 'is-active' );
   206 							t.find( '.button-controls .spinner' ).removeClass( 'is-active' );
   198 						});
   207 						});
   199 					});
   208 					});
   200 				},
   209 				},
   201 				getItemData : function( itemType, id ) {
   210 				getItemData : function( itemType, id ) {
   202 					itemType = itemType || 'menu-item';
   211 					itemType = itemType || 'menu-item';
   386 			api.refreshKeyboardAccessibility();
   395 			api.refreshKeyboardAccessibility();
   387 			api.refreshAdvancedAccessibility();
   396 			api.refreshAdvancedAccessibility();
   388 
   397 
   389 			// Refresh the accessibility when the user comes close to the item in any way
   398 			// Refresh the accessibility when the user comes close to the item in any way
   390 			menu.on( 'mouseenter.refreshAccessibility focus.refreshAccessibility touchstart.refreshAccessibility' , '.menu-item' , function(){
   399 			menu.on( 'mouseenter.refreshAccessibility focus.refreshAccessibility touchstart.refreshAccessibility' , '.menu-item' , function(){
   391 				api.refreshAdvancedAccessibilityOfItem( $( this ).find( '.item-edit' ) );
   400 				api.refreshAdvancedAccessibilityOfItem( $( this ).find( 'a.item-edit' ) );
   392 			} );
   401 			} );
   393 
   402 
   394 			// We have to update on click as well because we might hover first, change the item, and then click.
   403 			// We have to update on click as well because we might hover first, change the item, and then click.
   395 			menu.on( 'click', '.item-edit', function() {
   404 			menu.on( 'click', 'a.item-edit', function() {
   396 				api.refreshAdvancedAccessibilityOfItem( $( this ) );
   405 				api.refreshAdvancedAccessibilityOfItem( $( this ) );
   397 			} );
   406 			} );
   398 
   407 
   399 			// Links for moving items
   408 			// Links for moving items
   400 			menu.on( 'click', '.menus-move', function ( e ) {
   409 			menu.on( 'click', '.menus-move', function () {
   401 				var $this = $( this ),
   410 				var $this = $( this ),
   402 					dir = $this.data( 'dir' );
   411 					dir = $this.data( 'dir' );
   403 
   412 
   404 				if ( 'undefined' !== typeof dir ) {
   413 				if ( 'undefined' !== typeof dir ) {
   405 					api.moveMenuItem( $( this ).parents( 'li.menu-item' ).find( 'a.item-edit' ), dir );
   414 					api.moveMenuItem( $( this ).parents( 'li.menu-item' ).find( 'a.item-edit' ), dir );
   406 				}
   415 				}
   407 				e.preventDefault();
       
   408 			});
   416 			});
   409 		},
   417 		},
   410 
   418 
   411 		/**
   419 		/**
   412 		 * refreshAdvancedAccessibilityOfItem( [itemToRefresh] )
   420 		 * refreshAdvancedAccessibilityOfItem( [itemToRefresh] )
   435 				prevItemNameLeft = menuItem.prevAll('.menu-item-depth-' + prevItemDepth).first().find( '.menu-item-title' ).text(),
   443 				prevItemNameLeft = menuItem.prevAll('.menu-item-depth-' + prevItemDepth).first().find( '.menu-item-title' ).text(),
   436 				prevItemNameRight = menuItem.prevAll('.menu-item-depth-' + depth).first().find( '.menu-item-title' ).text(),
   444 				prevItemNameRight = menuItem.prevAll('.menu-item-depth-' + depth).first().find( '.menu-item-title' ).text(),
   437 				totalMenuItems = $('#menu-to-edit li').length,
   445 				totalMenuItems = $('#menu-to-edit li').length,
   438 				hasSameDepthSibling = menuItem.nextAll( '.menu-item-depth-' + depth ).length;
   446 				hasSameDepthSibling = menuItem.nextAll( '.menu-item-depth-' + depth ).length;
   439 
   447 
   440 				menuItem.find( '.field-move' ).toggle( totalMenuItems > 1 ); 
   448 				menuItem.find( '.field-move' ).toggle( totalMenuItems > 1 );
   441 
   449 
   442 			// Where can they move this menu item?
   450 			// Where can they move this menu item?
   443 			if ( 0 !== position ) {
   451 			if ( 0 !== position ) {
   444 				thisLink = menuItem.find( '.menus-move-up' );
   452 				thisLink = menuItem.find( '.menus-move-up' );
   445 				thisLink.prop( 'title', menus.moveUp ).css( 'display', 'inline' );
   453 				thisLink.attr( 'aria-label', menus.moveUp ).css( 'display', 'inline' );
   446 			}
   454 			}
   447 
   455 
   448 			if ( 0 !== position && isPrimaryMenuItem ) {
   456 			if ( 0 !== position && isPrimaryMenuItem ) {
   449 				thisLink = menuItem.find( '.menus-move-top' );
   457 				thisLink = menuItem.find( '.menus-move-top' );
   450 				thisLink.prop( 'title', menus.moveToTop ).css( 'display', 'inline' );
   458 				thisLink.attr( 'aria-label', menus.moveToTop ).css( 'display', 'inline' );
   451 			}
   459 			}
   452 
   460 
   453 			if ( position + 1 !== totalMenuItems && 0 !== position ) {
   461 			if ( position + 1 !== totalMenuItems && 0 !== position ) {
   454 				thisLink = menuItem.find( '.menus-move-down' );
   462 				thisLink = menuItem.find( '.menus-move-down' );
   455 				thisLink.prop( 'title', menus.moveDown ).css( 'display', 'inline' );
   463 				thisLink.attr( 'aria-label', menus.moveDown ).css( 'display', 'inline' );
   456 			}
   464 			}
   457 
   465 
   458 			if ( 0 === position && 0 !== hasSameDepthSibling ) {
   466 			if ( 0 === position && 0 !== hasSameDepthSibling ) {
   459 				thisLink = menuItem.find( '.menus-move-down' );
   467 				thisLink = menuItem.find( '.menus-move-down' );
   460 				thisLink.prop( 'title', menus.moveDown ).css( 'display', 'inline' );
   468 				thisLink.attr( 'aria-label', menus.moveDown ).css( 'display', 'inline' );
   461 			}
   469 			}
   462 
   470 
   463 			if ( ! isPrimaryMenuItem ) {
   471 			if ( ! isPrimaryMenuItem ) {
   464 				thisLink = menuItem.find( '.menus-move-left' ),
   472 				thisLink = menuItem.find( '.menus-move-left' ),
   465 				thisLinkText = menus.outFrom.replace( '%s', prevItemNameLeft );
   473 				thisLinkText = menus.outFrom.replace( '%s', prevItemNameLeft );
   466 				thisLink.prop( 'title', menus.moveOutFrom.replace( '%s', prevItemNameLeft ) ).text( thisLinkText ).css( 'display', 'inline' );
   474 				thisLink.attr( 'aria-label', menus.moveOutFrom.replace( '%s', prevItemNameLeft ) ).text( thisLinkText ).css( 'display', 'inline' );
   467 			}
   475 			}
   468 
   476 
   469 			if ( 0 !== position ) {
   477 			if ( 0 !== position ) {
   470 				if ( menuItem.find( '.menu-item-data-parent-id' ).val() !== menuItem.prev().find( '.menu-item-data-db-id' ).val() ) {
   478 				if ( menuItem.find( '.menu-item-data-parent-id' ).val() !== menuItem.prev().find( '.menu-item-data-db-id' ).val() ) {
   471 					thisLink = menuItem.find( '.menus-move-right' ),
   479 					thisLink = menuItem.find( '.menus-move-right' ),
   472 					thisLinkText = menus.under.replace( '%s', prevItemNameRight );
   480 					thisLinkText = menus.under.replace( '%s', prevItemNameRight );
   473 					thisLink.prop( 'title', menus.moveUnder.replace( '%s', prevItemNameRight ) ).text( thisLinkText ).css( 'display', 'inline' );
   481 					thisLink.attr( 'aria-label', menus.moveUnder.replace( '%s', prevItemNameRight ) ).text( thisLinkText ).css( 'display', 'inline' );
   474 				}
   482 				}
   475 			}
   483 			}
   476 
   484 
   477 			if ( isPrimaryMenuItem ) {
   485 			if ( isPrimaryMenuItem ) {
   478 				primaryItems = $( '.menu-item-depth-0' ),
   486 				primaryItems = $( '.menu-item-depth-0' ),
   490 
   498 
   491 				// String together help text for sub menu items
   499 				// String together help text for sub menu items
   492 				title = menus.subMenuFocus.replace( '%1$s', itemName ).replace( '%2$d', itemPosition ).replace( '%3$s', parentItemName );
   500 				title = menus.subMenuFocus.replace( '%1$s', itemName ).replace( '%2$d', itemPosition ).replace( '%3$s', parentItemName );
   493 			}
   501 			}
   494 
   502 
   495 			$this.prop('title', title).html( title );
   503 			$this.attr( 'aria-label', title );
   496 
   504 
   497 			// Mark this item's accessibility as refreshed
   505 			// Mark this item's accessibility as refreshed
   498 			$this.data( 'needs_accessibility_refresh', false );
   506 			$this.data( 'needs_accessibility_refresh', false );
   499 		},
   507 		},
   500 
   508 
   503 		 *
   511 		 *
   504 		 * Hides all advanced accessibility buttons and marks them for refreshing.
   512 		 * Hides all advanced accessibility buttons and marks them for refreshing.
   505 		 */
   513 		 */
   506 		refreshAdvancedAccessibility : function() {
   514 		refreshAdvancedAccessibility : function() {
   507 
   515 
   508 			// Hide all links by default
   516 			// Hide all the move buttons by default.
   509 			$( '.menu-item-settings .field-move a' ).hide();
   517 			$( '.menu-item-settings .field-move .menus-move' ).hide();
   510 
   518 
   511 			// Mark all menu items as unprocessed
   519 			// Mark all menu items as unprocessed
   512 			$( '.item-edit' ).data( 'needs_accessibility_refresh', true );
   520 			$( 'a.item-edit' ).data( 'needs_accessibility_refresh', true );
   513 
   521 
   514 			// All open items have to be refreshed or they will show no links
   522 			// All open items have to be refreshed or they will show no links
   515 			$( '.menu-item-edit-active .item-edit' ).each( function() {
   523 			$( '.menu-item-edit-active a.item-edit' ).each( function() {
   516 				api.refreshAdvancedAccessibilityOfItem( this );
   524 				api.refreshAdvancedAccessibilityOfItem( this );
   517 			} );
   525 			} );
   518 		},
   526 		},
   519 
   527 
   520 		refreshKeyboardAccessibility : function() {
   528 		refreshKeyboardAccessibility : function() {
   521 			$( '.item-edit' ).off( 'focus' ).on( 'focus', function(){
   529 			$( 'a.item-edit' ).off( 'focus' ).on( 'focus', function(){
   522 				$(this).off( 'keydown' ).on( 'keydown', function(e){
   530 				$(this).off( 'keydown' ).on( 'keydown', function(e){
   523 
   531 
   524 					var arrows,
   532 					var arrows,
   525 						$this = $( this ),
   533 						$this = $( this ),
   526 						thisItem = $this.parents( 'li.menu-item' ),
   534 						thisItem = $this.parents( 'li.menu-item' ),
   618 			menuEdge += api.isRTL ? api.menuList.width() : 0;
   626 			menuEdge += api.isRTL ? api.menuList.width() : 0;
   619 
   627 
   620 			api.menuList.sortable({
   628 			api.menuList.sortable({
   621 				handle: '.menu-item-handle',
   629 				handle: '.menu-item-handle',
   622 				placeholder: 'sortable-placeholder',
   630 				placeholder: 'sortable-placeholder',
       
   631 				items: api.options.sortableItems,
   623 				start: function(e, ui) {
   632 				start: function(e, ui) {
   624 					var height, width, parent, children, tempHolder;
   633 					var height, width, parent, children, tempHolder;
   625 
   634 
   626 					// handle placement for rtl orientation
   635 					// handle placement for rtl orientation
   627 					if ( api.isRTL )
   636 					if ( api.isRTL )
   658 					width += api.depthToPx(maxChildDepth - originalDepth); // Account for children
   667 					width += api.depthToPx(maxChildDepth - originalDepth); // Account for children
   659 					width -= 2; // Subtract 2 for borders
   668 					width -= 2; // Subtract 2 for borders
   660 					ui.placeholder.width(width);
   669 					ui.placeholder.width(width);
   661 
   670 
   662 					// Update the list of menu items.
   671 					// Update the list of menu items.
   663 					tempHolder = ui.placeholder.next();
   672 					tempHolder = ui.placeholder.next( '.menu-item' );
   664 					tempHolder.css( 'margin-top', helperHeight + 'px' ); // Set the margin to absorb the placeholder
   673 					tempHolder.css( 'margin-top', helperHeight + 'px' ); // Set the margin to absorb the placeholder
   665 					ui.placeholder.detach(); // detach or jQuery UI will think the placeholder is a menu item
   674 					ui.placeholder.detach(); // detach or jQuery UI will think the placeholder is a menu item
   666 					$(this).sortable( 'refresh' ); // The children aren't sortable. We should let jQ UI know.
   675 					$(this).sortable( 'refresh' ); // The children aren't sortable. We should let jQ UI know.
   667 					ui.item.after( ui.placeholder ); // reattach the placeholder.
   676 					ui.item.after( ui.placeholder ); // reattach the placeholder.
   668 					tempHolder.css('margin-top', 0); // reset the margin
   677 					tempHolder.css('margin-top', 0); // reset the margin
   717 				},
   726 				},
   718 				sort: function(e, ui) {
   727 				sort: function(e, ui) {
   719 					var offset = ui.helper.offset(),
   728 					var offset = ui.helper.offset(),
   720 						edge = api.isRTL ? offset.left + ui.helper.width() : offset.left,
   729 						edge = api.isRTL ? offset.left + ui.helper.width() : offset.left,
   721 						depth = api.negateIfRTL * api.pxToDepth( edge - menuEdge );
   730 						depth = api.negateIfRTL * api.pxToDepth( edge - menuEdge );
       
   731 
   722 					// Check and correct if depth is not within range.
   732 					// Check and correct if depth is not within range.
   723 					// Also, if the dragged element is dragged upwards over
   733 					// Also, if the dragged element is dragged upwards over
   724 					// an item, shift the placeholder to a child position.
   734 					// an item, shift the placeholder to a child position.
   725 					if ( depth > maxDepth || offset.top < prevBottom ) depth = maxDepth;
   735 					if ( depth > maxDepth || offset.top < ( prevBottom - api.options.targetTolerance ) ) {
   726 					else if ( depth < minDepth ) depth = minDepth;
   736 						depth = maxDepth;
       
   737 					} else if ( depth < minDepth ) {
       
   738 						depth = minDepth;
       
   739 					}
   727 
   740 
   728 					if( depth != currentDepth )
   741 					if( depth != currentDepth )
   729 						updateCurrentDepth(ui, depth);
   742 						updateCurrentDepth(ui, depth);
   730 
   743 
   731 					// If we overlap the next element, manually shift downwards
   744 					// If we overlap the next element, manually shift downwards
   738 			});
   751 			});
   739 
   752 
   740 			function updateSharedVars(ui) {
   753 			function updateSharedVars(ui) {
   741 				var depth;
   754 				var depth;
   742 
   755 
   743 				prev = ui.placeholder.prev();
   756 				prev = ui.placeholder.prev( '.menu-item' );
   744 				next = ui.placeholder.next();
   757 				next = ui.placeholder.next( '.menu-item' );
   745 
   758 
   746 				// Make sure we don't select the moving item.
   759 				// Make sure we don't select the moving item.
   747 				if( prev[0] == ui.item[0] ) prev = prev.prev();
   760 				if( prev[0] == ui.item[0] ) prev = prev.prev( '.menu-item' );
   748 				if( next[0] == ui.item[0] ) next = next.next();
   761 				if( next[0] == ui.item[0] ) next = next.next( '.menu-item' );
   749 
   762 
   750 				prevBottom = (prev.length) ? prev.offset().top + prev.height() : 0;
   763 				prevBottom = (prev.length) ? prev.offset().top + prev.height() : 0;
   751 				nextThreshold = (next.length) ? next.offset().top + next.height() / 3 : 0;
   764 				nextThreshold = (next.length) ? next.offset().top + next.height() / 3 : 0;
   752 				minDepth = (next.length) ? next.menuItemDepth() : 0;
   765 				minDepth = (next.length) ? next.menuItemDepth() : 0;
   753 
   766 
   815 						return that.eventOnClickCancelLink(e.target);
   828 						return that.eventOnClickCancelLink(e.target);
   816 					}
   829 					}
   817 				}
   830 				}
   818 			});
   831 			});
   819 			$('#add-custom-links input[type="text"]').keypress(function(e){
   832 			$('#add-custom-links input[type="text"]').keypress(function(e){
       
   833 				$('#customlinkdiv').removeClass('form-invalid');
       
   834 
   820 				if ( e.keyCode === 13 ) {
   835 				if ( e.keyCode === 13 ) {
   821 					e.preventDefault();
   836 					e.preventDefault();
   822 					$( '#submit-customlinkdiv' ).click();
   837 					$( '#submit-customlinkdiv' ).click();
   823 				}
   838 				}
   824 			});
   839 			});
   825 		},
   840 		},
   826 
   841 
   827 		/**
   842 		attachMenuSaveSubmitListeners : function() {
   828 		 * An interface for managing default values for input elements
   843 			/*
   829 		 * that is both JS and accessibility-friendly.
   844 			 * When a navigation menu is saved, store a JSON representation of all form data
   830 		 *
   845 			 * in a single input to avoid PHP `max_input_vars` limitations. See #14134.
   831 		 * Input elements that add the class 'input-with-default-title'
   846 			 */
   832 		 * will have their values set to the provided HTML title when empty.
   847 			$( '#update-nav-menu' ).submit( function() {
   833 		 */
   848 				var navMenuData = $( '#update-nav-menu' ).serializeArray();
   834 		setupInputWithDefaultTitle : function() {
   849 				$( '[name="nav-menu-data"]' ).val( JSON.stringify( navMenuData ) );
   835 			var name = 'input-with-default-title';
   850 			});
   836 
       
   837 			$('.' + name).each( function(){
       
   838 				var $t = $(this), title = $t.attr('title'), val = $t.val();
       
   839 				$t.data( name, title );
       
   840 
       
   841 				if( '' === val ) $t.val( title );
       
   842 				else if ( title == val ) return;
       
   843 				else $t.removeClass( name );
       
   844 			}).focus( function(){
       
   845 				var $t = $(this);
       
   846 				if( $t.val() == $t.data(name) )
       
   847 					$t.val('').removeClass( name );
       
   848 			}).blur( function(){
       
   849 				var $t = $(this);
       
   850 				if( '' === $t.val() )
       
   851 					$t.addClass( name ).val( $t.data(name) );
       
   852 			});
       
   853 
       
   854 			$( '.blank-slate .input-with-default-title' ).focus();
       
   855 		},
   851 		},
   856 
   852 
   857 		attachThemeLocationsListeners : function() {
   853 		attachThemeLocationsListeners : function() {
   858 			var loc = $('#nav-menu-theme-locations'), params = {};
   854 			var loc = $('#nav-menu-theme-locations'), params = {};
   859 			params.action = 'menu-locations-save';
   855 			params.action = 'menu-locations-save';
   869 				return false;
   865 				return false;
   870 			});
   866 			});
   871 		},
   867 		},
   872 
   868 
   873 		attachQuickSearchListeners : function() {
   869 		attachQuickSearchListeners : function() {
   874 			var searchTimer;
   870 			var searchTimer,
   875 
   871 				inputEvent;
   876 			$('.quick-search').keypress(function(e){
   872 
   877 				var t = $(this);
   873 			// Prevent form submission.
   878 
   874 			$( '#nav-menu-meta' ).on( 'submit', function( event ) {
   879 				if( 13 == e.which ) {
   875 				event.preventDefault();
   880 					api.updateQuickSearchResults( t );
   876 			});
   881 					return false;
   877 
   882 				}
   878 			/*
   883 
   879 			 * Use feature detection to determine whether inputs should use
   884 				if( searchTimer ) clearTimeout(searchTimer);
   880 			 * the `keyup` or `input` event. Input is preferred but lacks support
   885 
   881 			 * in legacy browsers. See changeset 34078, see also ticket #26600#comment:59
   886 				searchTimer = setTimeout(function(){
   882 			 */
   887 					api.updateQuickSearchResults( t );
   883 			if ( 'oninput' in document.createElement( 'input' ) ) {
   888 				}, 400);
   884 				inputEvent = 'input';
   889 			}).attr('autocomplete','off');
   885 			} else {
       
   886 				inputEvent = 'keyup';
       
   887 			}
       
   888 
       
   889 			$( '#nav-menu-meta' ).on( inputEvent, '.quick-search', function() {
       
   890 				var $this = $( this );
       
   891 
       
   892 				$this.attr( 'autocomplete', 'off' );
       
   893 
       
   894 				if ( searchTimer ) {
       
   895 					clearTimeout( searchTimer );
       
   896 				}
       
   897 
       
   898 				searchTimer = setTimeout( function() {
       
   899 					api.updateQuickSearchResults( $this );
       
   900  				}, 500 );
       
   901 			}).on( 'blur', '.quick-search', function() {
       
   902 				api.lastSearch = '';
       
   903 			});
   890 		},
   904 		},
   891 
   905 
   892 		updateQuickSearchResults : function(input) {
   906 		updateQuickSearchResults : function(input) {
   893 			var panel, params,
   907 			var panel, params,
   894 			minSearchLength = 2,
   908 				minSearchLength = 2,
   895 			q = input.val();
   909 				q = input.val();
   896 
   910 
   897 			if( q.length < minSearchLength ) return;
   911 			/*
       
   912 			 * Minimum characters for a search. Also avoid a new AJAX search when
       
   913 			 * the pressed key (e.g. arrows) doesn't change the searched term.
       
   914 			 */
       
   915 			if ( q.length < minSearchLength || api.lastSearch == q ) {
       
   916 				return;
       
   917 			}
       
   918 
       
   919 			api.lastSearch = q;
   898 
   920 
   899 			panel = input.parents('.tabs-panel');
   921 			panel = input.parents('.tabs-panel');
   900 			params = {
   922 			params = {
   901 				'action': 'menu-quick-search',
   923 				'action': 'menu-quick-search',
   902 				'response-format': 'markup',
   924 				'response-format': 'markup',
   917 			var url = $('#custom-menu-item-url').val(),
   939 			var url = $('#custom-menu-item-url').val(),
   918 				label = $('#custom-menu-item-name').val();
   940 				label = $('#custom-menu-item-name').val();
   919 
   941 
   920 			processMethod = processMethod || api.addMenuItemToBottom;
   942 			processMethod = processMethod || api.addMenuItemToBottom;
   921 
   943 
   922 			if ( '' === url || 'http://' == url )
   944 			if ( '' === url || 'http://' == url ) {
       
   945 				$('#customlinkdiv').addClass('form-invalid');
   923 				return false;
   946 				return false;
       
   947 			}
   924 
   948 
   925 			// Show the ajax spinner
   949 			// Show the ajax spinner
   926 			$( '.customlinkdiv .spinner' ).addClass( 'is-active' );
   950 			$( '.customlinkdiv .spinner' ).addClass( 'is-active' );
   927 			this.addLinkToMenu( url, label, processMethod, function() {
   951 			this.addLinkToMenu( url, label, processMethod, function() {
   928 				// Remove the ajax spinner
   952 				// Remove the ajax spinner
   976 				callback();
  1000 				callback();
   977 			});
  1001 			});
   978 		},
  1002 		},
   979 
  1003 
   980 		/**
  1004 		/**
   981 		 * Process the add menu item request response into menu list item.
  1005 		 * Process the add menu item request response into menu list item. Appends to menu.
   982 		 *
  1006 		 *
   983 		 * @param string menuMarkup The text server response of menu item markup.
  1007 		 * @param {string} menuMarkup The text server response of menu item markup.
   984 		 * @param object req The request arguments.
  1008 		 *
       
  1009 		 * @fires document#menu-item-added Passes menuMarkup as a jQuery object.
   985 		 */
  1010 		 */
   986 		addMenuItemToBottom : function( menuMarkup ) {
  1011 		addMenuItemToBottom : function( menuMarkup ) {
   987 			$(menuMarkup).hideAdvancedMenuItemFields().appendTo( api.targetList );
  1012 			var $menuMarkup = $( menuMarkup );
       
  1013 			$menuMarkup.hideAdvancedMenuItemFields().appendTo( api.targetList );
   988 			api.refreshKeyboardAccessibility();
  1014 			api.refreshKeyboardAccessibility();
   989 			api.refreshAdvancedAccessibility();
  1015 			api.refreshAdvancedAccessibility();
   990 		},
  1016 			$( document ).trigger( 'menu-item-added', [ $menuMarkup ] );
   991 
  1017 		},
       
  1018 
       
  1019 		/**
       
  1020 		 * Process the add menu item request response into menu list item. Prepends to menu.
       
  1021 		 *
       
  1022 		 * @param {string} menuMarkup The text server response of menu item markup.
       
  1023 		 *
       
  1024 		 * @fires document#menu-item-added Passes menuMarkup as a jQuery object.
       
  1025 		 */
   992 		addMenuItemToTop : function( menuMarkup ) {
  1026 		addMenuItemToTop : function( menuMarkup ) {
   993 			$(menuMarkup).hideAdvancedMenuItemFields().prependTo( api.targetList );
  1027 			var $menuMarkup = $( menuMarkup );
       
  1028 			$menuMarkup.hideAdvancedMenuItemFields().prependTo( api.targetList );
   994 			api.refreshKeyboardAccessibility();
  1029 			api.refreshKeyboardAccessibility();
   995 			api.refreshAdvancedAccessibility();
  1030 			api.refreshAdvancedAccessibility();
       
  1031 			$( document ).trigger( 'menu-item-added', [ $menuMarkup ] );
   996 		},
  1032 		},
   997 
  1033 
   998 		attachUnsavedChangesListener : function() {
  1034 		attachUnsavedChangesListener : function() {
   999 			$('#menu-management input, #menu-management select, #menu-management, #menu-management textarea, .menu-location-menus select').change(function(){
  1035 			$('#menu-management input, #menu-management select, #menu-management, #menu-management textarea, .menu-location-menus select').change(function(){
  1000 				api.registerChange();
  1036 				api.registerChange();
  1035 					$('.tabs', wrapper).removeClass('tabs');
  1071 					$('.tabs', wrapper).removeClass('tabs');
  1036 					target.parent().addClass('tabs');
  1072 					target.parent().addClass('tabs');
  1037 
  1073 
  1038 					// select the search bar
  1074 					// select the search bar
  1039 					$('.quick-search', wrapper).focus();
  1075 					$('.quick-search', wrapper).focus();
       
  1076 
       
  1077 					// Hide controls in the search tab if no items found.
       
  1078 					if ( ! wrapper.find( '.tabs-panel-active .menu-item-title' ).length ) {
       
  1079 						wrapper.addClass( 'has-no-menu-item' );
       
  1080 					} else {
       
  1081 						wrapper.removeClass( 'has-no-menu-item' );
       
  1082 					}
  1040 
  1083 
  1041 					e.preventDefault();
  1084 					e.preventDefault();
  1042 				} else if ( target.hasClass('select-all') ) {
  1085 				} else if ( target.hasClass('select-all') ) {
  1043 					selectAreaMatch = /#(.*)$/.exec(e.target.href);
  1086 					selectAreaMatch = /#(.*)$/.exec(e.target.href);
  1044 					if ( selectAreaMatch && selectAreaMatch[1] ) {
  1087 					if ( selectAreaMatch && selectAreaMatch[1] ) {
  1055 					if ( e.target.id && 'submit-customlinkdiv' == e.target.id )
  1098 					if ( e.target.id && 'submit-customlinkdiv' == e.target.id )
  1056 						api.addCustomLink( api.addMenuItemToBottom );
  1099 						api.addCustomLink( api.addMenuItemToBottom );
  1057 					else if ( e.target.id && -1 != e.target.id.indexOf('submit-') )
  1100 					else if ( e.target.id && -1 != e.target.id.indexOf('submit-') )
  1058 						$('#' + e.target.id.replace(/submit-/, '')).addSelectedToMenu( api.addMenuItemToBottom );
  1101 						$('#' + e.target.id.replace(/submit-/, '')).addSelectedToMenu( api.addMenuItemToBottom );
  1059 					return false;
  1102 					return false;
  1060 				} else if ( target.hasClass('page-numbers') ) {
  1103 				}
  1061 					$.post( ajaxurl, e.target.href.replace(/.*\?/, '').replace(/action=([^&]*)/, '') + '&action=menu-get-metabox',
  1104 			});
  1062 						function( resp ) {
  1105 
  1063 							if ( -1 == resp.indexOf('replace-id') )
  1106 			/*
  1064 								return;
  1107 			 * Delegate the `click` event and attach it just to the pagination
  1065 
  1108 			 * links thus excluding the current page `<span>`. See ticket #35577.
  1066 							var metaBoxData = $.parseJSON(resp),
  1109 			 */
  1067 							toReplace = document.getElementById(metaBoxData['replace-id']),
  1110 			$( '#nav-menu-meta' ).on( 'click', 'a.page-numbers', function() {
  1068 							placeholder = document.createElement('div'),
  1111 				var $container = $( this ).closest( '.inside' );
  1069 							wrap = document.createElement('div');
  1112 
  1070 
  1113 				$.post( ajaxurl, this.href.replace( /.*\?/, '' ).replace( /action=([^&]*)/, '' ) + '&action=menu-get-metabox',
  1071 							if ( ! metaBoxData.markup || ! toReplace )
  1114 					function( resp ) {
  1072 								return;
  1115 						var metaBoxData = $.parseJSON( resp ),
  1073 
  1116 							toReplace;
  1074 							wrap.innerHTML = metaBoxData.markup ? metaBoxData.markup : '';
  1117 
  1075 
  1118 						if ( -1 === resp.indexOf( 'replace-id' ) ) {
  1076 							toReplace.parentNode.insertBefore( placeholder, toReplace );
  1119 							return;
  1077 							placeholder.parentNode.removeChild( toReplace );
       
  1078 
       
  1079 							placeholder.parentNode.insertBefore( wrap, placeholder );
       
  1080 
       
  1081 							placeholder.parentNode.removeChild( placeholder );
       
  1082 
       
  1083 						}
  1120 						}
  1084 					);
  1121 
  1085 
  1122 						// Get the post type menu meta box to update.
  1086 					return false;
  1123 						toReplace = document.getElementById( metaBoxData['replace-id'] );
  1087 				}
  1124 
       
  1125 						if ( ! metaBoxData.markup || ! toReplace ) {
       
  1126 							return;
       
  1127 						}
       
  1128 
       
  1129 						// Update the post type menu meta box with new content from the response.
       
  1130 						$container.html( metaBoxData.markup );
       
  1131 					}
       
  1132 				);
       
  1133 
       
  1134 				return false;
  1088 			});
  1135 			});
  1089 		},
  1136 		},
  1090 
  1137 
  1091 		eventOnClickEditLink : function(clickedEl) {
  1138 		eventOnClickEditLink : function(clickedEl) {
  1092 			var settings, item,
  1139 			var settings, item,
  1168 			var matched, newID,
  1215 			var matched, newID,
  1169 			takenIDs = {},
  1216 			takenIDs = {},
  1170 			form = document.getElementById('nav-menu-meta'),
  1217 			form = document.getElementById('nav-menu-meta'),
  1171 			pattern = /menu-item[(\[^]\]*/,
  1218 			pattern = /menu-item[(\[^]\]*/,
  1172 			$items = $('<div>').html(resp).find('li'),
  1219 			$items = $('<div>').html(resp).find('li'),
       
  1220 			wrapper = panel.closest( '.accordion-section-content' ),
  1173 			$item;
  1221 			$item;
  1174 
  1222 
  1175 			if( ! $items.length ) {
  1223 			if( ! $items.length ) {
  1176 				$('.categorychecklist', panel).html( '<li><p>' + navMenuL10n.noResultsFound + '</p></li>' );
  1224 				$('.categorychecklist', panel).html( '<li><p>' + navMenuL10n.noResultsFound + '</p></li>' );
  1177 				$( '.spinner', panel ).removeClass( 'is-active' );
  1225 				$( '.spinner', panel ).removeClass( 'is-active' );
       
  1226 				wrapper.addClass( 'has-no-menu-item' );
  1178 				return;
  1227 				return;
  1179 			}
  1228 			}
  1180 
  1229 
  1181 			$items.each(function(){
  1230 			$items.each(function(){
  1182 				$item = $(this);
  1231 				$item = $(this);
  1200 				}
  1249 				}
  1201 			});
  1250 			});
  1202 
  1251 
  1203 			$('.categorychecklist', panel).html( $items );
  1252 			$('.categorychecklist', panel).html( $items );
  1204 			$( '.spinner', panel ).removeClass( 'is-active' );
  1253 			$( '.spinner', panel ).removeClass( 'is-active' );
  1205 		},
  1254 			wrapper.removeClass( 'has-no-menu-item' );
  1206 
  1255 		},
       
  1256 
       
  1257 		/**
       
  1258 		 * Remove a menu item.
       
  1259 		 * @param  {object} el The element to be removed as a jQuery object.
       
  1260 		 *
       
  1261 		 * @fires document#menu-removing-item Passes the element to be removed.
       
  1262 		 */
  1207 		removeMenuItem : function(el) {
  1263 		removeMenuItem : function(el) {
  1208 			var children = el.childMenuItems();
  1264 			var children = el.childMenuItems();
  1209 
  1265 
       
  1266 			$( document ).trigger( 'menu-removing-item', [ el ] );
  1210 			el.addClass('deleting').animate({
  1267 			el.addClass('deleting').animate({
  1211 					opacity : 0,
  1268 					opacity : 0,
  1212 					height: 0
  1269 					height: 0
  1213 				}, 350, function() {
  1270 				}, 350, function() {
  1214 					var ins = $('#menu-instructions');
  1271 					var ins = $('#menu-instructions');