2 * WordPress Administration Navigation Menu
3 * Interface JS functions
8 * @subpackage Administration
15 var api = wpNavMenu = {
18 menuItemDepthPerLevel : 30, // Do not use directly. Use depthToPx and pxToDepth instead.
22 menuList : undefined, // Set in init.
23 targetList : undefined, // Set in init.
25 isRTL: !! ( 'undefined' != typeof isRtl && isRtl ),
26 negateIfRTL: ( 'undefined' != typeof isRtl && isRtl ) ? -1 : 1,
28 // Functions that run on init.
30 api.menuList = $('#menu-to-edit');
31 api.targetList = api.menuList;
33 this.jQueryExtensions();
35 this.attachMenuEditListeners();
37 this.setupInputWithDefaultTitle();
38 this.attachQuickSearchListeners();
39 this.attachThemeLocationsListeners();
41 this.attachTabsPanelListeners();
43 this.attachUnsavedChangesListener();
45 if( api.menuList.length ) // If no menu, we're in the + tab.
50 this.initTabManager();
53 jQueryExtensions : function() {
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 );
60 updateDepthClass : function(current, prev) {
61 return this.each(function(){
63 prev = prev || t.menuItemDepth();
64 $(this).removeClass('menu-item-depth-'+ prev )
65 .addClass('menu-item-depth-'+ current );
68 shiftDepthClass : function(change) {
69 return this.each(function(){
71 depth = t.menuItemDepth();
72 $(this).removeClass('menu-item-depth-'+ depth )
73 .addClass('menu-item-depth-'+ (depth + change) );
76 childMenuItems : function() {
79 var t = $(this), depth = t.menuItemDepth(), next = t.next();
80 while( next.length && next.menuItemDepth() > depth ) {
81 result = result.add( next );
87 updateParentMenuItemDBId : function() {
88 return this.each(function(){
90 input = item.find('.menu-item-data-parent-id'),
91 depth = item.menuItemDepth(),
94 if( depth == 0 ) { // Item is on the top level, has no parent
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() );
103 hideAdvancedMenuItemFields : function() {
104 return this.each(function(){
106 $('.hide-column-tog').not(':checked').each(function(){
107 that.find('.field-' + $(this).val() ).addClass('hidden-field');
112 * Adds selected menu items to the menu.
114 * @param jQuery metabox The metabox jQuery object.
116 addSelectedToMenu : function(processMethod) {
117 if ( 0 == $('#menu-to-edit').length ) {
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\\[(\[^\\]\]*)');
126 processMethod = processMethod || api.addMenuItemToBottom;
128 // If no items are checked, bail.
129 if ( !checkboxes.length )
132 // Show the ajax spinner
133 t.find('.spinner').show();
135 // Retrieve menu item data
136 $(checkboxes).each(function(){
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 );
146 api.addItemToMenu(menuItems, processMethod, function(){
147 // Deselect the items and hide the ajax spinner
148 checkboxes.removeAttr('checked');
149 t.find('.spinner').hide();
153 getItemData : function( itemType, id ) {
154 itemType = itemType || 'menu-item';
156 var itemData = {}, i,
159 'menu-item-object-id',
161 'menu-item-parent-id',
162 'menu-item-position',
166 'menu-item-description',
167 'menu-item-attr-title',
173 if( !id && itemType == 'menu-item' ) {
174 id = this.find('.menu-item-data-db-id').val();
177 if( !id ) return itemData;
179 this.find('input').each(function() {
183 if( itemType == 'menu-item' )
184 field = fields[i] + '[' + id + ']';
185 else if( itemType == 'add-menu-item' )
186 field = 'menu-item[' + id + '][' + fields[i] + ']';
192 itemData[fields[i]] = this.value;
199 setItemData : function( itemData, itemType, id ) { // Can take a type, such as 'menu-item', or an id.
200 itemType = itemType || 'menu-item';
202 if( !id && itemType == 'menu-item' ) {
203 id = $('.menu-item-data-db-id', this).val();
206 if( !id ) return this;
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 + ']';
216 if ( field == t.attr('name') ) {
226 initToggles : function() {
228 postboxes.add_postbox_toggles('nav-menus');
230 // adjust columns functions for menus UI
231 columns.useCheckboxesForHidden();
232 columns.checked = function(field) {
233 $('.field-' + field).removeClass('hidden-field');
235 columns.unchecked = function(field) {
236 $('.field-' + field).addClass('hidden-field');
239 api.menuList.hideAdvancedMenuItemFields();
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();
249 // Use the right edge if RTL.
250 menuEdge += api.isRTL ? api.menuList.width() : 0;
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;
258 // handle placement for rtl orientation
260 ui.item[0].style.right = 'auto';
262 transport = ui.item.children('.menu-item-transport');
264 // Set depths. currentDepth must be set before children are located.
265 originalDepth = ui.item.menuItemDepth();
266 updateCurrentDepth(ui, originalDepth);
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 );
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);
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;
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);
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
302 // Now that the element is complete, we can update...
303 updateSharedVars(ui);
305 stop: function(e, ui) {
306 var children, depthChange = currentDepth - originalDepth;
308 // Return child elements to the list
309 children = transport.children().insertAfter(ui.item);
311 // Update depth classes
312 if( depthChange != 0 ) {
313 ui.item.updateDepthClass( currentDepth );
314 children.shiftDepthClass( depthChange );
315 updateMenuMaxDepth( depthChange );
318 api.registerChange();
319 // Update the item data.
320 ui.item.updateParentMenuItemDBId();
322 // address sortable's incorrectly-calculated top in opera
323 ui.item[0].style.top = 0;
325 // handle drop placement for rtl orientation
327 ui.item[0].style.left = 'auto';
328 ui.item[0].style.right = 0;
331 // The width of the tab bar might have changed. Just in case.
332 api.refreshMenuTabs( true );
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 );
340 updateSharedVars(ui);
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;
352 if( depth != currentDepth )
353 updateCurrentDepth(ui, depth);
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" );
364 function updateSharedVars(ui) {
367 prev = ui.placeholder.prev();
368 next = ui.placeholder.next();
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();
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;
379 maxDepth = ( (depth = prev.menuItemDepth() + 1) > api.options.globalMaxDepth ) ? api.options.globalMaxDepth : depth;
384 function updateCurrentDepth(ui, depth) {
385 ui.placeholder.updateDepthClass( depth, currentDepth );
386 currentDepth = depth;
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;
395 function updateMenuMaxDepth( depthChange ) {
396 var depth, newDepth = menuMaxDepth;
397 if ( depthChange === 0 ) {
399 } else if ( depthChange > 0 ) {
400 depth = maxChildDepth + depthChange;
401 if( depth > menuMaxDepth )
403 } else if ( depthChange < 0 && maxChildDepth == menuMaxDepth ) {
404 while( ! $('.menu-item-depth-' + newDepth, api.menuList).length && newDepth > 0 )
407 // Update the depth class.
408 body.removeClass( 'menu-max-depth-' + menuMaxDepth ).addClass( 'menu-max-depth-' + newDepth );
409 menuMaxDepth = newDepth;
413 attachMenuEditListeners : function() {
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);
430 $('#add-custom-links input[type="text"]').keypress(function(e){
431 if ( e.keyCode === 13 ) {
433 $("#submit-customlinkdiv").click();
439 * An interface for managing default values for input elements
440 * that is both JS and accessibility-friendly.
442 * Input elements that add the class 'input-with-default-title'
443 * will have their values set to the provided HTML title when empty.
445 setupInputWithDefaultTitle : function() {
446 var name = 'input-with-default-title';
448 $('.' + name).each( function(){
449 var $t = $(this), title = $t.attr('title'), val = $t.val();
450 $t.data( name, title );
452 if( '' == val ) $t.val( title );
453 else if ( title == val ) return;
454 else $t.removeClass( name );
455 }).focus( function(){
457 if( $t.val() == $t.data(name) )
458 $t.val('').removeClass( name );
462 $t.addClass( name ).val( $t.data(name) );
466 attachThemeLocationsListeners : function() {
467 var loc = $('#nav-menu-theme-locations'), params = {};
468 params['action'] = 'menu-locations-save';
469 params['menu-settings-column-nonce'] = $('#menu-settings-column-nonce').val();
470 loc.find('input[type="submit"]').click(function() {
471 loc.find('select').each(function() {
472 params[this.name] = $(this).val();
474 loc.find('.spinner').show();
475 $.post( ajaxurl, params, function(r) {
476 loc.find('.spinner').hide();
482 attachQuickSearchListeners : function() {
485 $('.quick-search').keypress(function(e){
488 if( 13 == e.which ) {
489 api.updateQuickSearchResults( t );
493 if( searchTimer ) clearTimeout(searchTimer);
495 searchTimer = setTimeout(function(){
496 api.updateQuickSearchResults( t );
498 }).attr('autocomplete','off');
501 updateQuickSearchResults : function(input) {
506 if( q.length < minSearchLength ) return;
508 panel = input.parents('.tabs-panel');
510 'action': 'menu-quick-search',
511 'response-format': 'markup',
512 'menu': $('#menu').val(),
513 'menu-settings-column-nonce': $('#menu-settings-column-nonce').val(),
515 'type': input.attr('name')
518 $('.spinner', panel).show();
520 $.post( ajaxurl, params, function(menuMarkup) {
521 api.processQuickSearchQueryResponse(menuMarkup, params, panel);
525 addCustomLink : function( processMethod ) {
526 var url = $('#custom-menu-item-url').val(),
527 label = $('#custom-menu-item-name').val();
529 processMethod = processMethod || api.addMenuItemToBottom;
531 if ( '' == url || 'http://' == url )
534 // Show the ajax spinner
535 $('.customlinkdiv .spinner').show();
536 this.addLinkToMenu( url, label, processMethod, function() {
537 // Remove the ajax spinner
538 $('.customlinkdiv .spinner').hide();
539 // Set custom link form back to defaults
540 $('#custom-menu-item-name').val('').blur();
541 $('#custom-menu-item-url').val('http://');
545 addLinkToMenu : function(url, label, processMethod, callback) {
546 processMethod = processMethod || api.addMenuItemToBottom;
547 callback = callback || function(){};
551 'menu-item-type': 'custom',
552 'menu-item-url': url,
553 'menu-item-title': label
555 }, processMethod, callback);
558 addItemToMenu : function(menuItem, processMethod, callback) {
559 var menu = $('#menu').val(),
560 nonce = $('#menu-settings-column-nonce').val();
562 processMethod = processMethod || function(){};
563 callback = callback || function(){};
566 'action': 'add-menu-item',
568 'menu-settings-column-nonce': nonce,
569 'menu-item': menuItem
572 $.post( ajaxurl, params, function(menuMarkup) {
573 var ins = $('#menu-instructions');
574 processMethod(menuMarkup, params);
575 if( ! ins.hasClass('menu-instructions-inactive') && ins.siblings().length )
576 ins.addClass('menu-instructions-inactive');
582 * Process the add menu item request response into menu list item.
584 * @param string menuMarkup The text server response of menu item markup.
585 * @param object req The request arguments.
587 addMenuItemToBottom : function( menuMarkup, req ) {
588 $(menuMarkup).hideAdvancedMenuItemFields().appendTo( api.targetList );
591 addMenuItemToTop : function( menuMarkup, req ) {
592 $(menuMarkup).hideAdvancedMenuItemFields().prependTo( api.targetList );
595 attachUnsavedChangesListener : function() {
596 $('#menu-management input, #menu-management select, #menu-management, #menu-management textarea').change(function(){
597 api.registerChange();
600 if ( 0 != $('#menu-to-edit').length ) {
601 window.onbeforeunload = function(){
602 if ( api.menusChanged )
603 return navMenuL10n.saveAlert;
606 // Make the post boxes read-only, as they can't be used yet
607 $('#menu-settings-column').find('input,select').prop('disabled', true).end().find('a').attr('href', '#').unbind('click');
611 registerChange : function() {
612 api.menusChanged = true;
615 attachTabsPanelListeners : function() {
616 $('#menu-settings-column').bind('click', function(e) {
617 var selectAreaMatch, panelId, wrapper, items,
618 target = $(e.target);
620 if ( target.hasClass('nav-tab-link') ) {
621 panelId = /#(.*)$/.exec(e.target.href);
622 if ( panelId && panelId[1] )
627 wrapper = target.parents('.inside').first();
629 // upon changing tabs, we want to uncheck all checkboxes
630 $('input', wrapper).removeAttr('checked');
632 $('.tabs-panel-active', wrapper).removeClass('tabs-panel-active').addClass('tabs-panel-inactive');
633 $('#' + panelId, wrapper).removeClass('tabs-panel-inactive').addClass('tabs-panel-active');
635 $('.tabs', wrapper).removeClass('tabs');
636 target.parent().addClass('tabs');
638 // select the search bar
639 $('.quick-search', wrapper).focus();
642 } else if ( target.hasClass('select-all') ) {
643 selectAreaMatch = /#(.*)$/.exec(e.target.href);
644 if ( selectAreaMatch && selectAreaMatch[1] ) {
645 items = $('#' + selectAreaMatch[1] + ' .tabs-panel-active .menu-item-title input');
646 if( items.length === items.filter(':checked').length )
647 items.removeAttr('checked');
649 items.prop('checked', true);
652 } else if ( target.hasClass('submit-add-to-menu') ) {
653 api.registerChange();
655 if ( e.target.id && 'submit-customlinkdiv' == e.target.id )
656 api.addCustomLink( api.addMenuItemToBottom );
657 else if ( e.target.id && -1 != e.target.id.indexOf('submit-') )
658 $('#' + e.target.id.replace(/submit-/, '')).addSelectedToMenu( api.addMenuItemToBottom );
660 } else if ( target.hasClass('page-numbers') ) {
661 $.post( ajaxurl, e.target.href.replace(/.*\?/, '').replace(/action=([^&]*)/, '') + '&action=menu-get-metabox',
663 if ( -1 == resp.indexOf('replace-id') )
666 var metaBoxData = $.parseJSON(resp),
667 toReplace = document.getElementById(metaBoxData['replace-id']),
668 placeholder = document.createElement('div'),
669 wrap = document.createElement('div');
671 if ( ! metaBoxData['markup'] || ! toReplace )
674 wrap.innerHTML = metaBoxData['markup'] ? metaBoxData['markup'] : '';
676 toReplace.parentNode.insertBefore( placeholder, toReplace );
677 placeholder.parentNode.removeChild( toReplace );
679 placeholder.parentNode.insertBefore( wrap, placeholder );
681 placeholder.parentNode.removeChild( placeholder );
691 initTabManager : function() {
692 var fixed = $('.nav-tabs-wrapper'),
693 fluid = fixed.children('.nav-tabs'),
694 active = fluid.children('.nav-tab-active'),
695 tabs = fluid.children('.nav-tab'),
697 fixedRight, fixedLeft,
698 arrowLeft, arrowRight, resizeTimer, css = {},
699 marginFluid = api.isRTL ? 'margin-right' : 'margin-left',
700 marginFixed = api.isRTL ? 'margin-left' : 'margin-right',
704 * Refreshes the menu tabs.
705 * Will show and hide arrows where necessary.
706 * Scrolls to the active tab by default.
708 * @param savePosition {boolean} Optional. Prevents scrolling so
709 * that the current position is maintained. Default false.
711 api.refreshMenuTabs = function( savePosition ) {
712 var fixedWidth = fixed.width(),
713 margin = 0, css = {};
714 fixedLeft = fixed.offset().left;
715 fixedRight = fixedLeft + fixedWidth;
718 active.makeTabVisible();
720 // Prevent space from building up next to the last tab if there's more to show
721 if( tabs.last().isTabVisible() ) {
722 margin = fixed.width() - tabsWidth;
723 margin = margin > 0 ? 0 : margin;
724 css[marginFluid] = margin + 'px';
725 fluid.animate( css, 100, "linear" );
728 // Show the arrows only when necessary
729 if( fixedWidth > tabsWidth )
730 arrowLeft.add( arrowRight ).hide();
732 arrowLeft.add( arrowRight ).show();
736 makeTabVisible : function() {
737 var t = this.eq(0), left, right, css = {}, shift = 0;
739 if( ! t.length ) return this;
741 left = t.offset().left;
742 right = left + t.outerWidth();
744 if( right > fixedRight )
745 shift = fixedRight - right;
746 else if ( left < fixedLeft )
747 shift = fixedLeft - left;
749 if( ! shift ) return this;
751 css[marginFluid] = "+=" + api.negateIfRTL * shift + 'px';
752 fluid.animate( css, Math.abs( shift ) * msPerPx, "linear" );
755 isTabVisible : function() {
757 left = t.offset().left,
758 right = left + t.outerWidth();
759 return ( right <= fixedRight && left >= fixedLeft ) ? true : false;
763 // Find the width of all tabs
764 tabs.each(function(){
765 tabsWidth += $(this).outerWidth(true);
768 // Set up fixed margin for overflow, unset padding
770 css[marginFixed] = (-1 * tabsWidth) + 'px';
773 // Build tab navigation
774 arrowLeft = $('<div class="nav-tabs-arrow nav-tabs-arrow-left"><a>«</a></div>');
775 arrowRight = $('<div class="nav-tabs-arrow nav-tabs-arrow-right"><a>»</a></div>');
776 // Attach to the document
777 fixed.wrap('<div class="nav-tabs-nav"/>').parent().prepend( arrowLeft ).append( arrowRight );
780 api.refreshMenuTabs();
781 // Make sure the tabs reset on resize
782 $(window).resize(function() {
783 if( resizeTimer ) clearTimeout(resizeTimer);
784 resizeTimer = setTimeout( api.refreshMenuTabs, 200);
787 // Build arrow functions
800 this.arrow.mousedown(function(){
801 var marginFluidVal = Math.abs( parseInt( fluid.css(marginFluid) ) ),
802 shift = marginFluidVal,
805 if( "-=" == that.operator )
806 shift = Math.abs( tabsWidth - fixed.width() ) - marginFluidVal;
808 if( ! shift ) return;
810 css[marginFluid] = that.operator + shift + 'px';
811 fluid.animate( css, shift * msPerPx, "linear" );
812 }).mouseup(function(){
815 tab = tabs[that.last]();
816 while( (next = tab[that.next]()) && next.length && ! next.isTabVisible() ) {
819 tab.makeTabVisible();
824 eventOnClickEditLink : function(clickedEl) {
826 matchedSection = /#(.*)$/.exec(clickedEl.href);
827 if ( matchedSection && matchedSection[1] ) {
828 settings = $('#'+matchedSection[1]);
829 item = settings.parent();
830 if( 0 != item.length ) {
831 if( item.hasClass('menu-item-edit-inactive') ) {
832 if( ! settings.data('menu-item-data') ) {
833 settings.data( 'menu-item-data', settings.getItemData() );
835 settings.slideDown('fast');
836 item.removeClass('menu-item-edit-inactive')
837 .addClass('menu-item-edit-active');
839 settings.slideUp('fast');
840 item.removeClass('menu-item-edit-active')
841 .addClass('menu-item-edit-inactive');
848 eventOnClickCancelLink : function(clickedEl) {
849 var settings = $(clickedEl).closest('.menu-item-settings');
850 settings.setItemData( settings.data('menu-item-data') );
854 eventOnClickMenuSave : function(clickedEl) {
856 menuName = $('#menu-name'),
857 menuNameVal = menuName.val();
858 // Cancel and warn if invalid menu name
859 if( !menuNameVal || menuNameVal == menuName.attr('title') || !menuNameVal.replace(/\s+/, '') ) {
860 menuName.parent().addClass('form-invalid');
863 // Copy menu theme locations
864 $('#nav-menu-theme-locations select').each(function() {
865 locs += '<input type="hidden" name="' + this.name + '" value="' + $(this).val() + '" />';
867 $('#update-nav-menu').append( locs );
868 // Update menu item position data
869 api.menuList.find('.menu-item-data-position').val( function(index) { return index + 1; } );
870 window.onbeforeunload = null;
875 eventOnClickMenuDelete : function(clickedEl) {
876 // Delete warning AYS
877 if ( confirm( navMenuL10n.warnDeleteMenu ) ) {
878 window.onbeforeunload = null;
884 eventOnClickMenuItemDelete : function(clickedEl) {
885 var itemID = parseInt(clickedEl.id.replace('delete-', ''), 10);
886 api.removeMenuItem( $('#menu-item-' + itemID) );
887 api.registerChange();
892 * Process the quick search response into a search result
894 * @param string resp The server response to the query.
895 * @param object req The request arguments.
896 * @param jQuery panel The tabs panel we're searching in.
898 processQuickSearchQueryResponse : function(resp, req, panel) {
901 form = document.getElementById('nav-menu-meta'),
902 pattern = new RegExp('menu-item\\[(\[^\\]\]*)', 'g'),
903 $items = $('<div>').html(resp).find('li'),
906 if( ! $items.length ) {
907 $('.categorychecklist', panel).html( '<li><p>' + navMenuL10n.noResultsFound + '</p></li>' );
908 $('.spinner', panel).hide();
912 $items.each(function(){
915 // make a unique DB ID number
916 matched = pattern.exec($item.html());
918 if ( matched && matched[1] ) {
920 while( form.elements['menu-item[' + newID + '][menu-item-type]'] || takenIDs[ newID ] ) {
924 takenIDs[newID] = true;
925 if ( newID != matched[1] ) {
926 $item.html( $item.html().replace(new RegExp(
927 'menu-item\\[' + matched[1] + '\\]', 'g'),
928 'menu-item[' + newID + ']'
934 $('.categorychecklist', panel).html( $items );
935 $('.spinner', panel).hide();
938 removeMenuItem : function(el) {
939 var children = el.childMenuItems();
941 el.addClass('deleting').animate({
945 var ins = $('#menu-instructions');
947 children.shiftDepthClass(-1).updateParentMenuItemDBId();
948 if( ! ins.siblings().length )
949 ins.removeClass('menu-instructions-inactive');
953 depthToPx : function(depth) {
954 return depth * api.options.menuItemDepthPerLevel;
957 pxToDepth : function(px) {
958 return Math.floor(px / api.options.menuItemDepthPerLevel);
963 $(document).ready(function(){ wpNavMenu.init(); });