2012-08-22 20:04:18 -04:00
/ * *
* WordPress Administration Navigation Menu
* Interface JS functions
*
* @ version 2.0 . 0
*
* @ package WordPress
* @ subpackage Administration
2018-06-27 22:30:15 -04:00
* @ output wp - admin / js / nav - menu . js
2012-08-22 20:04:18 -04:00
* /
2018-08-19 09:33:24 -04:00
/* global menus, postboxes, columns, isRtl, navMenuL10n, ajaxurl, wpNavMenu */
2012-08-22 20:04:18 -04:00
( function ( $ ) {
2013-11-14 01:09:11 -05:00
var api ;
2018-08-19 09:33:24 -04:00
/ * *
* Contains all the functions to handle WordPress navigation menus administration .
*
* @ namespace wpNavMenu
* /
api = window . wpNavMenu = {
2012-08-22 20:04:18 -04:00
options : {
menuItemDepthPerLevel : 30 , // Do not use directly. Use depthToPx and pxToDepth instead.
2015-07-01 12:44:24 -04:00
globalMaxDepth : 11 ,
sortableItems : '> *' ,
targetTolerance : 0
2012-08-22 20:04:18 -04:00
} ,
menuList : undefined , // Set in init.
targetList : undefined , // Set in init.
menusChanged : false ,
isRTL : ! ! ( 'undefined' != typeof isRtl && isRtl ) ,
negateIfRTL : ( 'undefined' != typeof isRtl && isRtl ) ? - 1 : 1 ,
2016-01-22 09:26:27 -05:00
lastSearch : '' ,
2012-08-22 20:04:18 -04:00
// Functions that run on init.
init : function ( ) {
api . menuList = $ ( '#menu-to-edit' ) ;
api . targetList = api . menuList ;
this . jQueryExtensions ( ) ;
this . attachMenuEditListeners ( ) ;
this . attachQuickSearchListeners ( ) ;
this . attachThemeLocationsListeners ( ) ;
2016-02-11 14:08:27 -05:00
this . attachMenuSaveSubmitListeners ( ) ;
2012-08-22 20:04:18 -04:00
this . attachTabsPanelListeners ( ) ;
this . attachUnsavedChangesListener ( ) ;
2013-03-20 01:46:39 -04:00
if ( api . menuList . length )
2012-08-22 20:04:18 -04:00
this . initSortables ( ) ;
2013-03-20 01:46:39 -04:00
if ( menus . oneThemeLocationNoMenus )
2013-02-15 23:53:59 -05:00
$ ( '#posttype-page' ) . addSelectedToMenu ( api . addMenuItemToBottom ) ;
2013-03-27 07:46:08 -04:00
this . initManageLocations ( ) ;
2013-02-15 23:53:59 -05:00
this . initAccessibility ( ) ;
this . initToggles ( ) ;
2014-06-09 14:39:15 -04:00
this . initPreviewing ( ) ;
2012-08-22 20:04:18 -04:00
} ,
jQueryExtensions : function ( ) {
2020-01-28 19:45:18 -05:00
// jQuery extensions.
2012-08-22 20:04:18 -04:00
$ . fn . extend ( {
menuItemDepth : function ( ) {
var margin = api . isRTL ? this . eq ( 0 ) . css ( 'margin-right' ) : this . eq ( 0 ) . css ( 'margin-left' ) ;
return api . pxToDepth ( margin && - 1 != margin . indexOf ( 'px' ) ? margin . slice ( 0 , - 2 ) : 0 ) ;
} ,
updateDepthClass : function ( current , prev ) {
return this . each ( function ( ) {
var t = $ ( this ) ;
prev = prev || t . menuItemDepth ( ) ;
$ ( this ) . removeClass ( 'menu-item-depth-' + prev )
. addClass ( 'menu-item-depth-' + current ) ;
} ) ;
} ,
shiftDepthClass : function ( change ) {
return this . each ( function ( ) {
var t = $ ( this ) ,
2016-09-15 12:10:31 -04:00
depth = t . menuItemDepth ( ) ,
newDepth = depth + change ;
2016-09-15 12:56:29 -04:00
2016-09-15 12:10:31 -04:00
t . removeClass ( 'menu-item-depth-' + depth )
. addClass ( 'menu-item-depth-' + ( newDepth ) ) ;
2016-09-15 12:56:29 -04:00
if ( 0 === newDepth ) {
2016-09-15 12:10:31 -04:00
t . find ( '.is-submenu' ) . hide ( ) ;
2016-09-15 12:56:29 -04:00
}
2012-08-22 20:04:18 -04:00
} ) ;
} ,
childMenuItems : function ( ) {
var result = $ ( ) ;
this . each ( function ( ) {
2015-06-13 10:03:25 -04:00
var t = $ ( this ) , depth = t . menuItemDepth ( ) , next = t . next ( '.menu-item' ) ;
2012-08-22 20:04:18 -04:00
while ( next . length && next . menuItemDepth ( ) > depth ) {
result = result . add ( next ) ;
2015-06-13 10:03:25 -04:00
next = next . next ( '.menu-item' ) ;
2012-08-22 20:04:18 -04:00
}
} ) ;
return result ;
} ,
2013-02-15 23:53:59 -05:00
shiftHorizontally : function ( dir ) {
return this . each ( function ( ) {
var t = $ ( this ) ,
depth = t . menuItemDepth ( ) ,
newDepth = depth + dir ;
2020-01-28 19:45:18 -05:00
// Change .menu-item-depth-n class.
2013-02-15 23:53:59 -05:00
t . moveHorizontally ( newDepth , depth ) ;
} ) ;
} ,
moveHorizontally : function ( newDepth , depth ) {
return this . each ( function ( ) {
var t = $ ( this ) ,
children = t . childMenuItems ( ) ,
diff = newDepth - depth ,
subItemText = t . find ( '.is-submenu' ) ;
2020-01-28 19:45:18 -05:00
// Change .menu-item-depth-n class.
2013-02-15 23:53:59 -05:00
t . updateDepthClass ( newDepth , depth ) . updateParentMenuItemDBId ( ) ;
2020-01-28 19:45:18 -05:00
// If it has children, move those too.
2013-02-15 23:53:59 -05:00
if ( children ) {
2013-11-14 01:09:11 -05:00
children . each ( function ( ) {
2013-02-15 23:53:59 -05:00
var t = $ ( this ) ,
thisDepth = t . menuItemDepth ( ) ,
newDepth = thisDepth + diff ;
t . updateDepthClass ( newDepth , thisDepth ) . updateParentMenuItemDBId ( ) ;
} ) ;
}
2020-01-28 19:45:18 -05:00
// Show "Sub item" helper text.
2013-02-15 23:53:59 -05:00
if ( 0 === newDepth )
subItemText . hide ( ) ;
else
subItemText . show ( ) ;
} ) ;
} ,
2012-08-22 20:04:18 -04:00
updateParentMenuItemDBId : function ( ) {
return this . each ( function ( ) {
var item = $ ( this ) ,
2013-02-15 23:53:59 -05:00
input = item . find ( '.menu-item-data-parent-id' ) ,
2013-11-14 01:09:11 -05:00
depth = parseInt ( item . menuItemDepth ( ) , 10 ) ,
2013-02-15 23:53:59 -05:00
parentDepth = depth - 1 ,
parent = item . prevAll ( '.menu-item-depth-' + parentDepth ) . first ( ) ;
2012-08-22 20:04:18 -04:00
2020-01-28 19:45:18 -05:00
if ( 0 === depth ) { // Item is on the top level, has no parent.
2012-08-22 20:04:18 -04:00
input . val ( 0 ) ;
} else { // Find the parent item, and retrieve its object id.
2013-02-15 23:53:59 -05:00
input . val ( parent . find ( '.menu-item-data-db-id' ) . val ( ) ) ;
2012-08-22 20:04:18 -04:00
}
} ) ;
} ,
hideAdvancedMenuItemFields : function ( ) {
return this . each ( function ( ) {
var that = $ ( this ) ;
$ ( '.hide-column-tog' ) . not ( ':checked' ) . each ( function ( ) {
that . find ( '.field-' + $ ( this ) . val ( ) ) . addClass ( 'hidden-field' ) ;
} ) ;
} ) ;
} ,
/ * *
* Adds selected menu items to the menu .
*
2017-12-15 08:42:46 -05:00
* @ ignore
*
2012-08-22 20:04:18 -04:00
* @ param jQuery metabox The metabox jQuery object .
* /
addSelectedToMenu : function ( processMethod ) {
2013-11-14 01:09:11 -05:00
if ( 0 === $ ( '#menu-to-edit' ) . length ) {
2012-08-22 20:04:18 -04:00
return false ;
}
return this . each ( function ( ) {
var t = $ ( this ) , menuItems = { } ,
2013-11-14 01:09:11 -05:00
checkboxes = ( menus . oneThemeLocationNoMenus && 0 === t . find ( '.tabs-panel-active .categorychecklist li input:checked' ) . length ) ? t . find ( '#page-all li input[type="checkbox"]' ) : t . find ( '.tabs-panel-active .categorychecklist li input:checked' ) ,
2013-11-16 02:42:11 -05:00
re = /menu-item\[([^\]]*)/ ;
2012-08-22 20:04:18 -04:00
processMethod = processMethod || api . addMenuItemToBottom ;
// If no items are checked, bail.
if ( ! checkboxes . length )
return false ;
2020-01-28 19:45:18 -05:00
// Show the Ajax spinner.
2016-01-31 13:06:27 -05:00
t . find ( '.button-controls .spinner' ) . addClass ( 'is-active' ) ;
2012-08-22 20:04:18 -04:00
2020-01-28 19:45:18 -05:00
// Retrieve menu item data.
2012-08-22 20:04:18 -04:00
$ ( checkboxes ) . each ( function ( ) {
var t = $ ( this ) ,
listItemDBIDMatch = re . exec ( t . attr ( 'name' ) ) ,
listItemDBID = 'undefined' == typeof listItemDBIDMatch [ 1 ] ? 0 : parseInt ( listItemDBIDMatch [ 1 ] , 10 ) ;
2013-10-03 05:12:08 -04:00
2012-08-22 20:04:18 -04:00
if ( this . className && - 1 != this . className . indexOf ( 'add-to-top' ) )
processMethod = api . addMenuItemToTop ;
menuItems [ listItemDBID ] = t . closest ( 'li' ) . getItemData ( 'add-menu-item' , listItemDBID ) ;
} ) ;
2020-01-28 19:45:18 -05:00
// Add the items.
2012-08-22 20:04:18 -04:00
api . addItemToMenu ( menuItems , processMethod , function ( ) {
2020-01-28 19:45:18 -05:00
// Deselect the items and hide the Ajax spinner.
2019-09-23 08:42:58 -04:00
checkboxes . prop ( 'checked' , false ) ;
t . find ( '.button-controls .select-all' ) . prop ( 'checked' , false ) ;
2016-01-31 13:06:27 -05:00
t . find ( '.button-controls .spinner' ) . removeClass ( 'is-active' ) ;
2012-08-22 20:04:18 -04:00
} ) ;
} ) ;
} ,
getItemData : function ( itemType , id ) {
itemType = itemType || 'menu-item' ;
var itemData = { } , i ,
fields = [
'menu-item-db-id' ,
'menu-item-object-id' ,
'menu-item-object' ,
'menu-item-parent-id' ,
'menu-item-position' ,
'menu-item-type' ,
'menu-item-title' ,
'menu-item-url' ,
'menu-item-description' ,
'menu-item-attr-title' ,
'menu-item-target' ,
'menu-item-classes' ,
'menu-item-xfn'
] ;
if ( ! id && itemType == 'menu-item' ) {
id = this . find ( '.menu-item-data-db-id' ) . val ( ) ;
}
if ( ! id ) return itemData ;
this . find ( 'input' ) . each ( function ( ) {
var field ;
i = fields . length ;
while ( i -- ) {
if ( itemType == 'menu-item' )
field = fields [ i ] + '[' + id + ']' ;
else if ( itemType == 'add-menu-item' )
field = 'menu-item[' + id + '][' + fields [ i ] + ']' ;
if (
this . name &&
field == this . name
) {
itemData [ fields [ i ] ] = this . value ;
}
}
} ) ;
return itemData ;
} ,
setItemData : function ( itemData , itemType , id ) { // Can take a type, such as 'menu-item', or an id.
itemType = itemType || 'menu-item' ;
if ( ! id && itemType == 'menu-item' ) {
id = $ ( '.menu-item-data-db-id' , this ) . val ( ) ;
}
if ( ! id ) return this ;
this . find ( 'input' ) . each ( function ( ) {
var t = $ ( this ) , field ;
$ . each ( itemData , function ( attr , val ) {
if ( itemType == 'menu-item' )
field = attr + '[' + id + ']' ;
else if ( itemType == 'add-menu-item' )
field = 'menu-item[' + id + '][' + attr + ']' ;
if ( field == t . attr ( 'name' ) ) {
t . val ( val ) ;
}
} ) ;
} ) ;
return this ;
}
} ) ;
} ,
2013-03-16 00:47:19 -04:00
countMenuItems : function ( depth ) {
return $ ( '.menu-item-depth-' + depth ) . length ;
} ,
moveMenuItem : function ( $this , dir ) {
2013-11-14 01:09:11 -05:00
var items , newItemPosition , newDepth ,
menuItems = $ ( '#menu-to-edit li' ) ,
2013-03-16 00:47:19 -04:00
menuItemsCount = menuItems . length ,
thisItem = $this . parents ( 'li.menu-item' ) ,
thisItemChildren = thisItem . childMenuItems ( ) ,
thisItemData = thisItem . getItemData ( ) ,
2013-11-14 01:09:11 -05:00
thisItemDepth = parseInt ( thisItem . menuItemDepth ( ) , 10 ) ,
thisItemPosition = parseInt ( thisItem . index ( ) , 10 ) ,
2013-03-16 00:47:19 -04:00
nextItem = thisItem . next ( ) ,
nextItemChildren = nextItem . childMenuItems ( ) ,
2013-11-14 01:09:11 -05:00
nextItemDepth = parseInt ( nextItem . menuItemDepth ( ) , 10 ) + 1 ,
2013-03-16 00:47:19 -04:00
prevItem = thisItem . prev ( ) ,
2013-11-14 01:09:11 -05:00
prevItemDepth = parseInt ( prevItem . menuItemDepth ( ) , 10 ) ,
2013-03-16 00:47:19 -04:00
prevItemId = prevItem . getItemData ( ) [ 'menu-item-db-id' ] ;
switch ( dir ) {
case 'up' :
2013-11-14 01:09:11 -05:00
newItemPosition = thisItemPosition - 1 ;
2013-03-16 00:47:19 -04:00
2020-01-28 19:45:18 -05:00
// Already at top.
2013-03-16 00:47:19 -04:00
if ( 0 === thisItemPosition )
break ;
2020-01-28 19:45:18 -05:00
// If a sub item is moved to top, shift it to 0 depth.
2013-03-16 00:47:19 -04:00
if ( 0 === newItemPosition && 0 !== thisItemDepth )
thisItem . moveHorizontally ( 0 , thisItemDepth ) ;
2020-01-28 19:45:18 -05:00
// If prev item is sub item, shift to match depth.
2013-03-16 00:47:19 -04:00
if ( 0 !== prevItemDepth )
thisItem . moveHorizontally ( prevItemDepth , thisItemDepth ) ;
// Does this item have sub items?
if ( thisItemChildren ) {
2013-11-14 01:09:11 -05:00
items = thisItem . add ( thisItemChildren ) ;
2020-01-28 19:45:18 -05:00
// Move the entire block.
2013-03-16 00:47:19 -04:00
items . detach ( ) . insertBefore ( menuItems . eq ( newItemPosition ) ) . updateParentMenuItemDBId ( ) ;
} else {
thisItem . detach ( ) . insertBefore ( menuItems . eq ( newItemPosition ) ) . updateParentMenuItemDBId ( ) ;
}
break ;
case 'down' :
// Does this item have sub items?
if ( thisItemChildren ) {
2013-11-14 01:09:11 -05:00
items = thisItem . add ( thisItemChildren ) ,
2013-03-16 00:47:19 -04:00
nextItem = menuItems . eq ( items . length + thisItemPosition ) ,
nextItemChildren = 0 !== nextItem . childMenuItems ( ) . length ;
if ( nextItemChildren ) {
2013-11-14 01:09:11 -05:00
newDepth = parseInt ( nextItem . menuItemDepth ( ) , 10 ) + 1 ;
2013-03-16 00:47:19 -04:00
thisItem . moveHorizontally ( newDepth , thisItemDepth ) ;
}
// Have we reached the bottom?
if ( menuItemsCount === thisItemPosition + items . length )
break ;
items . detach ( ) . insertAfter ( menuItems . eq ( thisItemPosition + items . length ) ) . updateParentMenuItemDBId ( ) ;
} else {
2020-01-28 19:45:18 -05:00
// If next item has sub items, shift depth.
2013-03-16 00:47:19 -04:00
if ( 0 !== nextItemChildren . length )
thisItem . moveHorizontally ( nextItemDepth , thisItemDepth ) ;
2020-01-28 19:45:18 -05:00
// Have we reached the bottom?
2013-03-16 00:47:19 -04:00
if ( menuItemsCount === thisItemPosition + 1 )
break ;
thisItem . detach ( ) . insertAfter ( menuItems . eq ( thisItemPosition + 1 ) ) . updateParentMenuItemDBId ( ) ;
}
break ;
case 'top' :
2020-01-28 19:45:18 -05:00
// Already at top.
2013-03-16 00:47:19 -04:00
if ( 0 === thisItemPosition )
break ;
// Does this item have sub items?
if ( thisItemChildren ) {
2013-11-14 01:09:11 -05:00
items = thisItem . add ( thisItemChildren ) ;
2020-01-28 19:45:18 -05:00
// Move the entire block.
2013-03-16 00:47:19 -04:00
items . detach ( ) . insertBefore ( menuItems . eq ( 0 ) ) . updateParentMenuItemDBId ( ) ;
} else {
thisItem . detach ( ) . insertBefore ( menuItems . eq ( 0 ) ) . updateParentMenuItemDBId ( ) ;
}
break ;
case 'left' :
2020-01-28 19:45:18 -05:00
// As far left as possible.
2013-03-16 00:47:19 -04:00
if ( 0 === thisItemDepth )
break ;
thisItem . shiftHorizontally ( - 1 ) ;
break ;
case 'right' :
2020-01-28 19:45:18 -05:00
// Can't be sub item at top.
2013-03-16 00:47:19 -04:00
if ( 0 === thisItemPosition )
break ;
2020-01-28 19:45:18 -05:00
// Already sub item of prevItem.
2013-03-16 00:47:19 -04:00
if ( thisItemData [ 'menu-item-parent-id' ] === prevItemId )
break ;
thisItem . shiftHorizontally ( 1 ) ;
break ;
}
$this . focus ( ) ;
api . registerChange ( ) ;
api . refreshKeyboardAccessibility ( ) ;
api . refreshAdvancedAccessibility ( ) ;
} ,
2013-02-15 23:53:59 -05:00
initAccessibility : function ( ) {
2013-12-04 12:10:11 -05:00
var menu = $ ( '#menu-to-edit' ) ;
2013-03-16 00:47:19 -04:00
api . refreshKeyboardAccessibility ( ) ;
api . refreshAdvancedAccessibility ( ) ;
2020-01-28 19:45:18 -05:00
// Refresh the accessibility when the user comes close to the item in any way.
2015-03-03 16:14:25 -05:00
menu . on ( 'mouseenter.refreshAccessibility focus.refreshAccessibility touchstart.refreshAccessibility' , '.menu-item' , function ( ) {
2015-07-29 17:30:25 -04:00
api . refreshAdvancedAccessibilityOfItem ( $ ( this ) . find ( 'a.item-edit' ) ) ;
2015-03-03 16:14:25 -05:00
} ) ;
2015-03-03 16:18:25 -05:00
// We have to update on click as well because we might hover first, change the item, and then click.
2015-07-29 17:30:25 -04:00
menu . on ( 'click' , 'a.item-edit' , function ( ) {
2015-03-03 16:14:25 -05:00
api . refreshAdvancedAccessibilityOfItem ( $ ( this ) ) ;
} ) ;
2020-01-28 19:45:18 -05:00
// Links for moving items.
2016-10-10 12:26:12 -04:00
menu . on ( 'click' , '.menus-move' , function ( ) {
2015-03-03 16:14:25 -05:00
var $this = $ ( this ) ,
dir = $this . data ( 'dir' ) ;
if ( 'undefined' !== typeof dir ) {
api . moveMenuItem ( $ ( this ) . parents ( 'li.menu-item' ) . find ( 'a.item-edit' ) , dir ) ;
}
2013-03-16 00:47:19 -04:00
} ) ;
} ,
2015-03-03 16:14:25 -05:00
/ * *
* refreshAdvancedAccessibilityOfItem ( [ itemToRefresh ] )
*
* Refreshes advanced accessibility buttons for one menu item .
* Shows or hides buttons based on the location of the menu item .
*
2020-06-20 08:58:10 -04:00
* @ param { object } itemToRefresh The menu item that might need its advanced accessibility buttons refreshed
2015-03-03 16:14:25 -05:00
* /
refreshAdvancedAccessibilityOfItem : function ( itemToRefresh ) {
2020-01-28 19:45:18 -05:00
// Only refresh accessibility when necessary.
2015-03-03 16:14:25 -05:00
if ( true !== $ ( itemToRefresh ) . data ( 'needs_accessibility_refresh' ) ) {
return ;
}
2013-03-16 00:47:19 -04:00
2015-03-03 16:14:25 -05:00
var thisLink , thisLinkText , primaryItems , itemPosition , title ,
parentItem , parentItemId , parentItemName , subItems ,
$this = $ ( itemToRefresh ) ,
menuItem = $this . closest ( 'li.menu-item' ) . first ( ) ,
depth = menuItem . menuItemDepth ( ) ,
isPrimaryMenuItem = ( 0 === depth ) ,
itemName = $this . closest ( '.menu-item-handle' ) . find ( '.menu-item-title' ) . text ( ) ,
position = parseInt ( menuItem . index ( ) , 10 ) ,
prevItemDepth = ( isPrimaryMenuItem ) ? depth : parseInt ( depth - 1 , 10 ) ,
prevItemNameLeft = menuItem . prevAll ( '.menu-item-depth-' + prevItemDepth ) . first ( ) . find ( '.menu-item-title' ) . text ( ) ,
prevItemNameRight = menuItem . prevAll ( '.menu-item-depth-' + depth ) . first ( ) . find ( '.menu-item-title' ) . text ( ) ,
totalMenuItems = $ ( '#menu-to-edit li' ) . length ,
hasSameDepthSibling = menuItem . nextAll ( '.menu-item-depth-' + depth ) . length ;
2013-03-16 00:47:19 -04:00
2015-07-01 12:44:24 -04:00
menuItem . find ( '.field-move' ) . toggle ( totalMenuItems > 1 ) ;
2015-02-01 00:29:26 -05:00
2015-03-03 16:14:25 -05:00
// Where can they move this menu item?
if ( 0 !== position ) {
thisLink = menuItem . find ( '.menus-move-up' ) ;
2016-01-22 09:26:27 -05:00
thisLink . attr ( 'aria-label' , menus . moveUp ) . css ( 'display' , 'inline' ) ;
2015-03-03 16:14:25 -05:00
}
2013-03-16 00:47:19 -04:00
2015-03-03 16:14:25 -05:00
if ( 0 !== position && isPrimaryMenuItem ) {
thisLink = menuItem . find ( '.menus-move-top' ) ;
2016-01-22 09:26:27 -05:00
thisLink . attr ( 'aria-label' , menus . moveToTop ) . css ( 'display' , 'inline' ) ;
2015-03-03 16:14:25 -05:00
}
2013-03-16 00:47:19 -04:00
2015-03-03 16:14:25 -05:00
if ( position + 1 !== totalMenuItems && 0 !== position ) {
thisLink = menuItem . find ( '.menus-move-down' ) ;
2016-01-22 09:26:27 -05:00
thisLink . attr ( 'aria-label' , menus . moveDown ) . css ( 'display' , 'inline' ) ;
2015-03-03 16:14:25 -05:00
}
2013-03-16 00:47:19 -04:00
2015-03-03 16:14:25 -05:00
if ( 0 === position && 0 !== hasSameDepthSibling ) {
thisLink = menuItem . find ( '.menus-move-down' ) ;
2016-01-22 09:26:27 -05:00
thisLink . attr ( 'aria-label' , menus . moveDown ) . css ( 'display' , 'inline' ) ;
2015-03-03 16:14:25 -05:00
}
2013-03-16 00:47:19 -04:00
2015-03-03 16:14:25 -05:00
if ( ! isPrimaryMenuItem ) {
thisLink = menuItem . find ( '.menus-move-left' ) ,
thisLinkText = menus . outFrom . replace ( '%s' , prevItemNameLeft ) ;
2016-01-22 09:26:27 -05:00
thisLink . attr ( 'aria-label' , menus . moveOutFrom . replace ( '%s' , prevItemNameLeft ) ) . text ( thisLinkText ) . css ( 'display' , 'inline' ) ;
2015-03-03 16:14:25 -05:00
}
2013-03-16 00:47:19 -04:00
2015-03-03 16:14:25 -05:00
if ( 0 !== position ) {
if ( menuItem . find ( '.menu-item-data-parent-id' ) . val ( ) !== menuItem . prev ( ) . find ( '.menu-item-data-db-id' ) . val ( ) ) {
thisLink = menuItem . find ( '.menus-move-right' ) ,
thisLinkText = menus . under . replace ( '%s' , prevItemNameRight ) ;
2016-01-22 09:26:27 -05:00
thisLink . attr ( 'aria-label' , menus . moveUnder . replace ( '%s' , prevItemNameRight ) ) . text ( thisLinkText ) . css ( 'display' , 'inline' ) ;
2013-03-16 00:47:19 -04:00
}
2015-03-03 16:14:25 -05:00
}
2013-03-16 00:47:19 -04:00
2015-03-03 16:14:25 -05:00
if ( isPrimaryMenuItem ) {
primaryItems = $ ( '.menu-item-depth-0' ) ,
itemPosition = primaryItems . index ( menuItem ) + 1 ,
totalMenuItems = primaryItems . length ,
2020-01-28 19:45:18 -05:00
// String together help text for primary menu items.
2015-03-03 16:14:25 -05:00
title = menus . menuFocus . replace ( '%1$s' , itemName ) . replace ( '%2$d' , itemPosition ) . replace ( '%3$d' , totalMenuItems ) ;
} else {
parentItem = menuItem . prevAll ( '.menu-item-depth-' + parseInt ( depth - 1 , 10 ) ) . first ( ) ,
parentItemId = parentItem . find ( '.menu-item-data-db-id' ) . val ( ) ,
parentItemName = parentItem . find ( '.menu-item-title' ) . text ( ) ,
subItems = $ ( '.menu-item .menu-item-data-parent-id[value="' + parentItemId + '"]' ) ,
itemPosition = $ ( subItems . parents ( '.menu-item' ) . get ( ) . reverse ( ) ) . index ( menuItem ) + 1 ;
2020-01-28 19:45:18 -05:00
// String together help text for sub menu items.
2015-03-03 16:14:25 -05:00
title = menus . subMenuFocus . replace ( '%1$s' , itemName ) . replace ( '%2$d' , itemPosition ) . replace ( '%3$s' , parentItemName ) ;
}
2013-03-16 00:47:19 -04:00
2017-08-03 18:12:43 -04:00
$this . attr ( 'aria-label' , title ) ;
2013-03-16 00:47:19 -04:00
2020-01-28 19:45:18 -05:00
// Mark this item's accessibility as refreshed.
2015-03-03 16:14:25 -05:00
$this . data ( 'needs_accessibility_refresh' , false ) ;
} ,
/ * *
* refreshAdvancedAccessibility
*
* Hides all advanced accessibility buttons and marks them for refreshing .
* /
refreshAdvancedAccessibility : function ( ) {
2016-10-10 12:26:12 -04:00
// Hide all the move buttons by default.
$ ( '.menu-item-settings .field-move .menus-move' ) . hide ( ) ;
2015-03-03 16:14:25 -05:00
2020-01-28 19:45:18 -05:00
// Mark all menu items as unprocessed.
2015-07-29 17:30:25 -04:00
$ ( 'a.item-edit' ) . data ( 'needs_accessibility_refresh' , true ) ;
2015-03-03 16:14:25 -05:00
2020-01-28 19:45:18 -05:00
// All open items have to be refreshed or they will show no links.
2015-07-29 17:30:25 -04:00
$ ( '.menu-item-edit-active a.item-edit' ) . each ( function ( ) {
2015-03-03 16:14:25 -05:00
api . refreshAdvancedAccessibilityOfItem ( this ) ;
} ) ;
2013-03-16 00:47:19 -04:00
} ,
refreshKeyboardAccessibility : function ( ) {
2015-07-29 17:30:25 -04:00
$ ( 'a.item-edit' ) . off ( 'focus' ) . on ( 'focus' , function ( ) {
2013-03-16 00:47:19 -04:00
$ ( this ) . off ( 'keydown' ) . on ( 'keydown' , function ( e ) {
2013-02-15 23:53:59 -05:00
2013-11-14 01:09:11 -05:00
var arrows ,
$this = $ ( this ) ,
thisItem = $this . parents ( 'li.menu-item' ) ,
thisItemData = thisItem . getItemData ( ) ;
2013-02-15 23:53:59 -05:00
2020-01-28 19:45:18 -05:00
// Bail if it's not an arrow key.
2013-02-15 23:53:59 -05:00
if ( 37 != e . which && 38 != e . which && 39 != e . which && 40 != e . which )
return ;
2020-01-28 19:45:18 -05:00
// Avoid multiple keydown events.
2013-02-15 23:53:59 -05:00
$this . off ( 'keydown' ) ;
2020-01-28 19:45:18 -05:00
// Bail if there is only one menu item.
2013-03-16 00:47:19 -04:00
if ( 1 === $ ( '#menu-to-edit li' ) . length )
2013-02-15 23:53:59 -05:00
return ;
2020-01-28 19:45:18 -05:00
// If RTL, swap left/right arrows.
2013-11-14 01:09:11 -05:00
arrows = { '38' : 'up' , '40' : 'down' , '37' : 'left' , '39' : 'right' } ;
2013-02-15 23:53:59 -05:00
if ( $ ( 'body' ) . hasClass ( 'rtl' ) )
arrows = { '38' : 'up' , '40' : 'down' , '39' : 'left' , '37' : 'right' } ;
switch ( arrows [ e . which ] ) {
case 'up' :
2013-03-16 00:47:19 -04:00
api . moveMenuItem ( $this , 'up' ) ;
2013-02-15 23:53:59 -05:00
break ;
case 'down' :
2013-03-16 00:47:19 -04:00
api . moveMenuItem ( $this , 'down' ) ;
2013-02-15 23:53:59 -05:00
break ;
case 'left' :
2013-03-16 00:47:19 -04:00
api . moveMenuItem ( $this , 'left' ) ;
2013-02-15 23:53:59 -05:00
break ;
case 'right' :
2013-03-16 00:47:19 -04:00
api . moveMenuItem ( $this , 'right' ) ;
2013-02-15 23:53:59 -05:00
break ;
}
2020-01-28 19:45:18 -05:00
// Put focus back on same menu item.
2013-02-15 23:53:59 -05:00
$ ( '#edit-' + thisItemData [ 'menu-item-db-id' ] ) . focus ( ) ;
return false ;
} ) ;
} ) ;
} ,
2014-06-09 14:39:15 -04:00
initPreviewing : function ( ) {
// Update the item handle title when the navigation label is changed.
2014-09-02 09:23:18 -04:00
$ ( '#menu-to-edit' ) . on ( 'change input' , '.edit-menu-item-title' , function ( e ) {
2014-06-09 14:39:15 -04:00
var input = $ ( e . currentTarget ) , title , titleEl ;
title = input . val ( ) ;
titleEl = input . closest ( '.menu-item' ) . find ( '.menu-item-title' ) ;
// Don't update to empty title.
if ( title ) {
titleEl . text ( title ) . removeClass ( 'no-title' ) ;
} else {
titleEl . text ( navMenuL10n . untitled ) . addClass ( 'no-title' ) ;
}
} ) ;
} ,
2012-08-22 20:04:18 -04:00
initToggles : function ( ) {
2020-01-28 19:45:18 -05:00
// Init postboxes.
2012-08-22 20:04:18 -04:00
postboxes . add _postbox _toggles ( 'nav-menus' ) ;
2020-01-28 19:45:18 -05:00
// Adjust columns functions for menus UI.
2012-08-22 20:04:18 -04:00
columns . useCheckboxesForHidden ( ) ;
columns . checked = function ( field ) {
$ ( '.field-' + field ) . removeClass ( 'hidden-field' ) ;
2013-11-14 01:09:11 -05:00
} ;
2012-08-22 20:04:18 -04:00
columns . unchecked = function ( field ) {
$ ( '.field-' + field ) . addClass ( 'hidden-field' ) ;
2013-11-14 01:09:11 -05:00
} ;
2020-01-28 19:45:18 -05:00
// Hide fields.
2012-08-22 20:04:18 -04:00
api . menuList . hideAdvancedMenuItemFields ( ) ;
2013-03-15 09:16:38 -04:00
$ ( '.hide-postbox-tog' ) . click ( function ( ) {
var hidden = $ ( '.accordion-container li.accordion-section' ) . filter ( ':hidden' ) . map ( function ( ) { return this . id ; } ) . get ( ) . join ( ',' ) ;
$ . post ( ajaxurl , {
action : 'closed-postboxes' ,
hidden : hidden ,
closedpostboxesnonce : jQuery ( '#closedpostboxesnonce' ) . val ( ) ,
page : 'nav-menus'
} ) ;
} ) ;
2012-08-22 20:04:18 -04:00
} ,
initSortables : function ( ) {
var currentDepth = 0 , originalDepth , minDepth , maxDepth ,
prev , next , prevBottom , nextThreshold , helperHeight , transport ,
menuEdge = api . menuList . offset ( ) . left ,
body = $ ( 'body' ) , maxChildDepth ,
menuMaxDepth = initialMenuMaxDepth ( ) ;
2013-11-14 01:09:11 -05:00
if ( 0 !== $ ( '#menu-to-edit li' ) . length )
2013-02-15 23:53:59 -05:00
$ ( '.drag-instructions' ) . show ( ) ;
2012-08-22 20:04:18 -04:00
// Use the right edge if RTL.
menuEdge += api . isRTL ? api . menuList . width ( ) : 0 ;
api . menuList . sortable ( {
handle : '.menu-item-handle' ,
placeholder : 'sortable-placeholder' ,
2015-06-13 10:03:25 -04:00
items : api . options . sortableItems ,
2012-08-22 20:04:18 -04:00
start : function ( e , ui ) {
var height , width , parent , children , tempHolder ;
2020-01-28 19:45:18 -05:00
// Handle placement for RTL orientation.
2012-08-22 20:04:18 -04:00
if ( api . isRTL )
ui . item [ 0 ] . style . right = 'auto' ;
transport = ui . item . children ( '.menu-item-transport' ) ;
// Set depths. currentDepth must be set before children are located.
originalDepth = ui . item . menuItemDepth ( ) ;
updateCurrentDepth ( ui , originalDepth ) ;
2020-01-28 19:45:18 -05:00
// Attach child elements to parent.
// Skip the placeholder.
2012-08-22 20:04:18 -04:00
parent = ( ui . item . next ( ) [ 0 ] == ui . placeholder [ 0 ] ) ? ui . item . next ( ) : ui . item ;
children = parent . childMenuItems ( ) ;
transport . append ( children ) ;
// Update the height of the placeholder to match the moving item.
height = transport . outerHeight ( ) ;
2020-01-28 19:45:18 -05:00
// If there are children, account for distance between top of children and parent.
2012-08-22 20:04:18 -04:00
height += ( height > 0 ) ? ( ui . placeholder . css ( 'margin-top' ) . slice ( 0 , - 2 ) * 1 ) : 0 ;
height += ui . helper . outerHeight ( ) ;
helperHeight = height ;
2020-01-28 19:45:18 -05:00
height -= 2 ; // Subtract 2 for borders.
2012-08-22 20:04:18 -04:00
ui . placeholder . height ( height ) ;
// Update the width of the placeholder to match the moving item.
maxChildDepth = originalDepth ;
children . each ( function ( ) {
var depth = $ ( this ) . menuItemDepth ( ) ;
maxChildDepth = ( depth > maxChildDepth ) ? depth : maxChildDepth ;
} ) ;
2020-01-28 19:45:18 -05:00
width = ui . helper . find ( '.menu-item-handle' ) . outerWidth ( ) ; // Get original width.
width += api . depthToPx ( maxChildDepth - originalDepth ) ; // Account for children.
width -= 2 ; // Subtract 2 for borders.
2012-08-22 20:04:18 -04:00
ui . placeholder . width ( width ) ;
// Update the list of menu items.
2015-06-13 10:03:25 -04:00
tempHolder = ui . placeholder . next ( '.menu-item' ) ;
2020-01-28 19:45:18 -05:00
tempHolder . css ( 'margin-top' , helperHeight + 'px' ) ; // Set the margin to absorb the placeholder.
ui . placeholder . detach ( ) ; // Detach or jQuery UI will think the placeholder is a menu item.
$ ( this ) . sortable ( 'refresh' ) ; // The children aren't sortable. We should let jQuery UI know.
ui . item . after ( ui . placeholder ) ; // Reattach the placeholder.
tempHolder . css ( 'margin-top' , 0 ) ; // Reset the margin.
2012-08-22 20:04:18 -04:00
// Now that the element is complete, we can update...
updateSharedVars ( ui ) ;
} ,
stop : function ( e , ui ) {
2013-11-14 01:09:11 -05:00
var children , subMenuTitle ,
depthChange = currentDepth - originalDepth ;
2012-08-22 20:04:18 -04:00
2020-01-28 19:45:18 -05:00
// Return child elements to the list.
2012-08-22 20:04:18 -04:00
children = transport . children ( ) . insertAfter ( ui . item ) ;
2020-01-28 19:45:18 -05:00
// Add "sub menu" description.
2013-11-14 01:09:11 -05:00
subMenuTitle = ui . item . find ( '.item-title .is-submenu' ) ;
2013-02-15 23:53:59 -05:00
if ( 0 < currentDepth )
subMenuTitle . show ( ) ;
else
subMenuTitle . hide ( ) ;
2020-01-28 19:45:18 -05:00
// Update depth classes.
2013-11-14 01:09:11 -05:00
if ( 0 !== depthChange ) {
2012-08-22 20:04:18 -04:00
ui . item . updateDepthClass ( currentDepth ) ;
children . shiftDepthClass ( depthChange ) ;
updateMenuMaxDepth ( depthChange ) ;
}
2020-01-28 19:45:18 -05:00
// Register a change.
2012-08-22 20:04:18 -04:00
api . registerChange ( ) ;
// Update the item data.
ui . item . updateParentMenuItemDBId ( ) ;
2020-01-28 19:45:18 -05:00
// Address sortable's incorrectly-calculated top in Opera.
2012-08-22 20:04:18 -04:00
ui . item [ 0 ] . style . top = 0 ;
2020-01-28 19:45:18 -05:00
// Handle drop placement for rtl orientation.
2012-08-22 20:04:18 -04:00
if ( api . isRTL ) {
ui . item [ 0 ] . style . left = 'auto' ;
ui . item [ 0 ] . style . right = 0 ;
}
2013-03-16 00:47:19 -04:00
api . refreshKeyboardAccessibility ( ) ;
api . refreshAdvancedAccessibility ( ) ;
2012-08-22 20:04:18 -04:00
} ,
change : function ( e , ui ) {
// Make sure the placeholder is inside the menu.
// Otherwise fix it, or we're in trouble.
if ( ! ui . placeholder . parent ( ) . hasClass ( 'menu' ) )
( prev . length ) ? prev . after ( ui . placeholder ) : api . menuList . prepend ( ui . placeholder ) ;
updateSharedVars ( ui ) ;
} ,
sort : function ( e , ui ) {
var offset = ui . helper . offset ( ) ,
edge = api . isRTL ? offset . left + ui . helper . width ( ) : offset . left ,
depth = api . negateIfRTL * api . pxToDepth ( edge - menuEdge ) ;
2015-07-01 12:44:24 -04:00
2020-01-28 19:45:18 -05:00
/ *
* Check and correct if depth is not within range .
* Also , if the dragged element is dragged upwards over an item ,
* shift the placeholder to a child position .
* /
2015-07-01 12:44:24 -04:00
if ( depth > maxDepth || offset . top < ( prevBottom - api . options . targetTolerance ) ) {
depth = maxDepth ;
} else if ( depth < minDepth ) {
depth = minDepth ;
}
2012-08-22 20:04:18 -04:00
if ( depth != currentDepth )
updateCurrentDepth ( ui , depth ) ;
2020-01-28 19:45:18 -05:00
// If we overlap the next element, manually shift downwards.
2012-08-22 20:04:18 -04:00
if ( nextThreshold && offset . top + helperHeight > nextThreshold ) {
next . after ( ui . placeholder ) ;
updateSharedVars ( ui ) ;
2013-11-14 01:09:11 -05:00
$ ( this ) . sortable ( 'refreshPositions' ) ;
2012-08-22 20:04:18 -04:00
}
}
} ) ;
function updateSharedVars ( ui ) {
var depth ;
2015-06-13 10:03:25 -04:00
prev = ui . placeholder . prev ( '.menu-item' ) ;
next = ui . placeholder . next ( '.menu-item' ) ;
2012-08-22 20:04:18 -04:00
// Make sure we don't select the moving item.
2015-06-13 10:03:25 -04:00
if ( prev [ 0 ] == ui . item [ 0 ] ) prev = prev . prev ( '.menu-item' ) ;
if ( next [ 0 ] == ui . item [ 0 ] ) next = next . next ( '.menu-item' ) ;
2012-08-22 20:04:18 -04:00
prevBottom = ( prev . length ) ? prev . offset ( ) . top + prev . height ( ) : 0 ;
nextThreshold = ( next . length ) ? next . offset ( ) . top + next . height ( ) / 3 : 0 ;
minDepth = ( next . length ) ? next . menuItemDepth ( ) : 0 ;
if ( prev . length )
maxDepth = ( ( depth = prev . menuItemDepth ( ) + 1 ) > api . options . globalMaxDepth ) ? api . options . globalMaxDepth : depth ;
else
maxDepth = 0 ;
}
function updateCurrentDepth ( ui , depth ) {
ui . placeholder . updateDepthClass ( depth , currentDepth ) ;
currentDepth = depth ;
}
function initialMenuMaxDepth ( ) {
if ( ! body [ 0 ] . className ) return 0 ;
var match = body [ 0 ] . className . match ( /menu-max-depth-(\d+)/ ) ;
2013-11-14 01:09:11 -05:00
return match && match [ 1 ] ? parseInt ( match [ 1 ] , 10 ) : 0 ;
2012-08-22 20:04:18 -04:00
}
function updateMenuMaxDepth ( depthChange ) {
var depth , newDepth = menuMaxDepth ;
if ( depthChange === 0 ) {
return ;
} else if ( depthChange > 0 ) {
depth = maxChildDepth + depthChange ;
if ( depth > menuMaxDepth )
newDepth = depth ;
} else if ( depthChange < 0 && maxChildDepth == menuMaxDepth ) {
while ( ! $ ( '.menu-item-depth-' + newDepth , api . menuList ) . length && newDepth > 0 )
newDepth -- ;
}
// Update the depth class.
body . removeClass ( 'menu-max-depth-' + menuMaxDepth ) . addClass ( 'menu-max-depth-' + newDepth ) ;
menuMaxDepth = newDepth ;
}
} ,
2013-03-27 07:46:08 -04:00
initManageLocations : function ( ) {
$ ( '#menu-locations-wrap form' ) . submit ( function ( ) {
window . onbeforeunload = null ;
} ) ;
$ ( '.menu-location-menus select' ) . on ( 'change' , function ( ) {
var editLink = $ ( this ) . closest ( 'tr' ) . find ( '.locations-edit-menu-link' ) ;
if ( $ ( this ) . find ( 'option:selected' ) . data ( 'orig' ) )
editLink . show ( ) ;
else
editLink . hide ( ) ;
} ) ;
} ,
2012-08-22 20:04:18 -04:00
attachMenuEditListeners : function ( ) {
var that = this ;
$ ( '#update-nav-menu' ) . bind ( 'click' , function ( e ) {
if ( e . target && e . target . className ) {
if ( - 1 != e . target . className . indexOf ( 'item-edit' ) ) {
return that . eventOnClickEditLink ( e . target ) ;
} else if ( - 1 != e . target . className . indexOf ( 'menu-save' ) ) {
return that . eventOnClickMenuSave ( e . target ) ;
} else if ( - 1 != e . target . className . indexOf ( 'menu-delete' ) ) {
return that . eventOnClickMenuDelete ( e . target ) ;
} else if ( - 1 != e . target . className . indexOf ( 'item-delete' ) ) {
return that . eventOnClickMenuItemDelete ( e . target ) ;
} else if ( - 1 != e . target . className . indexOf ( 'item-cancel' ) ) {
return that . eventOnClickCancelLink ( e . target ) ;
}
}
} ) ;
2019-01-21 17:10:49 -05:00
$ ( '#menu-name' ) . on ( 'input' , _ . debounce ( function ( ) {
var menuName = $ ( document . getElementById ( 'menu-name' ) ) ,
menuNameVal = menuName . val ( ) ;
if ( ! menuNameVal || ! menuNameVal . replace ( /\s+/ , '' ) ) {
// Add warning for invalid menu name.
menuName . parent ( ) . addClass ( 'form-invalid' ) ;
} else {
// Remove warning for valid menu name.
menuName . parent ( ) . removeClass ( 'form-invalid' ) ;
}
} , 500 ) ) ;
2012-08-22 20:04:18 -04:00
$ ( '#add-custom-links input[type="text"]' ) . keypress ( function ( e ) {
2015-06-04 00:31:26 -04:00
$ ( '#customlinkdiv' ) . removeClass ( 'form-invalid' ) ;
2012-08-22 20:04:18 -04:00
if ( e . keyCode === 13 ) {
e . preventDefault ( ) ;
2013-11-14 01:09:11 -05:00
$ ( '#submit-customlinkdiv' ) . click ( ) ;
2012-08-22 20:04:18 -04:00
}
} ) ;
} ,
2016-02-11 14:08:27 -05:00
attachMenuSaveSubmitListeners : function ( ) {
/ *
* When a navigation menu is saved , store a JSON representation of all form data
* in a single input to avoid PHP ` max_input_vars ` limitations . See # 14134.
* /
2016-03-10 18:18:26 -05:00
$ ( '#update-nav-menu' ) . submit ( function ( ) {
var navMenuData = $ ( '#update-nav-menu' ) . serializeArray ( ) ;
$ ( '[name="nav-menu-data"]' ) . val ( JSON . stringify ( navMenuData ) ) ;
2016-02-11 14:08:27 -05:00
} ) ;
} ,
2012-08-22 20:04:18 -04:00
attachThemeLocationsListeners : function ( ) {
var loc = $ ( '#nav-menu-theme-locations' ) , params = { } ;
2013-11-14 01:09:11 -05:00
params . action = 'menu-locations-save' ;
2012-08-22 20:04:18 -04:00
params [ 'menu-settings-column-nonce' ] = $ ( '#menu-settings-column-nonce' ) . val ( ) ;
loc . find ( 'input[type="submit"]' ) . click ( function ( ) {
loc . find ( 'select' ) . each ( function ( ) {
params [ this . name ] = $ ( this ) . val ( ) ;
} ) ;
2015-04-03 00:52:27 -04:00
loc . find ( '.spinner' ) . addClass ( 'is-active' ) ;
2013-11-14 01:09:11 -05:00
$ . post ( ajaxurl , params , function ( ) {
2015-04-03 00:52:27 -04:00
loc . find ( '.spinner' ) . removeClass ( 'is-active' ) ;
2012-08-22 20:04:18 -04:00
} ) ;
return false ;
} ) ;
} ,
attachQuickSearchListeners : function ( ) {
2019-01-09 21:57:50 -05:00
var searchTimer ;
2016-01-22 09:26:27 -05:00
2016-01-31 09:03:27 -05:00
// Prevent form submission.
$ ( '#nav-menu-meta' ) . on ( 'submit' , function ( event ) {
event . preventDefault ( ) ;
} ) ;
2019-01-09 21:57:50 -05:00
$ ( '#nav-menu-meta' ) . on ( 'input' , '.quick-search' , function ( ) {
2016-10-16 16:03:29 -04:00
var $this = $ ( this ) ;
2012-08-22 20:04:18 -04:00
2016-10-16 16:03:29 -04:00
$this . attr ( 'autocomplete' , 'off' ) ;
2012-08-22 20:04:18 -04:00
2016-10-16 16:03:29 -04:00
if ( searchTimer ) {
clearTimeout ( searchTimer ) ;
}
searchTimer = setTimeout ( function ( ) {
api . updateQuickSearchResults ( $this ) ;
} , 500 ) ;
} ) . on ( 'blur' , '.quick-search' , function ( ) {
2016-01-22 09:26:27 -05:00
api . lastSearch = '' ;
2016-10-16 16:03:29 -04:00
} ) ;
2012-08-22 20:04:18 -04:00
} ,
updateQuickSearchResults : function ( input ) {
var panel , params ,
2016-01-22 09:26:27 -05:00
minSearchLength = 2 ,
q = input . val ( ) ;
/ *
2020-06-25 08:43:07 -04:00
* Minimum characters for a search . Also avoid a new Ajax search when
2016-01-22 09:26:27 -05:00
* the pressed key ( e . g . arrows ) doesn ' t change the searched term .
* /
if ( q . length < minSearchLength || api . lastSearch == q ) {
return ;
}
2012-08-22 20:04:18 -04:00
2016-01-22 09:26:27 -05:00
api . lastSearch = q ;
2012-08-22 20:04:18 -04:00
panel = input . parents ( '.tabs-panel' ) ;
params = {
'action' : 'menu-quick-search' ,
'response-format' : 'markup' ,
'menu' : $ ( '#menu' ) . val ( ) ,
'menu-settings-column-nonce' : $ ( '#menu-settings-column-nonce' ) . val ( ) ,
'q' : q ,
'type' : input . attr ( 'name' )
} ;
2015-04-03 00:52:27 -04:00
$ ( '.spinner' , panel ) . addClass ( 'is-active' ) ;
2012-08-22 20:04:18 -04:00
$ . post ( ajaxurl , params , function ( menuMarkup ) {
api . processQuickSearchQueryResponse ( menuMarkup , params , panel ) ;
} ) ;
} ,
addCustomLink : function ( processMethod ) {
2019-08-20 20:27:57 -04:00
var url = $ ( '#custom-menu-item-url' ) . val ( ) . trim ( ) ,
2012-08-22 20:04:18 -04:00
label = $ ( '#custom-menu-item-name' ) . val ( ) ;
processMethod = processMethod || api . addMenuItemToBottom ;
2019-09-26 16:32:55 -04:00
if ( '' === url || 'https://' == url || 'http://' == url ) {
2015-06-04 00:31:26 -04:00
$ ( '#customlinkdiv' ) . addClass ( 'form-invalid' ) ;
2012-08-22 20:04:18 -04:00
return false ;
2015-06-04 00:31:26 -04:00
}
2012-08-22 20:04:18 -04:00
2020-01-28 19:45:18 -05:00
// Show the Ajax spinner.
2015-04-03 00:52:27 -04:00
$ ( '.customlinkdiv .spinner' ) . addClass ( 'is-active' ) ;
2012-08-22 20:04:18 -04:00
this . addLinkToMenu ( url , label , processMethod , function ( ) {
2020-01-28 19:45:18 -05:00
// Remove the Ajax spinner.
2015-04-03 00:52:27 -04:00
$ ( '.customlinkdiv .spinner' ) . removeClass ( 'is-active' ) ;
2020-01-28 19:45:18 -05:00
// Set custom link form back to defaults.
2012-08-22 20:04:18 -04:00
$ ( '#custom-menu-item-name' ) . val ( '' ) . blur ( ) ;
2019-09-26 16:32:55 -04:00
$ ( '#custom-menu-item-url' ) . val ( '' ) . attr ( 'placeholder' , 'https://' ) ;
2012-08-22 20:04:18 -04:00
} ) ;
} ,
addLinkToMenu : function ( url , label , processMethod , callback ) {
processMethod = processMethod || api . addMenuItemToBottom ;
callback = callback || function ( ) { } ;
api . addItemToMenu ( {
'-1' : {
'menu-item-type' : 'custom' ,
'menu-item-url' : url ,
'menu-item-title' : label
}
} , processMethod , callback ) ;
} ,
addItemToMenu : function ( menuItem , processMethod , callback ) {
var menu = $ ( '#menu' ) . val ( ) ,
2013-11-14 01:09:11 -05:00
nonce = $ ( '#menu-settings-column-nonce' ) . val ( ) ,
params ;
2012-08-22 20:04:18 -04:00
processMethod = processMethod || function ( ) { } ;
callback = callback || function ( ) { } ;
params = {
'action' : 'add-menu-item' ,
'menu' : menu ,
'menu-settings-column-nonce' : nonce ,
'menu-item' : menuItem
} ;
$ . post ( ajaxurl , params , function ( menuMarkup ) {
var ins = $ ( '#menu-instructions' ) ;
2013-09-21 06:51:09 -04:00
2020-01-28 19:45:18 -05:00
menuMarkup = $ . trim ( menuMarkup ) ; // Trim leading whitespaces.
2012-08-22 20:04:18 -04:00
processMethod ( menuMarkup , params ) ;
2013-09-21 06:51:09 -04:00
2020-01-28 19:45:18 -05:00
// Make it stand out a bit more visually, by adding a fadeIn.
2013-02-15 23:53:59 -05:00
$ ( 'li.pending' ) . hide ( ) . fadeIn ( 'slow' ) ;
$ ( '.drag-instructions' ) . show ( ) ;
if ( ! ins . hasClass ( 'menu-instructions-inactive' ) && ins . siblings ( ) . length )
ins . addClass ( 'menu-instructions-inactive' ) ;
2013-09-21 06:51:09 -04:00
2012-08-22 20:04:18 -04:00
callback ( ) ;
} ) ;
} ,
/ * *
2017-01-20 11:15:42 -05:00
* Process the add menu item request response into menu list item . Appends to menu .
2012-08-22 20:04:18 -04:00
*
2017-01-20 11:15:42 -05:00
* @ param { string } menuMarkup The text server response of menu item markup .
*
* @ fires document # menu - item - added Passes menuMarkup as a jQuery object .
2012-08-22 20:04:18 -04:00
* /
2013-11-14 01:09:11 -05:00
addMenuItemToBottom : function ( menuMarkup ) {
2017-01-20 11:15:42 -05:00
var $menuMarkup = $ ( menuMarkup ) ;
$menuMarkup . hideAdvancedMenuItemFields ( ) . appendTo ( api . targetList ) ;
2013-03-16 00:47:19 -04:00
api . refreshKeyboardAccessibility ( ) ;
api . refreshAdvancedAccessibility ( ) ;
2017-01-20 11:15:42 -05:00
$ ( document ) . trigger ( 'menu-item-added' , [ $menuMarkup ] ) ;
2012-08-22 20:04:18 -04:00
} ,
2017-01-20 11:15:42 -05:00
/ * *
* Process the add menu item request response into menu list item . Prepends to menu .
*
* @ param { string } menuMarkup The text server response of menu item markup .
*
* @ fires document # menu - item - added Passes menuMarkup as a jQuery object .
* /
2013-11-14 01:09:11 -05:00
addMenuItemToTop : function ( menuMarkup ) {
2017-01-20 11:15:42 -05:00
var $menuMarkup = $ ( menuMarkup ) ;
$menuMarkup . hideAdvancedMenuItemFields ( ) . prependTo ( api . targetList ) ;
2013-03-16 00:47:19 -04:00
api . refreshKeyboardAccessibility ( ) ;
api . refreshAdvancedAccessibility ( ) ;
2017-01-20 11:15:42 -05:00
$ ( document ) . trigger ( 'menu-item-added' , [ $menuMarkup ] ) ;
2012-08-22 20:04:18 -04:00
} ,
attachUnsavedChangesListener : function ( ) {
2013-03-27 07:46:08 -04:00
$ ( '#menu-management input, #menu-management select, #menu-management, #menu-management textarea, .menu-location-menus select' ) . change ( function ( ) {
2012-08-22 20:04:18 -04:00
api . registerChange ( ) ;
} ) ;
2013-11-14 01:09:11 -05:00
if ( 0 !== $ ( '#menu-to-edit' ) . length || 0 !== $ ( '.menu-location-menus select' ) . length ) {
2012-08-22 20:04:18 -04:00
window . onbeforeunload = function ( ) {
if ( api . menusChanged )
return navMenuL10n . saveAlert ;
} ;
} else {
2020-01-28 19:45:18 -05:00
// Make the post boxes read-only, as they can't be used yet.
2013-02-15 23:53:59 -05:00
$ ( '#menu-settings-column' ) . find ( 'input,select' ) . end ( ) . find ( 'a' ) . attr ( 'href' , '#' ) . unbind ( 'click' ) ;
2012-08-22 20:04:18 -04:00
}
} ,
registerChange : function ( ) {
api . menusChanged = true ;
} ,
attachTabsPanelListeners : function ( ) {
$ ( '#menu-settings-column' ) . bind ( 'click' , function ( e ) {
2019-09-23 08:42:58 -04:00
var selectAreaMatch , selectAll , panelId , wrapper , items ,
2012-08-22 20:04:18 -04:00
target = $ ( e . target ) ;
if ( target . hasClass ( 'nav-tab-link' ) ) {
2013-03-15 09:16:38 -04:00
panelId = target . data ( 'type' ) ;
wrapper = target . parents ( '.accordion-section-content' ) . first ( ) ;
2012-08-22 20:04:18 -04:00
2020-01-28 19:45:18 -05:00
// Upon changing tabs, we want to uncheck all checkboxes.
2019-09-23 08:42:58 -04:00
$ ( 'input' , wrapper ) . prop ( 'checked' , false ) ;
2012-08-22 20:04:18 -04:00
$ ( '.tabs-panel-active' , wrapper ) . removeClass ( 'tabs-panel-active' ) . addClass ( 'tabs-panel-inactive' ) ;
$ ( '#' + panelId , wrapper ) . removeClass ( 'tabs-panel-inactive' ) . addClass ( 'tabs-panel-active' ) ;
$ ( '.tabs' , wrapper ) . removeClass ( 'tabs' ) ;
target . parent ( ) . addClass ( 'tabs' ) ;
2020-01-28 19:45:18 -05:00
// Select the search bar.
2012-08-22 20:04:18 -04:00
$ ( '.quick-search' , wrapper ) . focus ( ) ;
2016-10-07 16:56:29 -04:00
// Hide controls in the search tab if no items found.
if ( ! wrapper . find ( '.tabs-panel-active .menu-item-title' ) . length ) {
wrapper . addClass ( 'has-no-menu-item' ) ;
} else {
wrapper . removeClass ( 'has-no-menu-item' ) ;
}
2013-03-15 09:16:38 -04:00
e . preventDefault ( ) ;
2019-09-23 08:42:58 -04:00
} else if ( target . hasClass ( 'select-all' ) ) {
selectAreaMatch = target . closest ( '.button-controls' ) . data ( 'items-type' ) ;
if ( selectAreaMatch ) {
items = $ ( '#' + selectAreaMatch + ' .tabs-panel-active .menu-item-title input' ) ;
if ( items . length === items . filter ( ':checked' ) . length && ! target . is ( ':checked' ) ) {
items . prop ( 'checked' , false ) ;
} else if ( target . is ( ':checked' ) ) {
items . prop ( 'checked' , true ) ;
}
}
} else if ( target . hasClass ( 'menu-item-checkbox' ) ) {
selectAreaMatch = target . closest ( '.tabs-panel-active' ) . parent ( ) . attr ( 'id' ) ;
if ( selectAreaMatch ) {
items = $ ( '#' + selectAreaMatch + ' .tabs-panel-active .menu-item-title input' ) ;
selectAll = $ ( '.button-controls[data-items-type="' + selectAreaMatch + '"] .select-all' ) ;
if ( items . length === items . filter ( ':checked' ) . length && ! selectAll . is ( ':checked' ) ) {
selectAll . prop ( 'checked' , true ) ;
} else if ( selectAll . is ( ':checked' ) ) {
selectAll . prop ( 'checked' , false ) ;
}
2012-08-22 20:04:18 -04:00
}
} else if ( target . hasClass ( 'submit-add-to-menu' ) ) {
api . registerChange ( ) ;
if ( e . target . id && 'submit-customlinkdiv' == e . target . id )
api . addCustomLink ( api . addMenuItemToBottom ) ;
else if ( e . target . id && - 1 != e . target . id . indexOf ( 'submit-' ) )
$ ( '#' + e . target . id . replace ( /submit-/ , '' ) ) . addSelectedToMenu ( api . addMenuItemToBottom ) ;
return false ;
2016-10-27 11:23:31 -04:00
}
} ) ;
2012-08-22 20:04:18 -04:00
2016-10-27 11:23:31 -04:00
/ *
* Delegate the ` click ` event and attach it just to the pagination
* links thus excluding the current page ` <span> ` . See ticket # 35577.
* /
$ ( '#nav-menu-meta' ) . on ( 'click' , 'a.page-numbers' , function ( ) {
var $container = $ ( this ) . closest ( '.inside' ) ;
2012-08-22 20:04:18 -04:00
2016-10-27 11:23:31 -04:00
$ . post ( ajaxurl , this . href . replace ( /.*\?/ , '' ) . replace ( /action=([^&]*)/ , '' ) + '&action=menu-get-metabox' ,
function ( resp ) {
var metaBoxData = $ . parseJSON ( resp ) ,
toReplace ;
2012-08-22 20:04:18 -04:00
2016-10-27 11:23:31 -04:00
if ( - 1 === resp . indexOf ( 'replace-id' ) ) {
return ;
}
2012-08-22 20:04:18 -04:00
2016-10-27 11:23:31 -04:00
// Get the post type menu meta box to update.
toReplace = document . getElementById ( metaBoxData [ 'replace-id' ] ) ;
2012-08-22 20:04:18 -04:00
2016-10-27 11:23:31 -04:00
if ( ! metaBoxData . markup || ! toReplace ) {
return ;
2012-08-22 20:04:18 -04:00
}
2016-10-27 11:23:31 -04:00
// Update the post type menu meta box with new content from the response.
$container . html ( metaBoxData . markup ) ;
}
) ;
return false ;
2012-08-22 20:04:18 -04:00
} ) ;
} ,
eventOnClickEditLink : function ( clickedEl ) {
var settings , item ,
matchedSection = /#(.*)$/ . exec ( clickedEl . href ) ;
2020-02-16 01:27:08 -05:00
2012-08-22 20:04:18 -04:00
if ( matchedSection && matchedSection [ 1 ] ) {
settings = $ ( '#' + matchedSection [ 1 ] ) ;
item = settings . parent ( ) ;
2013-11-14 01:09:11 -05:00
if ( 0 !== item . length ) {
2012-08-22 20:04:18 -04:00
if ( item . hasClass ( 'menu-item-edit-inactive' ) ) {
if ( ! settings . data ( 'menu-item-data' ) ) {
settings . data ( 'menu-item-data' , settings . getItemData ( ) ) ;
}
settings . slideDown ( 'fast' ) ;
item . removeClass ( 'menu-item-edit-inactive' )
. addClass ( 'menu-item-edit-active' ) ;
} else {
settings . slideUp ( 'fast' ) ;
item . removeClass ( 'menu-item-edit-active' )
. addClass ( 'menu-item-edit-inactive' ) ;
}
return false ;
}
}
} ,
eventOnClickCancelLink : function ( clickedEl ) {
2013-02-15 23:53:59 -05:00
var settings = $ ( clickedEl ) . closest ( '.menu-item-settings' ) ,
thisMenuItem = $ ( clickedEl ) . closest ( '.menu-item' ) ;
2020-02-16 01:27:08 -05:00
thisMenuItem . removeClass ( 'menu-item-edit-active' ) . addClass ( 'menu-item-edit-inactive' ) ;
settings . setItemData ( settings . data ( 'menu-item-data' ) ) . hide ( ) ;
// Restore the title of the currently active/expanded menu item.
thisMenuItem . find ( '.menu-item-title' ) . text ( settings . data ( 'menu-item-data' ) [ 'menu-item-title' ] ) ;
2012-08-22 20:04:18 -04:00
return false ;
} ,
2013-11-14 01:09:11 -05:00
eventOnClickMenuSave : function ( ) {
2012-08-22 20:04:18 -04:00
var locs = '' ,
menuName = $ ( '#menu-name' ) ,
menuNameVal = menuName . val ( ) ;
2020-02-16 01:27:08 -05:00
2020-01-28 19:45:18 -05:00
// Cancel and warn if invalid menu name.
2019-01-21 17:10:49 -05:00
if ( ! menuNameVal || ! menuNameVal . replace ( /\s+/ , '' ) ) {
menuName . parent ( ) . addClass ( 'form-invalid' ) ;
2012-08-22 20:04:18 -04:00
return false ;
}
2020-01-28 19:45:18 -05:00
// Copy menu theme locations.
2012-08-22 20:04:18 -04:00
$ ( '#nav-menu-theme-locations select' ) . each ( function ( ) {
locs += '<input type="hidden" name="' + this . name + '" value="' + $ ( this ) . val ( ) + '" />' ;
} ) ;
$ ( '#update-nav-menu' ) . append ( locs ) ;
2020-01-28 19:45:18 -05:00
// Update menu item position data.
2012-08-22 20:04:18 -04:00
api . menuList . find ( '.menu-item-data-position' ) . val ( function ( index ) { return index + 1 ; } ) ;
window . onbeforeunload = null ;
return true ;
} ,
2013-11-14 01:09:11 -05:00
eventOnClickMenuDelete : function ( ) {
2020-01-28 19:45:18 -05:00
// Delete warning AYS.
2013-11-14 01:09:11 -05:00
if ( window . confirm ( navMenuL10n . warnDeleteMenu ) ) {
2012-08-22 20:04:18 -04:00
window . onbeforeunload = null ;
return true ;
}
return false ;
} ,
eventOnClickMenuItemDelete : function ( clickedEl ) {
var itemID = parseInt ( clickedEl . id . replace ( 'delete-' , '' ) , 10 ) ;
2020-02-16 01:27:08 -05:00
2012-08-22 20:04:18 -04:00
api . removeMenuItem ( $ ( '#menu-item-' + itemID ) ) ;
api . registerChange ( ) ;
return false ;
} ,
/ * *
* Process the quick search response into a search result
*
* @ param string resp The server response to the query .
* @ param object req The request arguments .
* @ param jQuery panel The tabs panel we ' re searching in .
* /
processQuickSearchQueryResponse : function ( resp , req , panel ) {
var matched , newID ,
takenIDs = { } ,
form = document . getElementById ( 'nav-menu-meta' ) ,
2013-11-14 01:09:11 -05:00
pattern = /menu-item[(\[^]\]*/ ,
2012-08-22 20:04:18 -04:00
$items = $ ( '<div>' ) . html ( resp ) . find ( 'li' ) ,
2016-10-07 16:56:29 -04:00
wrapper = panel . closest ( '.accordion-section-content' ) ,
2019-09-23 08:42:58 -04:00
selectAll = wrapper . find ( '.button-controls .select-all' ) ,
2012-08-22 20:04:18 -04:00
$item ;
if ( ! $items . length ) {
$ ( '.categorychecklist' , panel ) . html ( '<li><p>' + navMenuL10n . noResultsFound + '</p></li>' ) ;
2015-04-03 00:52:27 -04:00
$ ( '.spinner' , panel ) . removeClass ( 'is-active' ) ;
2016-10-07 16:56:29 -04:00
wrapper . addClass ( 'has-no-menu-item' ) ;
2012-08-22 20:04:18 -04:00
return ;
}
$items . each ( function ( ) {
$item = $ ( this ) ;
2020-01-28 19:45:18 -05:00
// Make a unique DB ID number.
2012-08-22 20:04:18 -04:00
matched = pattern . exec ( $item . html ( ) ) ;
if ( matched && matched [ 1 ] ) {
newID = matched [ 1 ] ;
while ( form . elements [ 'menu-item[' + newID + '][menu-item-type]' ] || takenIDs [ newID ] ) {
newID -- ;
}
takenIDs [ newID ] = true ;
if ( newID != matched [ 1 ] ) {
$item . html ( $item . html ( ) . replace ( new RegExp (
'menu-item\\[' + matched [ 1 ] + '\\]' , 'g' ) ,
'menu-item[' + newID + ']'
) ) ;
}
}
} ) ;
$ ( '.categorychecklist' , panel ) . html ( $items ) ;
2015-04-03 00:52:27 -04:00
$ ( '.spinner' , panel ) . removeClass ( 'is-active' ) ;
2016-10-07 16:56:29 -04:00
wrapper . removeClass ( 'has-no-menu-item' ) ;
2019-09-23 08:42:58 -04:00
if ( selectAll . is ( ':checked' ) ) {
selectAll . prop ( 'checked' , false ) ;
}
2012-08-22 20:04:18 -04:00
} ,
2017-01-20 11:15:42 -05:00
/ * *
* Remove a menu item .
2020-06-20 08:58:10 -04:00
*
* @ param { object } el The element to be removed as a jQuery object .
2017-01-20 11:15:42 -05:00
*
* @ fires document # menu - removing - item Passes the element to be removed .
* /
2012-08-22 20:04:18 -04:00
removeMenuItem : function ( el ) {
var children = el . childMenuItems ( ) ;
2017-01-20 11:15:42 -05:00
$ ( document ) . trigger ( 'menu-removing-item' , [ el ] ) ;
2012-08-22 20:04:18 -04:00
el . addClass ( 'deleting' ) . animate ( {
opacity : 0 ,
height : 0
} , 350 , function ( ) {
var ins = $ ( '#menu-instructions' ) ;
el . remove ( ) ;
2013-02-15 23:53:59 -05:00
children . shiftDepthClass ( - 1 ) . updateParentMenuItemDBId ( ) ;
2013-11-14 01:09:11 -05:00
if ( 0 === $ ( '#menu-to-edit li' ) . length ) {
2013-02-15 23:53:59 -05:00
$ ( '.drag-instructions' ) . hide ( ) ;
ins . removeClass ( 'menu-instructions-inactive' ) ;
}
2015-02-01 00:29:26 -05:00
api . refreshAdvancedAccessibility ( ) ;
2012-08-22 20:04:18 -04:00
} ) ;
} ,
depthToPx : function ( depth ) {
return depth * api . options . menuItemDepthPerLevel ;
} ,
pxToDepth : function ( px ) {
return Math . floor ( px / api . options . menuItemDepthPerLevel ) ;
}
} ;
$ ( document ) . ready ( function ( ) { wpNavMenu . init ( ) ; } ) ;
} ) ( jQuery ) ;