]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-admin/js/nav-menu.js
WordPress 3.6.1
[autoinstalls/wordpress.git] / wp-admin / js / nav-menu.js
1 /**
2  * WordPress Administration Navigation Menu
3  * Interface JS functions
4  *
5  * @version 2.0.0
6  *
7  * @package WordPress
8  * @subpackage Administration
9  */
10
11 var wpNavMenu;
12
13 (function($) {
14
15         var api = wpNavMenu = {
16
17                 options : {
18                         menuItemDepthPerLevel : 30, // Do not use directly. Use depthToPx and pxToDepth instead.
19                         globalMaxDepth : 11
20                 },
21
22                 menuList : undefined,   // Set in init.
23                 targetList : undefined, // Set in init.
24                 menusChanged : false,
25                 isRTL: !! ( 'undefined' != typeof isRtl && isRtl ),
26                 negateIfRTL: ( 'undefined' != typeof isRtl && isRtl ) ? -1 : 1,
27
28                 // Functions that run on init.
29                 init : function() {
30                         api.menuList = $('#menu-to-edit');
31                         api.targetList = api.menuList;
32
33                         this.jQueryExtensions();
34
35                         this.attachMenuEditListeners();
36
37                         this.setupInputWithDefaultTitle();
38                         this.attachQuickSearchListeners();
39                         this.attachThemeLocationsListeners();
40
41                         this.attachTabsPanelListeners();
42
43                         this.attachUnsavedChangesListener();
44
45                         if ( api.menuList.length )
46                                 this.initSortables();
47
48                         if ( menus.oneThemeLocationNoMenus )
49                                 $( '#posttype-page' ).addSelectedToMenu( api.addMenuItemToBottom );
50
51                         this.initManageLocations();
52
53                         this.initAccessibility();
54
55                         this.initToggles();
56                 },
57
58                 jQueryExtensions : function() {
59                         // jQuery extensions
60                         $.fn.extend({
61                                 menuItemDepth : function() {
62                                         var margin = api.isRTL ? this.eq(0).css('margin-right') : this.eq(0).css('margin-left');
63                                         return api.pxToDepth( margin && -1 != margin.indexOf('px') ? margin.slice(0, -2) : 0 );
64                                 },
65                                 updateDepthClass : function(current, prev) {
66                                         return this.each(function(){
67                                                 var t = $(this);
68                                                 prev = prev || t.menuItemDepth();
69                                                 $(this).removeClass('menu-item-depth-'+ prev )
70                                                         .addClass('menu-item-depth-'+ current );
71                                         });
72                                 },
73                                 shiftDepthClass : function(change) {
74                                         return this.each(function(){
75                                                 var t = $(this),
76                                                         depth = t.menuItemDepth();
77                                                 $(this).removeClass('menu-item-depth-'+ depth )
78                                                         .addClass('menu-item-depth-'+ (depth + change) );
79                                         });
80                                 },
81                                 childMenuItems : function() {
82                                         var result = $();
83                                         this.each(function(){
84                                                 var t = $(this), depth = t.menuItemDepth(), next = t.next();
85                                                 while( next.length && next.menuItemDepth() > depth ) {
86                                                         result = result.add( next );
87                                                         next = next.next();
88                                                 }
89                                         });
90                                         return result;
91                                 },
92                                 shiftHorizontally : function( dir ) {
93                                         return this.each(function(){
94                                                 var t = $(this),
95                                                         depth = t.menuItemDepth(),
96                                                         newDepth = depth + dir;
97
98                                                 // Change .menu-item-depth-n class
99                                                 t.moveHorizontally( newDepth, depth );
100                                         });
101                                 },
102                                 moveHorizontally : function( newDepth, depth ) {
103                                         return this.each(function(){
104                                                 var t = $(this),
105                                                         children = t.childMenuItems(),
106                                                         diff = newDepth - depth,
107                                                         subItemText = t.find('.is-submenu');
108
109                                                 // Change .menu-item-depth-n class
110                                                 t.updateDepthClass( newDepth, depth ).updateParentMenuItemDBId();
111
112                                                 // If it has children, move those too
113                                                 if ( children ) {
114                                                         children.each(function( index ) {
115                                                                 var t = $(this),
116                                                                         thisDepth = t.menuItemDepth(),
117                                                                         newDepth = thisDepth + diff;
118                                                                 t.updateDepthClass(newDepth, thisDepth).updateParentMenuItemDBId();
119                                                         });
120                                                 }
121
122                                                 // Show "Sub item" helper text
123                                                 if (0 === newDepth)
124                                                         subItemText.hide();
125                                                 else
126                                                         subItemText.show();
127                                         });
128                                 },
129                                 updateParentMenuItemDBId : function() {
130                                         return this.each(function(){
131                                                 var item = $(this),
132                                                         input = item.find( '.menu-item-data-parent-id' ),
133                                                         depth = parseInt( item.menuItemDepth() ),
134                                                         parentDepth = depth - 1,
135                                                         parent = item.prevAll( '.menu-item-depth-' + parentDepth ).first();
136
137                                                 if ( 0 == depth ) { // Item is on the top level, has no parent
138                                                         input.val(0);
139                                                 } else { // Find the parent item, and retrieve its object id.
140                                                         input.val( parent.find( '.menu-item-data-db-id' ).val() );
141                                                 }
142                                         });
143                                 },
144                                 hideAdvancedMenuItemFields : function() {
145                                         return this.each(function(){
146                                                 var that = $(this);
147                                                 $('.hide-column-tog').not(':checked').each(function(){
148                                                         that.find('.field-' + $(this).val() ).addClass('hidden-field');
149                                                 });
150                                         });
151                                 },
152                                 /**
153                                  * Adds selected menu items to the menu.
154                                  *
155                                  * @param jQuery metabox The metabox jQuery object.
156                                  */
157                                 addSelectedToMenu : function(processMethod) {
158                                         if ( 0 == $('#menu-to-edit').length ) {
159                                                 return false;
160                                         }
161
162                                         return this.each(function() {
163                                                 var t = $(this), menuItems = {},
164                                                         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'),
165                                                         re = new RegExp('menu-item\\[(\[^\\]\]*)');
166
167                                                 processMethod = processMethod || api.addMenuItemToBottom;
168
169                                                 // If no items are checked, bail.
170                                                 if ( !checkboxes.length )
171                                                         return false;
172
173                                                 // Show the ajax spinner
174                                                 t.find('.spinner').show();
175
176                                                 // Retrieve menu item data
177                                                 $(checkboxes).each(function(){
178                                                         var t = $(this),
179                                                                 listItemDBIDMatch = re.exec( t.attr('name') ),
180                                                                 listItemDBID = 'undefined' == typeof listItemDBIDMatch[1] ? 0 : parseInt(listItemDBIDMatch[1], 10);
181                                                         if ( this.className && -1 != this.className.indexOf('add-to-top') )
182                                                                 processMethod = api.addMenuItemToTop;
183                                                         menuItems[listItemDBID] = t.closest('li').getItemData( 'add-menu-item', listItemDBID );
184                                                 });
185
186                                                 // Add the items
187                                                 api.addItemToMenu(menuItems, processMethod, function(){
188                                                         // Deselect the items and hide the ajax spinner
189                                                         checkboxes.removeAttr('checked');
190                                                         t.find('.spinner').hide();
191                                                 });
192                                         });
193                                 },
194                                 getItemData : function( itemType, id ) {
195                                         itemType = itemType || 'menu-item';
196
197                                         var itemData = {}, i,
198                                         fields = [
199                                                 'menu-item-db-id',
200                                                 'menu-item-object-id',
201                                                 'menu-item-object',
202                                                 'menu-item-parent-id',
203                                                 'menu-item-position',
204                                                 'menu-item-type',
205                                                 'menu-item-title',
206                                                 'menu-item-url',
207                                                 'menu-item-description',
208                                                 'menu-item-attr-title',
209                                                 'menu-item-target',
210                                                 'menu-item-classes',
211                                                 'menu-item-xfn'
212                                         ];
213
214                                         if( !id && itemType == 'menu-item' ) {
215                                                 id = this.find('.menu-item-data-db-id').val();
216                                         }
217
218                                         if( !id ) return itemData;
219
220                                         this.find('input').each(function() {
221                                                 var field;
222                                                 i = fields.length;
223                                                 while ( i-- ) {
224                                                         if( itemType == 'menu-item' )
225                                                                 field = fields[i] + '[' + id + ']';
226                                                         else if( itemType == 'add-menu-item' )
227                                                                 field = 'menu-item[' + id + '][' + fields[i] + ']';
228
229                                                         if (
230                                                                 this.name &&
231                                                                 field == this.name
232                                                         ) {
233                                                                 itemData[fields[i]] = this.value;
234                                                         }
235                                                 }
236                                         });
237
238                                         return itemData;
239                                 },
240                                 setItemData : function( itemData, itemType, id ) { // Can take a type, such as 'menu-item', or an id.
241                                         itemType = itemType || 'menu-item';
242
243                                         if( !id && itemType == 'menu-item' ) {
244                                                 id = $('.menu-item-data-db-id', this).val();
245                                         }
246
247                                         if( !id ) return this;
248
249                                         this.find('input').each(function() {
250                                                 var t = $(this), field;
251                                                 $.each( itemData, function( attr, val ) {
252                                                         if( itemType == 'menu-item' )
253                                                                 field = attr + '[' + id + ']';
254                                                         else if( itemType == 'add-menu-item' )
255                                                                 field = 'menu-item[' + id + '][' + attr + ']';
256
257                                                         if ( field == t.attr('name') ) {
258                                                                 t.val( val );
259                                                         }
260                                                 });
261                                         });
262                                         return this;
263                                 }
264                         });
265                 },
266
267                 countMenuItems : function( depth ) {
268                         return $( '.menu-item-depth-' + depth ).length;
269                 },
270
271                 moveMenuItem : function( $this, dir ) {
272
273                         var menuItems = $('#menu-to-edit li');
274                                 menuItemsCount = menuItems.length,
275                                 thisItem = $this.parents( 'li.menu-item' ),
276                                 thisItemChildren = thisItem.childMenuItems(),
277                                 thisItemData = thisItem.getItemData(),
278                                 thisItemDepth = parseInt( thisItem.menuItemDepth() ),
279                                 thisItemPosition = parseInt( thisItem.index() ),
280                                 nextItem = thisItem.next(),
281                                 nextItemChildren = nextItem.childMenuItems(),
282                                 nextItemDepth = parseInt( nextItem.menuItemDepth() ) + 1,
283                                 prevItem = thisItem.prev(),
284                                 prevItemDepth = parseInt( prevItem.menuItemDepth() ),
285                                 prevItemId = prevItem.getItemData()['menu-item-db-id'];
286
287                         switch ( dir ) {
288                         case 'up':
289                                 var newItemPosition = thisItemPosition - 1;
290
291                                 // Already at top
292                                 if ( 0 === thisItemPosition )
293                                         break;
294
295                                 // If a sub item is moved to top, shift it to 0 depth
296                                 if ( 0 === newItemPosition && 0 !== thisItemDepth )
297                                         thisItem.moveHorizontally( 0, thisItemDepth );
298
299                                 // If prev item is sub item, shift to match depth
300                                 if ( 0 !== prevItemDepth )
301                                         thisItem.moveHorizontally( prevItemDepth, thisItemDepth );
302
303                                 // Does this item have sub items?
304                                 if ( thisItemChildren ) {
305                                         var items = thisItem.add( thisItemChildren );
306                                         // Move the entire block
307                                         items.detach().insertBefore( menuItems.eq( newItemPosition ) ).updateParentMenuItemDBId();
308                                 } else {
309                                         thisItem.detach().insertBefore( menuItems.eq( newItemPosition ) ).updateParentMenuItemDBId();
310                                 }
311                                 break;
312                         case 'down':
313                                 // Does this item have sub items?
314                                 if ( thisItemChildren ) {
315                                         var items = thisItem.add( thisItemChildren ),
316                                                 nextItem = menuItems.eq( items.length + thisItemPosition ),
317                                                 nextItemChildren = 0 !== nextItem.childMenuItems().length;
318
319                                         if ( nextItemChildren ) {
320                                                 var newDepth = parseInt( nextItem.menuItemDepth() ) + 1;
321                                                 thisItem.moveHorizontally( newDepth, thisItemDepth );
322                                         }
323
324                                         // Have we reached the bottom?
325                                         if ( menuItemsCount === thisItemPosition + items.length )
326                                                 break;
327
328                                         items.detach().insertAfter( menuItems.eq( thisItemPosition + items.length ) ).updateParentMenuItemDBId();
329                                 } else {
330                                         // If next item has sub items, shift depth
331                                         if ( 0 !== nextItemChildren.length )
332                                                 thisItem.moveHorizontally( nextItemDepth, thisItemDepth );
333
334                                         // Have we reached the bottom
335                                         if ( menuItemsCount === thisItemPosition + 1 )
336                                                 break;
337                                         thisItem.detach().insertAfter( menuItems.eq( thisItemPosition + 1 ) ).updateParentMenuItemDBId();
338                                 }
339                                 break;
340                         case 'top':
341                                 // Already at top
342                                 if ( 0 === thisItemPosition )
343                                         break;
344                                 // Does this item have sub items?
345                                 if ( thisItemChildren ) {
346                                         var items = thisItem.add( thisItemChildren );
347                                         // Move the entire block
348                                         items.detach().insertBefore( menuItems.eq( 0 ) ).updateParentMenuItemDBId();
349                                 } else {
350                                         thisItem.detach().insertBefore( menuItems.eq( 0 ) ).updateParentMenuItemDBId();
351                                 }
352                                 break;
353                         case 'left':
354                                 // As far left as possible
355                                 if ( 0 === thisItemDepth )
356                                         break;
357                                 thisItem.shiftHorizontally( -1 );
358                                 break;
359                         case 'right':
360                                 // Can't be sub item at top
361                                 if ( 0 === thisItemPosition )
362                                         break;
363                                 // Already sub item of prevItem
364                                 if ( thisItemData['menu-item-parent-id'] === prevItemId )
365                                         break;
366                                 thisItem.shiftHorizontally( 1 );
367                                 break;
368                         }
369                         $this.focus();
370                         api.registerChange();
371                         api.refreshKeyboardAccessibility();
372                         api.refreshAdvancedAccessibility();
373                 },
374
375                 initAccessibility : function() {
376                         api.refreshKeyboardAccessibility();
377                         api.refreshAdvancedAccessibility();
378
379                         // Events
380                         $( '.menus-move-up' ).on( 'click', function ( e ) {
381                                 api.moveMenuItem( $( this ).parents( 'li.menu-item' ).find( 'a.item-edit' ), 'up' );
382                                 e.preventDefault();
383                         });
384                         $( '.menus-move-down' ).on( 'click', function ( e ) {
385                                 api.moveMenuItem( $( this ).parents( 'li.menu-item' ).find( 'a.item-edit' ), 'down' );
386                                 e.preventDefault();
387                         });
388                         $( '.menus-move-top' ).on( 'click', function ( e ) {
389                                 api.moveMenuItem( $( this ).parents( 'li.menu-item' ).find( 'a.item-edit' ), 'top' );
390                                 e.preventDefault();
391                         });
392                         $( '.menus-move-left' ).on( 'click', function ( e ) {
393                                 api.moveMenuItem( $( this ).parents( 'li.menu-item' ).find( 'a.item-edit' ), 'left' );
394                                 e.preventDefault();
395                         });
396                         $( '.menus-move-right' ).on( 'click', function ( e ) {
397                                 api.moveMenuItem( $( this ).parents( 'li.menu-item' ).find( 'a.item-edit' ), 'right' );
398                                 e.preventDefault();
399                         });
400                 },
401
402                 refreshAdvancedAccessibility : function() {
403
404                         // Hide all links by default
405                         $( '.menu-item-settings .field-move a' ).hide();
406
407                         $( '.item-edit' ).each( function() {
408                                 var $this = $(this),
409                                         movement = [],
410                                         availableMovement = '',
411                                         menuItem = $this.parents( 'li.menu-item' ).first(),
412                                         depth = menuItem.menuItemDepth(),
413                                         isPrimaryMenuItem = ( 0 === depth ),
414                                         itemName = $this.parents( '.menu-item-handle' ).find( '.menu-item-title' ).text(),
415                                         position = parseInt( menuItem.index() ),
416                                         prevItemDepth = ( isPrimaryMenuItem ) ? depth : parseInt( depth - 1 ),
417                                         prevItemNameLeft = menuItem.prevAll('.menu-item-depth-' + prevItemDepth).first().find( '.menu-item-title' ).text(),
418                                         prevItemNameRight = menuItem.prevAll('.menu-item-depth-' + depth).first().find( '.menu-item-title' ).text(),
419                                         totalMenuItems = $('#menu-to-edit li').length,
420                                         hasSameDepthSibling = menuItem.nextAll( '.menu-item-depth-' + depth ).length;
421
422                                 // Where can they move this menu item?
423                                 if ( 0 !== position ) {
424                                         var thisLink = menuItem.find( '.menus-move-up' );
425                                         thisLink.prop( 'title', menus.moveUp ).show();
426                                 }
427
428                                 if ( 0 !== position && isPrimaryMenuItem ) {
429                                         var thisLink = menuItem.find( '.menus-move-top' );
430                                         thisLink.prop( 'title', menus.moveToTop ).show();
431                                 }
432
433                                 if ( position + 1 !== totalMenuItems && 0 !== position ) {
434                                         var thisLink = menuItem.find( '.menus-move-down' );
435                                         thisLink.prop( 'title', menus.moveDown ).show();
436                                 }
437
438                                 if ( 0 === position && 0 !== hasSameDepthSibling ) {
439                                         var thisLink = menuItem.find( '.menus-move-down' );
440                                         thisLink.prop( 'title', menus.moveDown ).show();
441                                 }
442
443                                 if ( ! isPrimaryMenuItem ) {
444                                         var thisLink = menuItem.find( '.menus-move-left' ),
445                                                 thisLinkText = menus.outFrom.replace( '%s', prevItemNameLeft );
446                                         thisLink.prop( 'title', menus.moveOutFrom.replace( '%s', prevItemNameLeft ) ).html( thisLinkText ).show();
447                                 }
448
449                                 if ( 0 !== position ) {
450                                         if ( menuItem.find( '.menu-item-data-parent-id' ).val() !== menuItem.prev().find( '.menu-item-data-db-id' ).val() ) {
451                                                 var thisLink = menuItem.find( '.menus-move-right' ),
452                                                         thisLinkText = menus.under.replace( '%s', prevItemNameRight );
453                                                 thisLink.prop( 'title', menus.moveUnder.replace( '%s', prevItemNameRight ) ).html( thisLinkText ).show();
454                                         }
455                                 }
456
457                                 if ( isPrimaryMenuItem ) {
458                                         var primaryItems = $( '.menu-item-depth-0' ),
459                                                 itemPosition = primaryItems.index( menuItem ) + 1,
460                                                 totalMenuItems = primaryItems.length,
461
462                                                 // String together help text for primary menu items
463                                                 title = menus.menuFocus.replace( '%1$s', itemName ).replace( '%2$d', itemPosition ).replace( '%3$d', totalMenuItems );
464                                 } else {
465                                         var parentItem = menuItem.prevAll( '.menu-item-depth-' + parseInt( depth - 1 ) ).first(),
466                                                 parentItemId = parentItem.find( '.menu-item-data-db-id' ).val(),
467                                                 parentItemName = parentItem.find( '.menu-item-title' ).text(),
468                                                 subItems = $( '.menu-item .menu-item-data-parent-id[value="' + parentItemId + '"]' ),
469                                                 itemPosition = $( subItems.parents('.menu-item').get().reverse() ).index( menuItem ) + 1;
470
471                                                 // String together help text for sub menu items
472                                                 title = menus.subMenuFocus.replace( '%1$s', itemName ).replace( '%2$d', itemPosition ).replace( '%3$s', parentItemName );
473                                 }
474
475                                 $this.prop('title', title).html( title );
476                         });
477                 },
478
479                 refreshKeyboardAccessibility : function() {
480                         $( '.item-edit' ).off( 'focus' ).on( 'focus', function(){
481                                 $(this).off( 'keydown' ).on( 'keydown', function(e){
482
483                                         var $this = $(this);
484
485                                         // Bail if it's not an arrow key
486                                         if ( 37 != e.which && 38 != e.which && 39 != e.which && 40 != e.which )
487                                                 return;
488
489                                         // Avoid multiple keydown events
490                                         $this.off('keydown');
491
492                                         // Bail if there is only one menu item
493                                         if ( 1 === $('#menu-to-edit li').length )
494                                                 return;
495
496                                         // If RTL, swap left/right arrows
497                                         var arrows = { '38' : 'up', '40' : 'down', '37' : 'left', '39' : 'right' };
498                                         if ( $('body').hasClass('rtl') )
499                                                 arrows = { '38' : 'up', '40' : 'down', '39' : 'left', '37' : 'right' };
500
501                                         switch ( arrows[e.which] ) {
502                                         case 'up':
503                                                 api.moveMenuItem( $this, 'up' );
504                                                 break;
505                                         case 'down':
506                                                 api.moveMenuItem( $this, 'down' );
507                                                 break;
508                                         case 'left':
509                                                 api.moveMenuItem( $this, 'left' );
510                                                 break;
511                                         case 'right':
512                                                 api.moveMenuItem( $this, 'right' );
513                                                 break;
514                                         }
515                                         // Put focus back on same menu item
516                                         $( '#edit-' + thisItemData['menu-item-db-id'] ).focus();
517                                         return false;
518                                 });
519                         });
520                 },
521
522                 initToggles : function() {
523                         // init postboxes
524                         postboxes.add_postbox_toggles('nav-menus');
525
526                         // adjust columns functions for menus UI
527                         columns.useCheckboxesForHidden();
528                         columns.checked = function(field) {
529                                 $('.field-' + field).removeClass('hidden-field');
530                         }
531                         columns.unchecked = function(field) {
532                                 $('.field-' + field).addClass('hidden-field');
533                         }
534                         // hide fields
535                         api.menuList.hideAdvancedMenuItemFields();
536
537                         $('.hide-postbox-tog').click(function () {
538                                 var hidden = $( '.accordion-container li.accordion-section' ).filter(':hidden').map(function() { return this.id; }).get().join(',');
539                                 $.post(ajaxurl, {
540                                         action: 'closed-postboxes',
541                                         hidden: hidden,
542                                         closedpostboxesnonce: jQuery('#closedpostboxesnonce').val(),
543                                         page: 'nav-menus'
544                                 });
545                         });
546                 },
547
548                 initSortables : function() {
549                         var currentDepth = 0, originalDepth, minDepth, maxDepth,
550                                 prev, next, prevBottom, nextThreshold, helperHeight, transport,
551                                 menuEdge = api.menuList.offset().left,
552                                 body = $('body'), maxChildDepth,
553                                 menuMaxDepth = initialMenuMaxDepth();
554
555                         if( 0 != $( '#menu-to-edit li' ).length )
556                                 $( '.drag-instructions' ).show();
557
558                         // Use the right edge if RTL.
559                         menuEdge += api.isRTL ? api.menuList.width() : 0;
560
561                         api.menuList.sortable({
562                                 handle: '.menu-item-handle',
563                                 placeholder: 'sortable-placeholder',
564                                 start: function(e, ui) {
565                                         var height, width, parent, children, tempHolder;
566
567                                         // handle placement for rtl orientation
568                                         if ( api.isRTL )
569                                                 ui.item[0].style.right = 'auto';
570
571                                         transport = ui.item.children('.menu-item-transport');
572
573                                         // Set depths. currentDepth must be set before children are located.
574                                         originalDepth = ui.item.menuItemDepth();
575                                         updateCurrentDepth(ui, originalDepth);
576
577                                         // Attach child elements to parent
578                                         // Skip the placeholder
579                                         parent = ( ui.item.next()[0] == ui.placeholder[0] ) ? ui.item.next() : ui.item;
580                                         children = parent.childMenuItems();
581                                         transport.append( children );
582
583                                         // Update the height of the placeholder to match the moving item.
584                                         height = transport.outerHeight();
585                                         // If there are children, account for distance between top of children and parent
586                                         height += ( height > 0 ) ? (ui.placeholder.css('margin-top').slice(0, -2) * 1) : 0;
587                                         height += ui.helper.outerHeight();
588                                         helperHeight = height;
589                                         height -= 2; // Subtract 2 for borders
590                                         ui.placeholder.height(height);
591
592                                         // Update the width of the placeholder to match the moving item.
593                                         maxChildDepth = originalDepth;
594                                         children.each(function(){
595                                                 var depth = $(this).menuItemDepth();
596                                                 maxChildDepth = (depth > maxChildDepth) ? depth : maxChildDepth;
597                                         });
598                                         width = ui.helper.find('.menu-item-handle').outerWidth(); // Get original width
599                                         width += api.depthToPx(maxChildDepth - originalDepth); // Account for children
600                                         width -= 2; // Subtract 2 for borders
601                                         ui.placeholder.width(width);
602
603                                         // Update the list of menu items.
604                                         tempHolder = ui.placeholder.next();
605                                         tempHolder.css( 'margin-top', helperHeight + 'px' ); // Set the margin to absorb the placeholder
606                                         ui.placeholder.detach(); // detach or jQuery UI will think the placeholder is a menu item
607                                         $(this).sortable( "refresh" ); // The children aren't sortable. We should let jQ UI know.
608                                         ui.item.after( ui.placeholder ); // reattach the placeholder.
609                                         tempHolder.css('margin-top', 0); // reset the margin
610
611                                         // Now that the element is complete, we can update...
612                                         updateSharedVars(ui);
613                                 },
614                                 stop: function(e, ui) {
615                                         var children, depthChange = currentDepth - originalDepth;
616
617                                         // Return child elements to the list
618                                         children = transport.children().insertAfter(ui.item);
619
620                                         // Add "sub menu" description
621                                         var subMenuTitle = ui.item.find( '.item-title .is-submenu' );
622                                         if ( 0 < currentDepth )
623                                                 subMenuTitle.show();
624                                         else
625                                                 subMenuTitle.hide();
626
627                                         // Update depth classes
628                                         if( depthChange != 0 ) {
629                                                 ui.item.updateDepthClass( currentDepth );
630                                                 children.shiftDepthClass( depthChange );
631                                                 updateMenuMaxDepth( depthChange );
632                                         }
633                                         // Register a change
634                                         api.registerChange();
635                                         // Update the item data.
636                                         ui.item.updateParentMenuItemDBId();
637
638                                         // address sortable's incorrectly-calculated top in opera
639                                         ui.item[0].style.top = 0;
640
641                                         // handle drop placement for rtl orientation
642                                         if ( api.isRTL ) {
643                                                 ui.item[0].style.left = 'auto';
644                                                 ui.item[0].style.right = 0;
645                                         }
646
647                                         api.refreshKeyboardAccessibility();
648                                         api.refreshAdvancedAccessibility();
649                                 },
650                                 change: function(e, ui) {
651                                         // Make sure the placeholder is inside the menu.
652                                         // Otherwise fix it, or we're in trouble.
653                                         if( ! ui.placeholder.parent().hasClass('menu') )
654                                                 (prev.length) ? prev.after( ui.placeholder ) : api.menuList.prepend( ui.placeholder );
655
656                                         updateSharedVars(ui);
657                                 },
658                                 sort: function(e, ui) {
659                                         var offset = ui.helper.offset(),
660                                                 edge = api.isRTL ? offset.left + ui.helper.width() : offset.left,
661                                                 depth = api.negateIfRTL * api.pxToDepth( edge - menuEdge );
662                                         // Check and correct if depth is not within range.
663                                         // Also, if the dragged element is dragged upwards over
664                                         // an item, shift the placeholder to a child position.
665                                         if ( depth > maxDepth || offset.top < prevBottom ) depth = maxDepth;
666                                         else if ( depth < minDepth ) depth = minDepth;
667
668                                         if( depth != currentDepth )
669                                                 updateCurrentDepth(ui, depth);
670
671                                         // If we overlap the next element, manually shift downwards
672                                         if( nextThreshold && offset.top + helperHeight > nextThreshold ) {
673                                                 next.after( ui.placeholder );
674                                                 updateSharedVars( ui );
675                                                 $(this).sortable( "refreshPositions" );
676                                         }
677                                 }
678                         });
679
680                         function updateSharedVars(ui) {
681                                 var depth;
682
683                                 prev = ui.placeholder.prev();
684                                 next = ui.placeholder.next();
685
686                                 // Make sure we don't select the moving item.
687                                 if( prev[0] == ui.item[0] ) prev = prev.prev();
688                                 if( next[0] == ui.item[0] ) next = next.next();
689
690                                 prevBottom = (prev.length) ? prev.offset().top + prev.height() : 0;
691                                 nextThreshold = (next.length) ? next.offset().top + next.height() / 3 : 0;
692                                 minDepth = (next.length) ? next.menuItemDepth() : 0;
693
694                                 if( prev.length )
695                                         maxDepth = ( (depth = prev.menuItemDepth() + 1) > api.options.globalMaxDepth ) ? api.options.globalMaxDepth : depth;
696                                 else
697                                         maxDepth = 0;
698                         }
699
700                         function updateCurrentDepth(ui, depth) {
701                                 ui.placeholder.updateDepthClass( depth, currentDepth );
702                                 currentDepth = depth;
703                         }
704
705                         function initialMenuMaxDepth() {
706                                 if( ! body[0].className ) return 0;
707                                 var match = body[0].className.match(/menu-max-depth-(\d+)/);
708                                 return match && match[1] ? parseInt(match[1]) : 0;
709                         }
710
711                         function updateMenuMaxDepth( depthChange ) {
712                                 var depth, newDepth = menuMaxDepth;
713                                 if ( depthChange === 0 ) {
714                                         return;
715                                 } else if ( depthChange > 0 ) {
716                                         depth = maxChildDepth + depthChange;
717                                         if( depth > menuMaxDepth )
718                                                 newDepth = depth;
719                                 } else if ( depthChange < 0 && maxChildDepth == menuMaxDepth ) {
720                                         while( ! $('.menu-item-depth-' + newDepth, api.menuList).length && newDepth > 0 )
721                                                 newDepth--;
722                                 }
723                                 // Update the depth class.
724                                 body.removeClass( 'menu-max-depth-' + menuMaxDepth ).addClass( 'menu-max-depth-' + newDepth );
725                                 menuMaxDepth = newDepth;
726                         }
727                 },
728
729                 initManageLocations : function () {
730                         $('#menu-locations-wrap form').submit(function(){
731                                 window.onbeforeunload = null;
732                         });
733                         $('.menu-location-menus select').on('change', function () {
734                                 var editLink = $(this).closest('tr').find('.locations-edit-menu-link');
735                                 if ($(this).find('option:selected').data('orig'))
736                                         editLink.show();
737                                 else
738                                         editLink.hide();
739                         });
740                 },
741
742                 attachMenuEditListeners : function() {
743                         var that = this;
744                         $('#update-nav-menu').bind('click', function(e) {
745                                 if ( e.target && e.target.className ) {
746                                         if ( -1 != e.target.className.indexOf('item-edit') ) {
747                                                 return that.eventOnClickEditLink(e.target);
748                                         } else if ( -1 != e.target.className.indexOf('menu-save') ) {
749                                                 return that.eventOnClickMenuSave(e.target);
750                                         } else if ( -1 != e.target.className.indexOf('menu-delete') ) {
751                                                 return that.eventOnClickMenuDelete(e.target);
752                                         } else if ( -1 != e.target.className.indexOf('item-delete') ) {
753                                                 return that.eventOnClickMenuItemDelete(e.target);
754                                         } else if ( -1 != e.target.className.indexOf('item-cancel') ) {
755                                                 return that.eventOnClickCancelLink(e.target);
756                                         }
757                                 }
758                         });
759                         $('#add-custom-links input[type="text"]').keypress(function(e){
760                                 if ( e.keyCode === 13 ) {
761                                         e.preventDefault();
762                                         $("#submit-customlinkdiv").click();
763                                 }
764                         });
765                 },
766
767                 /**
768                  * An interface for managing default values for input elements
769                  * that is both JS and accessibility-friendly.
770                  *
771                  * Input elements that add the class 'input-with-default-title'
772                  * will have their values set to the provided HTML title when empty.
773                  */
774                 setupInputWithDefaultTitle : function() {
775                         var name = 'input-with-default-title';
776
777                         $('.' + name).each( function(){
778                                 var $t = $(this), title = $t.attr('title'), val = $t.val();
779                                 $t.data( name, title );
780
781                                 if( '' == val ) $t.val( title );
782                                 else if ( title == val ) return;
783                                 else $t.removeClass( name );
784                         }).focus( function(){
785                                 var $t = $(this);
786                                 if( $t.val() == $t.data(name) )
787                                         $t.val('').removeClass( name );
788                         }).blur( function(){
789                                 var $t = $(this);
790                                 if( '' == $t.val() )
791                                         $t.addClass( name ).val( $t.data(name) );
792                         });
793
794                         $( '.blank-slate .input-with-default-title' ).focus();
795                 },
796
797                 attachThemeLocationsListeners : function() {
798                         var loc = $('#nav-menu-theme-locations'), params = {};
799                         params['action'] = 'menu-locations-save';
800                         params['menu-settings-column-nonce'] = $('#menu-settings-column-nonce').val();
801                         loc.find('input[type="submit"]').click(function() {
802                                 loc.find('select').each(function() {
803                                         params[this.name] = $(this).val();
804                                 });
805                                 loc.find('.spinner').show();
806                                 $.post( ajaxurl, params, function(r) {
807                                         loc.find('.spinner').hide();
808                                 });
809                                 return false;
810                         });
811                 },
812
813                 attachQuickSearchListeners : function() {
814                         var searchTimer;
815
816                         $('.quick-search').keypress(function(e){
817                                 var t = $(this);
818
819                                 if( 13 == e.which ) {
820                                         api.updateQuickSearchResults( t );
821                                         return false;
822                                 }
823
824                                 if( searchTimer ) clearTimeout(searchTimer);
825
826                                 searchTimer = setTimeout(function(){
827                                         api.updateQuickSearchResults( t );
828                                 }, 400);
829                         }).attr('autocomplete','off');
830                 },
831
832                 updateQuickSearchResults : function(input) {
833                         var panel, params,
834                         minSearchLength = 2,
835                         q = input.val();
836
837                         if( q.length < minSearchLength ) return;
838
839                         panel = input.parents('.tabs-panel');
840                         params = {
841                                 'action': 'menu-quick-search',
842                                 'response-format': 'markup',
843                                 'menu': $('#menu').val(),
844                                 'menu-settings-column-nonce': $('#menu-settings-column-nonce').val(),
845                                 'q': q,
846                                 'type': input.attr('name')
847                         };
848
849                         $('.spinner', panel).show();
850
851                         $.post( ajaxurl, params, function(menuMarkup) {
852                                 api.processQuickSearchQueryResponse(menuMarkup, params, panel);
853                         });
854                 },
855
856                 addCustomLink : function( processMethod ) {
857                         var url = $('#custom-menu-item-url').val(),
858                                 label = $('#custom-menu-item-name').val();
859
860                         processMethod = processMethod || api.addMenuItemToBottom;
861
862                         if ( '' == url || 'http://' == url )
863                                 return false;
864
865                         // Show the ajax spinner
866                         $('.customlinkdiv .spinner').show();
867                         this.addLinkToMenu( url, label, processMethod, function() {
868                                 // Remove the ajax spinner
869                                 $('.customlinkdiv .spinner').hide();
870                                 // Set custom link form back to defaults
871                                 $('#custom-menu-item-name').val('').blur();
872                                 $('#custom-menu-item-url').val('http://');
873                         });
874                 },
875
876                 addLinkToMenu : function(url, label, processMethod, callback) {
877                         processMethod = processMethod || api.addMenuItemToBottom;
878                         callback = callback || function(){};
879
880                         api.addItemToMenu({
881                                 '-1': {
882                                         'menu-item-type': 'custom',
883                                         'menu-item-url': url,
884                                         'menu-item-title': label
885                                 }
886                         }, processMethod, callback);
887                 },
888
889                 addItemToMenu : function(menuItem, processMethod, callback) {
890                         var menu = $('#menu').val(),
891                                 nonce = $('#menu-settings-column-nonce').val();
892
893                         processMethod = processMethod || function(){};
894                         callback = callback || function(){};
895
896                         params = {
897                                 'action': 'add-menu-item',
898                                 'menu': menu,
899                                 'menu-settings-column-nonce': nonce,
900                                 'menu-item': menuItem
901                         };
902
903                         $.post( ajaxurl, params, function(menuMarkup) {
904                                 var ins = $('#menu-instructions');
905                                 processMethod(menuMarkup, params);
906                                 // Make it stand out a bit more visually, by adding a fadeIn
907                                 $( 'li.pending' ).hide().fadeIn('slow');
908                                 $( '.drag-instructions' ).show();
909                                 if( ! ins.hasClass( 'menu-instructions-inactive' ) && ins.siblings().length )
910                                         ins.addClass( 'menu-instructions-inactive' );
911                                 callback();
912                         });
913                 },
914
915                 /**
916                  * Process the add menu item request response into menu list item.
917                  *
918                  * @param string menuMarkup The text server response of menu item markup.
919                  * @param object req The request arguments.
920                  */
921                 addMenuItemToBottom : function( menuMarkup, req ) {
922                         $(menuMarkup).hideAdvancedMenuItemFields().appendTo( api.targetList );
923                         api.refreshKeyboardAccessibility();
924                         api.refreshAdvancedAccessibility();
925                 },
926
927                 addMenuItemToTop : function( menuMarkup, req ) {
928                         $(menuMarkup).hideAdvancedMenuItemFields().prependTo( api.targetList );
929                         api.refreshKeyboardAccessibility();
930                         api.refreshAdvancedAccessibility();
931                 },
932
933                 attachUnsavedChangesListener : function() {
934                         $('#menu-management input, #menu-management select, #menu-management, #menu-management textarea, .menu-location-menus select').change(function(){
935                                 api.registerChange();
936                         });
937
938                         if ( 0 != $('#menu-to-edit').length || 0 != $('.menu-location-menus select').length ) {
939                                 window.onbeforeunload = function(){
940                                         if ( api.menusChanged )
941                                                 return navMenuL10n.saveAlert;
942                                 };
943                         } else {
944                                 // Make the post boxes read-only, as they can't be used yet
945                                 $( '#menu-settings-column' ).find( 'input,select' ).end().find( 'a' ).attr( 'href', '#' ).unbind( 'click' );
946                         }
947                 },
948
949                 registerChange : function() {
950                         api.menusChanged = true;
951                 },
952
953                 attachTabsPanelListeners : function() {
954                         $('#menu-settings-column').bind('click', function(e) {
955                                 var selectAreaMatch, panelId, wrapper, items,
956                                         target = $(e.target);
957
958                                 if ( target.hasClass('nav-tab-link') ) {
959
960                                         panelId = target.data( 'type' );
961
962                                         wrapper = target.parents('.accordion-section-content').first();
963
964                                         // upon changing tabs, we want to uncheck all checkboxes
965                                         $('input', wrapper).removeAttr('checked');
966
967                                         $('.tabs-panel-active', wrapper).removeClass('tabs-panel-active').addClass('tabs-panel-inactive');
968                                         $('#' + panelId, wrapper).removeClass('tabs-panel-inactive').addClass('tabs-panel-active');
969
970                                         $('.tabs', wrapper).removeClass('tabs');
971                                         target.parent().addClass('tabs');
972
973                                         // select the search bar
974                                         $('.quick-search', wrapper).focus();
975
976                                         e.preventDefault();
977                                 } else if ( target.hasClass('select-all') ) {
978                                         selectAreaMatch = /#(.*)$/.exec(e.target.href);
979                                         if ( selectAreaMatch && selectAreaMatch[1] ) {
980                                                 items = $('#' + selectAreaMatch[1] + ' .tabs-panel-active .menu-item-title input');
981                                                 if( items.length === items.filter(':checked').length )
982                                                         items.removeAttr('checked');
983                                                 else
984                                                         items.prop('checked', true);
985                                                 return false;
986                                         }
987                                 } else if ( target.hasClass('submit-add-to-menu') ) {
988                                         api.registerChange();
989
990                                         if ( e.target.id && 'submit-customlinkdiv' == e.target.id )
991                                                 api.addCustomLink( api.addMenuItemToBottom );
992                                         else if ( e.target.id && -1 != e.target.id.indexOf('submit-') )
993                                                 $('#' + e.target.id.replace(/submit-/, '')).addSelectedToMenu( api.addMenuItemToBottom );
994                                         return false;
995                                 } else if ( target.hasClass('page-numbers') ) {
996                                         $.post( ajaxurl, e.target.href.replace(/.*\?/, '').replace(/action=([^&]*)/, '') + '&action=menu-get-metabox',
997                                                 function( resp ) {
998                                                         if ( -1 == resp.indexOf('replace-id') )
999                                                                 return;
1000
1001                                                         var metaBoxData = $.parseJSON(resp),
1002                                                         toReplace = document.getElementById(metaBoxData['replace-id']),
1003                                                         placeholder = document.createElement('div'),
1004                                                         wrap = document.createElement('div');
1005
1006                                                         if ( ! metaBoxData['markup'] || ! toReplace )
1007                                                                 return;
1008
1009                                                         wrap.innerHTML = metaBoxData['markup'] ? metaBoxData['markup'] : '';
1010
1011                                                         toReplace.parentNode.insertBefore( placeholder, toReplace );
1012                                                         placeholder.parentNode.removeChild( toReplace );
1013
1014                                                         placeholder.parentNode.insertBefore( wrap, placeholder );
1015
1016                                                         placeholder.parentNode.removeChild( placeholder );
1017
1018                                                 }
1019                                         );
1020
1021                                         return false;
1022                                 }
1023                         });
1024                 },
1025
1026                 eventOnClickEditLink : function(clickedEl) {
1027                         var settings, item,
1028                         matchedSection = /#(.*)$/.exec(clickedEl.href);
1029                         if ( matchedSection && matchedSection[1] ) {
1030                                 settings = $('#'+matchedSection[1]);
1031                                 item = settings.parent();
1032                                 if( 0 != item.length ) {
1033                                         if( item.hasClass('menu-item-edit-inactive') ) {
1034                                                 if( ! settings.data('menu-item-data') ) {
1035                                                         settings.data( 'menu-item-data', settings.getItemData() );
1036                                                 }
1037                                                 settings.slideDown('fast');
1038                                                 item.removeClass('menu-item-edit-inactive')
1039                                                         .addClass('menu-item-edit-active');
1040                                         } else {
1041                                                 settings.slideUp('fast');
1042                                                 item.removeClass('menu-item-edit-active')
1043                                                         .addClass('menu-item-edit-inactive');
1044                                         }
1045                                         return false;
1046                                 }
1047                         }
1048                 },
1049
1050                 eventOnClickCancelLink : function(clickedEl) {
1051                         var settings = $( clickedEl ).closest( '.menu-item-settings' ),
1052                                 thisMenuItem = $( clickedEl ).closest( '.menu-item' );
1053                         thisMenuItem.removeClass('menu-item-edit-active').addClass('menu-item-edit-inactive');
1054                         settings.setItemData( settings.data('menu-item-data') ).hide();
1055                         return false;
1056                 },
1057
1058                 eventOnClickMenuSave : function(clickedEl) {
1059                         var locs = '',
1060                         menuName = $('#menu-name'),
1061                         menuNameVal = menuName.val();
1062                         // Cancel and warn if invalid menu name
1063                         if( !menuNameVal || menuNameVal == menuName.attr('title') || !menuNameVal.replace(/\s+/, '') ) {
1064                                 menuName.parent().addClass('form-invalid');
1065                                 return false;
1066                         }
1067                         // Copy menu theme locations
1068                         $('#nav-menu-theme-locations select').each(function() {
1069                                 locs += '<input type="hidden" name="' + this.name + '" value="' + $(this).val() + '" />';
1070                         });
1071                         $('#update-nav-menu').append( locs );
1072                         // Update menu item position data
1073                         api.menuList.find('.menu-item-data-position').val( function(index) { return index + 1; } );
1074                         window.onbeforeunload = null;
1075
1076                         return true;
1077                 },
1078
1079                 eventOnClickMenuDelete : function(clickedEl) {
1080                         // Delete warning AYS
1081                         if ( confirm( navMenuL10n.warnDeleteMenu ) ) {
1082                                 window.onbeforeunload = null;
1083                                 return true;
1084                         }
1085                         return false;
1086                 },
1087
1088                 eventOnClickMenuItemDelete : function(clickedEl) {
1089                         var itemID = parseInt(clickedEl.id.replace('delete-', ''), 10);
1090                         api.removeMenuItem( $('#menu-item-' + itemID) );
1091                         api.registerChange();
1092                         return false;
1093                 },
1094
1095                 /**
1096                  * Process the quick search response into a search result
1097                  *
1098                  * @param string resp The server response to the query.
1099                  * @param object req The request arguments.
1100                  * @param jQuery panel The tabs panel we're searching in.
1101                  */
1102                 processQuickSearchQueryResponse : function(resp, req, panel) {
1103                         var matched, newID,
1104                         takenIDs = {},
1105                         form = document.getElementById('nav-menu-meta'),
1106                         pattern = new RegExp('menu-item\\[(\[^\\]\]*)', 'g'),
1107                         $items = $('<div>').html(resp).find('li'),
1108                         $item;
1109
1110                         if( ! $items.length ) {
1111                                 $('.categorychecklist', panel).html( '<li><p>' + navMenuL10n.noResultsFound + '</p></li>' );
1112                                 $('.spinner', panel).hide();
1113                                 return;
1114                         }
1115
1116                         $items.each(function(){
1117                                 $item = $(this);
1118
1119                                 // make a unique DB ID number
1120                                 matched = pattern.exec($item.html());
1121
1122                                 if ( matched && matched[1] ) {
1123                                         newID = matched[1];
1124                                         while( form.elements['menu-item[' + newID + '][menu-item-type]'] || takenIDs[ newID ] ) {
1125                                                 newID--;
1126                                         }
1127
1128                                         takenIDs[newID] = true;
1129                                         if ( newID != matched[1] ) {
1130                                                 $item.html( $item.html().replace(new RegExp(
1131                                                         'menu-item\\[' + matched[1] + '\\]', 'g'),
1132                                                         'menu-item[' + newID + ']'
1133                                                 ) );
1134                                         }
1135                                 }
1136                         });
1137
1138                         $('.categorychecklist', panel).html( $items );
1139                         $('.spinner', panel).hide();
1140                 },
1141
1142                 removeMenuItem : function(el) {
1143                         var children = el.childMenuItems();
1144
1145                         el.addClass('deleting').animate({
1146                                         opacity : 0,
1147                                         height: 0
1148                                 }, 350, function() {
1149                                         var ins = $('#menu-instructions');
1150                                         el.remove();
1151                                         children.shiftDepthClass( -1 ).updateParentMenuItemDBId();
1152                                         if( 0 == $( '#menu-to-edit li' ).length ) {
1153                                                 $( '.drag-instructions' ).hide();
1154                                                 ins.removeClass( 'menu-instructions-inactive' );
1155                                         }
1156                                 });
1157                 },
1158
1159                 depthToPx : function(depth) {
1160                         return depth * api.options.menuItemDepthPerLevel;
1161                 },
1162
1163                 pxToDepth : function(px) {
1164                         return Math.floor(px / api.options.menuItemDepthPerLevel);
1165                 }
1166
1167         };
1168
1169         $(document).ready(function(){ wpNavMenu.init(); });
1170
1171 })(jQuery);