]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-admin/js/nav-menu.dev.js
Wordpress 3.2.1-scripts
[autoinstalls/wordpress.git] / wp-admin / js / nav-menu.dev.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 ) // If no menu, we're in the + tab.
46                                 this.initSortables();
47
48                         this.initToggles();
49
50                         this.initTabManager();
51                 },
52
53                 jQueryExtensions : function() {
54                         // jQuery extensions
55                         $.fn.extend({
56                                 menuItemDepth : function() {
57                                         var margin = api.isRTL ? this.eq(0).css('margin-right') : this.eq(0).css('margin-left');
58                                         return api.pxToDepth( margin && -1 != margin.indexOf('px') ? margin.slice(0, -2) : 0 );
59                                 },
60                                 updateDepthClass : function(current, prev) {
61                                         return this.each(function(){
62                                                 var t = $(this);
63                                                 prev = prev || t.menuItemDepth();
64                                                 $(this).removeClass('menu-item-depth-'+ prev )
65                                                         .addClass('menu-item-depth-'+ current );
66                                         });
67                                 },
68                                 shiftDepthClass : function(change) {
69                                         return this.each(function(){
70                                                 var t = $(this),
71                                                         depth = t.menuItemDepth();
72                                                 $(this).removeClass('menu-item-depth-'+ depth )
73                                                         .addClass('menu-item-depth-'+ (depth + change) );
74                                         });
75                                 },
76                                 childMenuItems : function() {
77                                         var result = $();
78                                         this.each(function(){
79                                                 var t = $(this), depth = t.menuItemDepth(), next = t.next();
80                                                 while( next.length && next.menuItemDepth() > depth ) {
81                                                         result = result.add( next );
82                                                         next = next.next();
83                                                 }
84                                         });
85                                         return result;
86                                 },
87                                 updateParentMenuItemDBId : function() {
88                                         return this.each(function(){
89                                                 var item = $(this),
90                                                         input = item.find('.menu-item-data-parent-id'),
91                                                         depth = item.menuItemDepth(),
92                                                         parent = item.prev();
93
94                                                 if( depth == 0 ) { // Item is on the top level, has no parent
95                                                         input.val(0);
96                                                 } else { // Find the parent item, and retrieve its object id.
97                                                         while( ! parent[0] || ! parent[0].className || -1 == parent[0].className.indexOf('menu-item') || ( parent.menuItemDepth() != depth - 1 ) )
98                                                                 parent = parent.prev();
99                                                         input.val( parent.find('.menu-item-data-db-id').val() );
100                                                 }
101                                         });
102                                 },
103                                 hideAdvancedMenuItemFields : function() {
104                                         return this.each(function(){
105                                                 var that = $(this);
106                                                 $('.hide-column-tog').not(':checked').each(function(){
107                                                         that.find('.field-' + $(this).val() ).addClass('hidden-field');
108                                                 });
109                                         });
110                                 },
111                                 /**
112                                  * Adds selected menu items to the menu.
113                                  *
114                                  * @param jQuery metabox The metabox jQuery object.
115                                  */
116                                 addSelectedToMenu : function(processMethod) {
117                                         if ( 0 == $('#menu-to-edit').length ) {
118                                                 return false;
119                                         }
120
121                                         return this.each(function() {
122                                                 var t = $(this), menuItems = {},
123                                                         checkboxes = t.find('.tabs-panel-active .categorychecklist li input:checked'),
124                                                         re = new RegExp('menu-item\\[(\[^\\]\]*)');
125
126                                                 processMethod = processMethod || api.addMenuItemToBottom;
127
128                                                 // If no items are checked, bail.
129                                                 if ( !checkboxes.length )
130                                                         return false;
131
132                                                 // Show the ajax spinner
133                                                 t.find('img.waiting').show();
134
135                                                 // Retrieve menu item data
136                                                 $(checkboxes).each(function(){
137                                                         var t = $(this),
138                                                                 listItemDBIDMatch = re.exec( t.attr('name') ),
139                                                                 listItemDBID = 'undefined' == typeof listItemDBIDMatch[1] ? 0 : parseInt(listItemDBIDMatch[1], 10);
140                                                         if ( this.className && -1 != this.className.indexOf('add-to-top') )
141                                                                 processMethod = api.addMenuItemToTop;
142                                                         menuItems[listItemDBID] = t.closest('li').getItemData( 'add-menu-item', listItemDBID );
143                                                 });
144
145                                                 // Add the items
146                                                 api.addItemToMenu(menuItems, processMethod, function(){
147                                                         // Deselect the items and hide the ajax spinner
148                                                         checkboxes.removeAttr('checked');
149                                                         t.find('img.waiting').hide();
150                                                 });
151                                         });
152                                 },
153                                 getItemData : function( itemType, id ) {
154                                         itemType = itemType || 'menu-item';
155
156                                         var itemData = {}, i,
157                                         fields = [
158                                                 'menu-item-db-id',
159                                                 'menu-item-object-id',
160                                                 'menu-item-object',
161                                                 'menu-item-parent-id',
162                                                 'menu-item-position',
163                                                 'menu-item-type',
164                                                 'menu-item-title',
165                                                 'menu-item-url',
166                                                 'menu-item-description',
167                                                 'menu-item-attr-title',
168                                                 'menu-item-target',
169                                                 'menu-item-classes',
170                                                 'menu-item-xfn'
171                                         ];
172
173                                         if( !id && itemType == 'menu-item' ) {
174                                                 id = this.find('.menu-item-data-db-id').val();
175                                         }
176
177                                         if( !id ) return itemData;
178
179                                         this.find('input').each(function() {
180                                                 var field;
181                                                 i = fields.length;
182                                                 while ( i-- ) {
183                                                         if( itemType == 'menu-item' )
184                                                                 field = fields[i] + '[' + id + ']';
185                                                         else if( itemType == 'add-menu-item' )
186                                                                 field = 'menu-item[' + id + '][' + fields[i] + ']';
187
188                                                         if (
189                                                                 this.name &&
190                                                                 field == this.name
191                                                         ) {
192                                                                 itemData[fields[i]] = this.value;
193                                                         }
194                                                 }
195                                         });
196
197                                         return itemData;
198                                 },
199                                 setItemData : function( itemData, itemType, id ) { // Can take a type, such as 'menu-item', or an id.
200                                         itemType = itemType || 'menu-item';
201
202                                         if( !id && itemType == 'menu-item' ) {
203                                                 id = $('.menu-item-data-db-id', this).val();
204                                         }
205
206                                         if( !id ) return this;
207
208                                         this.find('input').each(function() {
209                                                 var t = $(this), field;
210                                                 $.each( itemData, function( attr, val ) {
211                                                         if( itemType == 'menu-item' )
212                                                                 field = attr + '[' + id + ']';
213                                                         else if( itemType == 'add-menu-item' )
214                                                                 field = 'menu-item[' + id + '][' + attr + ']';
215
216                                                         if ( field == t.attr('name') ) {
217                                                                 t.val( val );
218                                                         }
219                                                 });
220                                         });
221                                         return this;
222                                 }
223                         });
224                 },
225
226                 initToggles : function() {
227                         // init postboxes
228                         postboxes.add_postbox_toggles('nav-menus');
229
230                         // adjust columns functions for menus UI
231                         columns.useCheckboxesForHidden();
232                         columns.checked = function(field) {
233                                 $('.field-' + field).removeClass('hidden-field');
234                         }
235                         columns.unchecked = function(field) {
236                                 $('.field-' + field).addClass('hidden-field');
237                         }
238                         // hide fields
239                         api.menuList.hideAdvancedMenuItemFields();
240                 },
241
242                 initSortables : function() {
243                         var currentDepth = 0, originalDepth, minDepth, maxDepth,
244                                 prev, next, prevBottom, nextThreshold, helperHeight, transport,
245                                 menuEdge = api.menuList.offset().left,
246                                 body = $('body'), maxChildDepth,
247                                 menuMaxDepth = initialMenuMaxDepth();
248
249                         // Use the right edge if RTL.
250                         menuEdge += api.isRTL ? api.menuList.width() : 0;
251
252                         api.menuList.sortable({
253                                 handle: '.menu-item-handle',
254                                 placeholder: 'sortable-placeholder',
255                                 start: function(e, ui) {
256                                         var height, width, parent, children, tempHolder;
257
258                                         // handle placement for rtl orientation
259                                         if ( api.isRTL )
260                                                 ui.item[0].style.right = 'auto';
261
262                                         transport = ui.item.children('.menu-item-transport');
263
264                                         // Set depths. currentDepth must be set before children are located.
265                                         originalDepth = ui.item.menuItemDepth();
266                                         updateCurrentDepth(ui, originalDepth);
267
268                                         // Attach child elements to parent
269                                         // Skip the placeholder
270                                         parent = ( ui.item.next()[0] == ui.placeholder[0] ) ? ui.item.next() : ui.item;
271                                         children = parent.childMenuItems();
272                                         transport.append( children );
273
274                                         // Update the height of the placeholder to match the moving item.
275                                         height = transport.outerHeight();
276                                         // If there are children, account for distance between top of children and parent
277                                         height += ( height > 0 ) ? (ui.placeholder.css('margin-top').slice(0, -2) * 1) : 0;
278                                         height += ui.helper.outerHeight();
279                                         helperHeight = height;
280                                         height -= 2; // Subtract 2 for borders
281                                         ui.placeholder.height(height);
282
283                                         // Update the width of the placeholder to match the moving item.
284                                         maxChildDepth = originalDepth;
285                                         children.each(function(){
286                                                 var depth = $(this).menuItemDepth();
287                                                 maxChildDepth = (depth > maxChildDepth) ? depth : maxChildDepth;
288                                         });
289                                         width = ui.helper.find('.menu-item-handle').outerWidth(); // Get original width
290                                         width += api.depthToPx(maxChildDepth - originalDepth); // Account for children
291                                         width -= 2; // Subtract 2 for borders
292                                         ui.placeholder.width(width);
293
294                                         // Update the list of menu items.
295                                         tempHolder = ui.placeholder.next();
296                                         tempHolder.css( 'margin-top', helperHeight + 'px' ); // Set the margin to absorb the placeholder
297                                         ui.placeholder.detach(); // detach or jQuery UI will think the placeholder is a menu item
298                                         $(this).sortable( "refresh" ); // The children aren't sortable. We should let jQ UI know.
299                                         ui.item.after( ui.placeholder ); // reattach the placeholder.
300                                         tempHolder.css('margin-top', 0); // reset the margin
301
302                                         // Now that the element is complete, we can update...
303                                         updateSharedVars(ui);
304                                 },
305                                 stop: function(e, ui) {
306                                         var children, depthChange = currentDepth - originalDepth;
307
308                                         // Return child elements to the list
309                                         children = transport.children().insertAfter(ui.item);
310
311                                         // Update depth classes
312                                         if( depthChange != 0 ) {
313                                                 ui.item.updateDepthClass( currentDepth );
314                                                 children.shiftDepthClass( depthChange );
315                                                 updateMenuMaxDepth( depthChange );
316                                         }
317                                         // Register a change
318                                         api.registerChange();
319                                         // Update the item data.
320                                         ui.item.updateParentMenuItemDBId();
321
322                                         // address sortable's incorrectly-calculated top in opera
323                                         ui.item[0].style.top = 0;
324
325                                         // handle drop placement for rtl orientation
326                                         if ( api.isRTL ) {
327                                                 ui.item[0].style.left = 'auto';
328                                                 ui.item[0].style.right = 0;
329                                         }
330
331                                         // The width of the tab bar might have changed. Just in case.
332                                         api.refreshMenuTabs( true );
333                                 },
334                                 change: function(e, ui) {
335                                         // Make sure the placeholder is inside the menu.
336                                         // Otherwise fix it, or we're in trouble.
337                                         if( ! ui.placeholder.parent().hasClass('menu') )
338                                                 (prev.length) ? prev.after( ui.placeholder ) : api.menuList.prepend( ui.placeholder );
339
340                                         updateSharedVars(ui);
341                                 },
342                                 sort: function(e, ui) {
343                                         var offset = ui.helper.offset(),
344                                                 edge = api.isRTL ? offset.left + ui.helper.width() : offset.left,
345                                                 depth = api.negateIfRTL * api.pxToDepth( edge - menuEdge );
346                                         // Check and correct if depth is not within range.
347                                         // Also, if the dragged element is dragged upwards over
348                                         // an item, shift the placeholder to a child position.
349                                         if ( depth > maxDepth || offset.top < prevBottom ) depth = maxDepth;
350                                         else if ( depth < minDepth ) depth = minDepth;
351
352                                         if( depth != currentDepth )
353                                                 updateCurrentDepth(ui, depth);
354
355                                         // If we overlap the next element, manually shift downwards
356                                         if( nextThreshold && offset.top + helperHeight > nextThreshold ) {
357                                                 next.after( ui.placeholder );
358                                                 updateSharedVars( ui );
359                                                 $(this).sortable( "refreshPositions" );
360                                         }
361                                 }
362                         });
363
364                         function updateSharedVars(ui) {
365                                 var depth;
366
367                                 prev = ui.placeholder.prev();
368                                 next = ui.placeholder.next();
369
370                                 // Make sure we don't select the moving item.
371                                 if( prev[0] == ui.item[0] ) prev = prev.prev();
372                                 if( next[0] == ui.item[0] ) next = next.next();
373
374                                 prevBottom = (prev.length) ? prev.offset().top + prev.height() : 0;
375                                 nextThreshold = (next.length) ? next.offset().top + next.height() / 3 : 0;
376                                 minDepth = (next.length) ? next.menuItemDepth() : 0;
377
378                                 if( prev.length )
379                                         maxDepth = ( (depth = prev.menuItemDepth() + 1) > api.options.globalMaxDepth ) ? api.options.globalMaxDepth : depth;
380                                 else
381                                         maxDepth = 0;
382                         }
383
384                         function updateCurrentDepth(ui, depth) {
385                                 ui.placeholder.updateDepthClass( depth, currentDepth );
386                                 currentDepth = depth;
387                         }
388
389                         function initialMenuMaxDepth() {
390                                 if( ! body[0].className ) return 0;
391                                 var match = body[0].className.match(/menu-max-depth-(\d+)/);
392                                 return match && match[1] ? parseInt(match[1]) : 0;
393                         }
394
395                         function updateMenuMaxDepth( depthChange ) {
396                                 var depth, newDepth = menuMaxDepth;
397                                 if ( depthChange === 0 ) {
398                                         return;
399                                 } else if ( depthChange > 0 ) {
400                                         depth = maxChildDepth + depthChange;
401                                         if( depth > menuMaxDepth )
402                                                 newDepth = depth;
403                                 } else if ( depthChange < 0 && maxChildDepth == menuMaxDepth ) {
404                                         while( ! $('.menu-item-depth-' + newDepth, api.menuList).length && newDepth > 0 )
405                                                 newDepth--;
406                                 }
407                                 // Update the depth class.
408                                 body.removeClass( 'menu-max-depth-' + menuMaxDepth ).addClass( 'menu-max-depth-' + newDepth );
409                                 menuMaxDepth = newDepth;
410                         }
411                 },
412
413                 attachMenuEditListeners : function() {
414                         var that = this;
415                         $('#update-nav-menu').bind('click', function(e) {
416                                 if ( e.target && e.target.className ) {
417                                         if ( -1 != e.target.className.indexOf('item-edit') ) {
418                                                 return that.eventOnClickEditLink(e.target);
419                                         } else if ( -1 != e.target.className.indexOf('menu-save') ) {
420                                                 return that.eventOnClickMenuSave(e.target);
421                                         } else if ( -1 != e.target.className.indexOf('menu-delete') ) {
422                                                 return that.eventOnClickMenuDelete(e.target);
423                                         } else if ( -1 != e.target.className.indexOf('item-delete') ) {
424                                                 return that.eventOnClickMenuItemDelete(e.target);
425                                         } else if ( -1 != e.target.className.indexOf('item-cancel') ) {
426                                                 return that.eventOnClickCancelLink(e.target);
427                                         }
428                                 }
429                         });
430                 },
431
432                 /**
433                  * An interface for managing default values for input elements
434                  * that is both JS and accessibility-friendly.
435                  *
436                  * Input elements that add the class 'input-with-default-title'
437                  * will have their values set to the provided HTML title when empty.
438                  */
439                 setupInputWithDefaultTitle : function() {
440                         var name = 'input-with-default-title';
441
442                         $('.' + name).each( function(){
443                                 var $t = $(this), title = $t.attr('title'), val = $t.val();
444                                 $t.data( name, title );
445
446                                 if( '' == val ) $t.val( title );
447                                 else if ( title == val ) return;
448                                 else $t.removeClass( name );
449                         }).focus( function(){
450                                 var $t = $(this);
451                                 if( $t.val() == $t.data(name) )
452                                         $t.val('').removeClass( name );
453                         }).blur( function(){
454                                 var $t = $(this);
455                                 if( '' == $t.val() )
456                                         $t.addClass( name ).val( $t.data(name) );
457                         });
458                 },
459
460                 attachThemeLocationsListeners : function() {
461                         var loc = $('#nav-menu-theme-locations'), params = {};
462                         params['action'] = 'menu-locations-save';
463                         params['menu-settings-column-nonce'] = $('#menu-settings-column-nonce').val();
464                         loc.find('input[type="submit"]').click(function() {
465                                 loc.find('select').each(function() {
466                                         params[this.name] = $(this).val();
467                                 });
468                                 loc.find('.waiting').show();
469                                 $.post( ajaxurl, params, function(r) {
470                                         loc.find('.waiting').hide();
471                                 });
472                                 return false;
473                         });
474                 },
475
476                 attachQuickSearchListeners : function() {
477                         var searchTimer;
478
479                         $('.quick-search').keypress(function(e){
480                                 var t = $(this);
481
482                                 if( 13 == e.which ) {
483                                         api.updateQuickSearchResults( t );
484                                         return false;
485                                 }
486
487                                 if( searchTimer ) clearTimeout(searchTimer);
488
489                                 searchTimer = setTimeout(function(){
490                                         api.updateQuickSearchResults( t );
491                                 }, 400);
492                         }).attr('autocomplete','off');
493                 },
494
495                 updateQuickSearchResults : function(input) {
496                         var panel, params,
497                         minSearchLength = 2,
498                         q = input.val();
499
500                         if( q.length < minSearchLength ) return;
501
502                         panel = input.parents('.tabs-panel');
503                         params = {
504                                 'action': 'menu-quick-search',
505                                 'response-format': 'markup',
506                                 'menu': $('#menu').val(),
507                                 'menu-settings-column-nonce': $('#menu-settings-column-nonce').val(),
508                                 'q': q,
509                                 'type': input.attr('name')
510                         };
511
512                         $('img.waiting', panel).show();
513
514                         $.post( ajaxurl, params, function(menuMarkup) {
515                                 api.processQuickSearchQueryResponse(menuMarkup, params, panel);
516                         });
517                 },
518
519                 addCustomLink : function( processMethod ) {
520                         var url = $('#custom-menu-item-url').val(),
521                                 label = $('#custom-menu-item-name').val();
522
523                         processMethod = processMethod || api.addMenuItemToBottom;
524
525                         if ( '' == url || 'http://' == url )
526                                 return false;
527
528                         // Show the ajax spinner
529                         $('.customlinkdiv img.waiting').show();
530                         this.addLinkToMenu( url, label, processMethod, function() {
531                                 // Remove the ajax spinner
532                                 $('.customlinkdiv img.waiting').hide();
533                                 // Set custom link form back to defaults
534                                 $('#custom-menu-item-name').val('').blur();
535                                 $('#custom-menu-item-url').val('http://');
536                         });
537                 },
538
539                 addLinkToMenu : function(url, label, processMethod, callback) {
540                         processMethod = processMethod || api.addMenuItemToBottom;
541                         callback = callback || function(){};
542
543                         api.addItemToMenu({
544                                 '-1': {
545                                         'menu-item-type': 'custom',
546                                         'menu-item-url': url,
547                                         'menu-item-title': label
548                                 }
549                         }, processMethod, callback);
550                 },
551
552                 addItemToMenu : function(menuItem, processMethod, callback) {
553                         var menu = $('#menu').val(),
554                                 nonce = $('#menu-settings-column-nonce').val();
555
556                         processMethod = processMethod || function(){};
557                         callback = callback || function(){};
558
559                         params = {
560                                 'action': 'add-menu-item',
561                                 'menu': menu,
562                                 'menu-settings-column-nonce': nonce,
563                                 'menu-item': menuItem
564                         };
565
566                         $.post( ajaxurl, params, function(menuMarkup) {
567                                 var ins = $('#menu-instructions');
568                                 processMethod(menuMarkup, params);
569                                 if( ! ins.hasClass('menu-instructions-inactive') && ins.siblings().length )
570                                         ins.addClass('menu-instructions-inactive');
571                                 callback();
572                         });
573                 },
574
575                 /**
576                  * Process the add menu item request response into menu list item.
577                  *
578                  * @param string menuMarkup The text server response of menu item markup.
579                  * @param object req The request arguments.
580                  */
581                 addMenuItemToBottom : function( menuMarkup, req ) {
582                         $(menuMarkup).hideAdvancedMenuItemFields().appendTo( api.targetList );
583                 },
584
585                 addMenuItemToTop : function( menuMarkup, req ) {
586                         $(menuMarkup).hideAdvancedMenuItemFields().prependTo( api.targetList );
587                 },
588
589                 attachUnsavedChangesListener : function() {
590                         $('#menu-management input, #menu-management select, #menu-management, #menu-management textarea').change(function(){
591                                 api.registerChange();
592                         });
593
594                         if ( 0 != $('#menu-to-edit').length ) {
595                                 window.onbeforeunload = function(){
596                                         if ( api.menusChanged )
597                                                 return navMenuL10n.saveAlert;
598                                 };
599                         } else {
600                                 // Make the post boxes read-only, as they can't be used yet
601                                 $('#menu-settings-column').find('input,select').prop('disabled', true).end().find('a').attr('href', '#').unbind('click');
602                         }
603                 },
604
605                 registerChange : function() {
606                         api.menusChanged = true;
607                 },
608
609                 attachTabsPanelListeners : function() {
610                         $('#menu-settings-column').bind('click', function(e) {
611                                 var selectAreaMatch, panelId, wrapper, items,
612                                         target = $(e.target);
613
614                                 if ( target.hasClass('nav-tab-link') ) {
615                                         panelId = /#(.*)$/.exec(e.target.href);
616                                         if ( panelId && panelId[1] )
617                                                 panelId = panelId[1]
618                                         else
619                                                 return false;
620
621                                         wrapper = target.parents('.inside').first();
622
623                                         // upon changing tabs, we want to uncheck all checkboxes
624                                         $('input', wrapper).removeAttr('checked');
625
626                                         $('.tabs-panel-active', wrapper).removeClass('tabs-panel-active').addClass('tabs-panel-inactive');
627                                         $('#' + panelId, wrapper).removeClass('tabs-panel-inactive').addClass('tabs-panel-active');
628
629                                         $('.tabs', wrapper).removeClass('tabs');
630                                         target.parent().addClass('tabs');
631
632                                         // select the search bar
633                                         $('.quick-search', wrapper).focus();
634
635                                         return false;
636                                 } else if ( target.hasClass('select-all') ) {
637                                         selectAreaMatch = /#(.*)$/.exec(e.target.href);
638                                         if ( selectAreaMatch && selectAreaMatch[1] ) {
639                                                 items = $('#' + selectAreaMatch[1] + ' .tabs-panel-active .menu-item-title input');
640                                                 if( items.length === items.filter(':checked').length )
641                                                         items.removeAttr('checked');
642                                                 else
643                                                         items.prop('checked', true);
644                                                 return false;
645                                         }
646                                 } else if ( target.hasClass('submit-add-to-menu') ) {
647                                         api.registerChange();
648
649                                         if ( e.target.id && 'submit-customlinkdiv' == e.target.id )
650                                                 api.addCustomLink( api.addMenuItemToBottom );
651                                         else if ( e.target.id && -1 != e.target.id.indexOf('submit-') )
652                                                 $('#' + e.target.id.replace(/submit-/, '')).addSelectedToMenu( api.addMenuItemToBottom );
653                                         return false;
654                                 } else if ( target.hasClass('page-numbers') ) {
655                                         $.post( ajaxurl, e.target.href.replace(/.*\?/, '').replace(/action=([^&]*)/, '') + '&action=menu-get-metabox',
656                                                 function( resp ) {
657                                                         if ( -1 == resp.indexOf('replace-id') )
658                                                                 return;
659
660                                                         var metaBoxData = $.parseJSON(resp),
661                                                         toReplace = document.getElementById(metaBoxData['replace-id']),
662                                                         placeholder = document.createElement('div'),
663                                                         wrap = document.createElement('div');
664
665                                                         if ( ! metaBoxData['markup'] || ! toReplace )
666                                                                 return;
667
668                                                         wrap.innerHTML = metaBoxData['markup'] ? metaBoxData['markup'] : '';
669
670                                                         toReplace.parentNode.insertBefore( placeholder, toReplace );
671                                                         placeholder.parentNode.removeChild( toReplace );
672
673                                                         placeholder.parentNode.insertBefore( wrap, placeholder );
674
675                                                         placeholder.parentNode.removeChild( placeholder );
676
677                                                 }
678                                         );
679
680                                         return false;
681                                 }
682                         });
683                 },
684
685                 initTabManager : function() {
686                         var fixed = $('.nav-tabs-wrapper'),
687                                 fluid = fixed.children('.nav-tabs'),
688                                 active = fluid.children('.nav-tab-active'),
689                                 tabs = fluid.children('.nav-tab'),
690                                 tabsWidth = 0,
691                                 fixedRight, fixedLeft,
692                                 arrowLeft, arrowRight, resizeTimer, css = {},
693                                 marginFluid = api.isRTL ? 'margin-right' : 'margin-left',
694                                 marginFixed = api.isRTL ? 'margin-left' : 'margin-right',
695                                 msPerPx = 2;
696
697                         /**
698                          * Refreshes the menu tabs.
699                          * Will show and hide arrows where necessary.
700                          * Scrolls to the active tab by default.
701                          *
702                          * @param savePosition {boolean} Optional. Prevents scrolling so
703                          *                that the current position is maintained. Default false.
704                          **/
705                         api.refreshMenuTabs = function( savePosition ) {
706                                 var fixedWidth = fixed.width(),
707                                         margin = 0, css = {};
708                                 fixedLeft = fixed.offset().left;
709                                 fixedRight = fixedLeft + fixedWidth;
710
711                                 if( !savePosition )
712                                         active.makeTabVisible();
713
714                                 // Prevent space from building up next to the last tab if there's more to show
715                                 if( tabs.last().isTabVisible() ) {
716                                         margin = fixed.width() - tabsWidth;
717                                         margin = margin > 0 ? 0 : margin;
718                                         css[marginFluid] = margin + 'px';
719                                         fluid.animate( css, 100, "linear" );
720                                 }
721
722                                 // Show the arrows only when necessary
723                                 if( fixedWidth > tabsWidth )
724                                         arrowLeft.add( arrowRight ).hide();
725                                 else
726                                         arrowLeft.add( arrowRight ).show();
727                         }
728
729                         $.fn.extend({
730                                 makeTabVisible : function() {
731                                         var t = this.eq(0), left, right, css = {}, shift = 0;
732
733                                         if( ! t.length ) return this;
734
735                                         left = t.offset().left;
736                                         right = left + t.outerWidth();
737
738                                         if( right > fixedRight )
739                                                 shift = fixedRight - right;
740                                         else if ( left < fixedLeft )
741                                                 shift = fixedLeft - left;
742
743                                         if( ! shift ) return this;
744
745                                         css[marginFluid] = "+=" + api.negateIfRTL * shift + 'px';
746                                         fluid.animate( css, Math.abs( shift ) * msPerPx, "linear" );
747                                         return this;
748                                 },
749                                 isTabVisible : function() {
750                                         var t = this.eq(0),
751                                                 left = t.offset().left,
752                                                 right = left + t.outerWidth();
753                                         return ( right <= fixedRight && left >= fixedLeft ) ? true : false;
754                                 }
755                         });
756
757                         // Find the width of all tabs
758                         tabs.each(function(){
759                                 tabsWidth += $(this).outerWidth(true);
760                         });
761
762                         // Set up fixed margin for overflow, unset padding
763                         css['padding'] = 0;
764                         css[marginFixed] = (-1 * tabsWidth) + 'px';
765                         fluid.css( css );
766
767                         // Build tab navigation
768                         arrowLeft = $('<div class="nav-tabs-arrow nav-tabs-arrow-left"><a>&laquo;</a></div>');
769                         arrowRight = $('<div class="nav-tabs-arrow nav-tabs-arrow-right"><a>&raquo;</a></div>');
770                         // Attach to the document
771                         fixed.wrap('<div class="nav-tabs-nav"/>').parent().prepend( arrowLeft ).append( arrowRight );
772
773                         // Set the menu tabs
774                         api.refreshMenuTabs();
775                         // Make sure the tabs reset on resize
776                         $(window).resize(function() {
777                                 if( resizeTimer ) clearTimeout(resizeTimer);
778                                 resizeTimer = setTimeout( api.refreshMenuTabs, 200);
779                         });
780
781                         // Build arrow functions
782                         $.each([{
783                                         arrow : arrowLeft,
784                                         next : "next",
785                                         last : "first",
786                                         operator : "+="
787                                 },{
788                                         arrow : arrowRight,
789                                         next : "prev",
790                                         last : "last",
791                                         operator : "-="
792                                 }], function(){
793                                 var that = this;
794                                 this.arrow.mousedown(function(){
795                                         var marginFluidVal = Math.abs( parseInt( fluid.css(marginFluid) ) ),
796                                                 shift = marginFluidVal,
797                                                 css = {};
798
799                                         if( "-=" == that.operator )
800                                                 shift = Math.abs( tabsWidth - fixed.width() ) - marginFluidVal;
801
802                                         if( ! shift ) return;
803
804                                         css[marginFluid] = that.operator + shift + 'px';
805                                         fluid.animate( css, shift * msPerPx, "linear" );
806                                 }).mouseup(function(){
807                                         var tab, next;
808                                         fluid.stop(true);
809                                         tab = tabs[that.last]();
810                                         while( (next = tab[that.next]()) && next.length && ! next.isTabVisible() ) {
811                                                 tab = next;
812                                         }
813                                         tab.makeTabVisible();
814                                 });
815                         });
816                 },
817
818                 eventOnClickEditLink : function(clickedEl) {
819                         var settings, item,
820                         matchedSection = /#(.*)$/.exec(clickedEl.href);
821                         if ( matchedSection && matchedSection[1] ) {
822                                 settings = $('#'+matchedSection[1]);
823                                 item = settings.parent();
824                                 if( 0 != item.length ) {
825                                         if( item.hasClass('menu-item-edit-inactive') ) {
826                                                 if( ! settings.data('menu-item-data') ) {
827                                                         settings.data( 'menu-item-data', settings.getItemData() );
828                                                 }
829                                                 settings.slideDown('fast');
830                                                 item.removeClass('menu-item-edit-inactive')
831                                                         .addClass('menu-item-edit-active');
832                                         } else {
833                                                 settings.slideUp('fast');
834                                                 item.removeClass('menu-item-edit-active')
835                                                         .addClass('menu-item-edit-inactive');
836                                         }
837                                         return false;
838                                 }
839                         }
840                 },
841
842                 eventOnClickCancelLink : function(clickedEl) {
843                         var settings = $(clickedEl).closest('.menu-item-settings');
844                         settings.setItemData( settings.data('menu-item-data') );
845                         return false;
846                 },
847
848                 eventOnClickMenuSave : function(clickedEl) {
849                         var locs = '',
850                         menuName = $('#menu-name'),
851                         menuNameVal = menuName.val();
852                         // Cancel and warn if invalid menu name
853                         if( !menuNameVal || menuNameVal == menuName.attr('title') || !menuNameVal.replace(/\s+/, '') ) {
854                                 menuName.parent().addClass('form-invalid');
855                                 return false;
856                         }
857                         // Copy menu theme locations
858                         $('#nav-menu-theme-locations select').each(function() {
859                                 locs += '<input type="hidden" name="' + this.name + '" value="' + $(this).val() + '" />';
860                         });
861                         $('#update-nav-menu').append( locs );
862                         // Update menu item position data
863                         api.menuList.find('.menu-item-data-position').val( function(index) { return index + 1; } );
864                         window.onbeforeunload = null;
865
866                         return true;
867                 },
868
869                 eventOnClickMenuDelete : function(clickedEl) {
870                         // Delete warning AYS
871                         if ( confirm( navMenuL10n.warnDeleteMenu ) ) {
872                                 window.onbeforeunload = null;
873                                 return true;
874                         }
875                         return false;
876                 },
877
878                 eventOnClickMenuItemDelete : function(clickedEl) {
879                         var itemID = parseInt(clickedEl.id.replace('delete-', ''), 10);
880                         api.removeMenuItem( $('#menu-item-' + itemID) );
881                         api.registerChange();
882                         return false;
883                 },
884
885                 /**
886                  * Process the quick search response into a search result
887                  *
888                  * @param string resp The server response to the query.
889                  * @param object req The request arguments.
890                  * @param jQuery panel The tabs panel we're searching in.
891                  */
892                 processQuickSearchQueryResponse : function(resp, req, panel) {
893                         var matched, newID,
894                         takenIDs = {},
895                         form = document.getElementById('nav-menu-meta'),
896                         pattern = new RegExp('menu-item\\[(\[^\\]\]*)', 'g'),
897                         $items = $('<div>').html(resp).find('li'),
898                         $item;
899
900                         if( ! $items.length ) {
901                                 $('.categorychecklist', panel).html( '<li><p>' + navMenuL10n.noResultsFound + '</p></li>' );
902                                 $('img.waiting', panel).hide();
903                                 return;
904                         }
905
906                         $items.each(function(){
907                                 $item = $(this);
908
909                                 // make a unique DB ID number
910                                 matched = pattern.exec($item.html());
911
912                                 if ( matched && matched[1] ) {
913                                         newID = matched[1];
914                                         while( form.elements['menu-item[' + newID + '][menu-item-type]'] || takenIDs[ newID ] ) {
915                                                 newID--;
916                                         }
917
918                                         takenIDs[newID] = true;
919                                         if ( newID != matched[1] ) {
920                                                 $item.html( $item.html().replace(new RegExp(
921                                                         'menu-item\\[' + matched[1] + '\\]', 'g'),
922                                                         'menu-item[' + newID + ']'
923                                                 ) );
924                                         }
925                                 }
926                         });
927
928                         $('.categorychecklist', panel).html( $items );
929                         $('img.waiting', panel).hide();
930                 },
931
932                 removeMenuItem : function(el) {
933                         var children = el.childMenuItems();
934
935                         el.addClass('deleting').animate({
936                                         opacity : 0,
937                                         height: 0
938                                 }, 350, function() {
939                                         var ins = $('#menu-instructions');
940                                         el.remove();
941                                         children.shiftDepthClass(-1).updateParentMenuItemDBId();
942                                         if( ! ins.siblings().length )
943                                                 ins.removeClass('menu-instructions-inactive');
944                                 });
945                 },
946
947                 depthToPx : function(depth) {
948                         return depth * api.options.menuItemDepthPerLevel;
949                 },
950
951                 pxToDepth : function(px) {
952                         return Math.floor(px / api.options.menuItemDepthPerLevel);
953                 }
954
955         };
956
957         $(document).ready(function(){ wpNavMenu.init(); });
958
959 })(jQuery);