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