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('img.waiting').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('img.waiting').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);
433 * An interface for managing default values for input elements
434 * that is both JS and accessibility-friendly.
436 * Input elements that add the class 'input-with-default-title'
437 * will have their values set to the provided HTML title when empty.
439 setupInputWithDefaultTitle : function() {
440 var name = 'input-with-default-title';
442 $('.' + name).each( function(){
443 var $t = $(this), title = $t.attr('title'), val = $t.val();
444 $t.data( name, title );
446 if( '' == val ) $t.val( title );
447 else if ( title == val ) return;
448 else $t.removeClass( name );
449 }).focus( function(){
451 if( $t.val() == $t.data(name) )
452 $t.val('').removeClass( name );
456 $t.addClass( name ).val( $t.data(name) );
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();
468 loc.find('.waiting').show();
469 $.post( ajaxurl, params, function(r) {
470 loc.find('.waiting').hide();
476 attachQuickSearchListeners : function() {
479 $('.quick-search').keypress(function(e){
482 if( 13 == e.which ) {
483 api.updateQuickSearchResults( t );
487 if( searchTimer ) clearTimeout(searchTimer);
489 searchTimer = setTimeout(function(){
490 api.updateQuickSearchResults( t );
492 }).attr('autocomplete','off');
495 updateQuickSearchResults : function(input) {
500 if( q.length < minSearchLength ) return;
502 panel = input.parents('.tabs-panel');
504 'action': 'menu-quick-search',
505 'response-format': 'markup',
506 'menu': $('#menu').val(),
507 'menu-settings-column-nonce': $('#menu-settings-column-nonce').val(),
509 'type': input.attr('name')
512 $('img.waiting', panel).show();
514 $.post( ajaxurl, params, function(menuMarkup) {
515 api.processQuickSearchQueryResponse(menuMarkup, params, panel);
519 addCustomLink : function( processMethod ) {
520 var url = $('#custom-menu-item-url').val(),
521 label = $('#custom-menu-item-name').val();
523 processMethod = processMethod || api.addMenuItemToBottom;
525 if ( '' == url || 'http://' == url )
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://');
539 addLinkToMenu : function(url, label, processMethod, callback) {
540 processMethod = processMethod || api.addMenuItemToBottom;
541 callback = callback || function(){};
545 'menu-item-type': 'custom',
546 'menu-item-url': url,
547 'menu-item-title': label
549 }, processMethod, callback);
552 addItemToMenu : function(menuItem, processMethod, callback) {
553 var menu = $('#menu').val(),
554 nonce = $('#menu-settings-column-nonce').val();
556 processMethod = processMethod || function(){};
557 callback = callback || function(){};
560 'action': 'add-menu-item',
562 'menu-settings-column-nonce': nonce,
563 'menu-item': menuItem
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');
576 * Process the add menu item request response into menu list item.
578 * @param string menuMarkup The text server response of menu item markup.
579 * @param object req The request arguments.
581 addMenuItemToBottom : function( menuMarkup, req ) {
582 $(menuMarkup).hideAdvancedMenuItemFields().appendTo( api.targetList );
585 addMenuItemToTop : function( menuMarkup, req ) {
586 $(menuMarkup).hideAdvancedMenuItemFields().prependTo( api.targetList );
589 attachUnsavedChangesListener : function() {
590 $('#menu-management input, #menu-management select, #menu-management, #menu-management textarea').change(function(){
591 api.registerChange();
594 if ( 0 != $('#menu-to-edit').length ) {
595 window.onbeforeunload = function(){
596 if ( api.menusChanged )
597 return navMenuL10n.saveAlert;
600 // Make the post boxes read-only, as they can't be used yet
601 $('#menu-settings-column').find('input,select').attr('disabled', 'disabled').end().find('a').attr('href', '#').unbind('click');
605 registerChange : function() {
606 api.menusChanged = true;
609 attachTabsPanelListeners : function() {
610 $('#menu-settings-column').bind('click', function(e) {
611 var selectAreaMatch, panelId, wrapper, items,
612 target = $(e.target);
614 if ( target.hasClass('nav-tab-link') ) {
615 panelId = /#(.*)$/.exec(e.target.href);
616 if ( panelId && panelId[1] )
621 wrapper = target.parents('.inside').first();
623 // upon changing tabs, we want to uncheck all checkboxes
624 $('input', wrapper).removeAttr('checked');
626 $('.tabs-panel-active', wrapper).removeClass('tabs-panel-active').addClass('tabs-panel-inactive');
627 $('#' + panelId, wrapper).removeClass('tabs-panel-inactive').addClass('tabs-panel-active');
629 $('.tabs', wrapper).removeClass('tabs');
630 target.parent().addClass('tabs');
632 // select the search bar
633 $('.quick-search', wrapper).focus();
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');
643 items.attr('checked', 'checked');
646 } else if ( target.hasClass('submit-add-to-menu') ) {
647 api.registerChange();
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 );
654 } else if ( target.hasClass('page-numbers') ) {
655 $.post( ajaxurl, e.target.href.replace(/.*\?/, '').replace(/action=([^&]*)/, '') + '&action=menu-get-metabox',
657 if ( -1 == resp.indexOf('replace-id') )
660 var metaBoxData = $.parseJSON(resp),
661 toReplace = document.getElementById(metaBoxData['replace-id']),
662 placeholder = document.createElement('div'),
663 wrap = document.createElement('div');
665 if ( ! metaBoxData['markup'] || ! toReplace )
668 wrap.innerHTML = metaBoxData['markup'] ? metaBoxData['markup'] : '';
670 toReplace.parentNode.insertBefore( placeholder, toReplace );
671 placeholder.parentNode.removeChild( toReplace );
673 placeholder.parentNode.insertBefore( wrap, placeholder );
675 placeholder.parentNode.removeChild( placeholder );
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'),
691 fixedRight, fixedLeft,
692 arrowLeft, arrowRight, resizeTimer, css = {},
693 marginFluid = api.isRTL ? 'margin-right' : 'margin-left',
694 marginFixed = api.isRTL ? 'margin-left' : 'margin-right',
698 * Refreshes the menu tabs.
699 * Will show and hide arrows where necessary.
700 * Scrolls to the active tab by default.
702 * @param savePosition {boolean} Optional. Prevents scrolling so
703 * that the current position is maintained. Default false.
705 api.refreshMenuTabs = function( savePosition ) {
706 var fixedWidth = fixed.width(),
707 margin = 0, css = {};
708 fixedLeft = fixed.offset().left;
709 fixedRight = fixedLeft + fixedWidth;
712 active.makeTabVisible();
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" );
722 // Show the arrows only when necessary
723 if( fixedWidth > tabsWidth )
724 arrowLeft.add( arrowRight ).hide();
726 arrowLeft.add( arrowRight ).show();
730 makeTabVisible : function() {
731 var t = this.eq(0), left, right, css = {}, shift = 0;
733 if( ! t.length ) return this;
735 left = t.offset().left;
736 right = left + t.outerWidth();
738 if( right > fixedRight )
739 shift = fixedRight - right;
740 else if ( left < fixedLeft )
741 shift = fixedLeft - left;
743 if( ! shift ) return this;
745 css[marginFluid] = "+=" + api.negateIfRTL * shift + 'px';
746 fluid.animate( css, Math.abs( shift ) * msPerPx, "linear" );
749 isTabVisible : function() {
751 left = t.offset().left,
752 right = left + t.outerWidth();
753 return ( right <= fixedRight && left >= fixedLeft ) ? true : false;
757 // Find the width of all tabs
758 tabs.each(function(){
759 tabsWidth += $(this).outerWidth(true);
762 // Set up fixed margin for overflow, unset padding
764 css[marginFixed] = (-1 * tabsWidth) + 'px';
767 // Build tab navigation
768 arrowLeft = $('<div class="nav-tabs-arrow nav-tabs-arrow-left"><a>«</a></div>');
769 arrowRight = $('<div class="nav-tabs-arrow nav-tabs-arrow-right"><a>»</a></div>');
770 // Attach to the document
771 fixed.wrap('<div class="nav-tabs-nav"/>').parent().prepend( arrowLeft ).append( arrowRight );
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);
781 // Build arrow functions
794 this.arrow.mousedown(function(){
795 var marginFluidVal = Math.abs( parseInt( fluid.css(marginFluid) ) ),
796 shift = marginFluidVal,
799 if( "-=" == that.operator )
800 shift = Math.abs( tabsWidth - fixed.width() ) - marginFluidVal;
802 if( ! shift ) return;
804 css[marginFluid] = that.operator + shift + 'px';
805 fluid.animate( css, shift * msPerPx, "linear" );
806 }).mouseup(function(){
809 tab = tabs[that.last]();
810 while( (next = tab[that.next]()) && next.length && ! next.isTabVisible() ) {
813 tab.makeTabVisible();
818 eventOnClickEditLink : function(clickedEl) {
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() );
829 settings.slideDown('fast');
830 item.removeClass('menu-item-edit-inactive')
831 .addClass('menu-item-edit-active');
833 settings.slideUp('fast');
834 item.removeClass('menu-item-edit-active')
835 .addClass('menu-item-edit-inactive');
842 eventOnClickCancelLink : function(clickedEl) {
843 var settings = $(clickedEl).closest('.menu-item-settings');
844 settings.setItemData( settings.data('menu-item-data') );
848 eventOnClickMenuSave : function(clickedEl) {
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');
857 // Copy menu theme locations
858 $('#nav-menu-theme-locations select').each(function() {
859 locs += '<input type="hidden" name="' + this.name + '" value="' + $(this).val() + '" />';
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;
869 eventOnClickMenuDelete : function(clickedEl) {
870 // Delete warning AYS
871 if ( confirm( navMenuL10n.warnDeleteMenu ) ) {
872 window.onbeforeunload = null;
878 eventOnClickMenuItemDelete : function(clickedEl) {
879 var itemID = parseInt(clickedEl.id.replace('delete-', ''), 10);
880 api.removeMenuItem( $('#menu-item-' + itemID) );
881 api.registerChange();
886 * Process the quick search response into a search result
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.
892 processQuickSearchQueryResponse : function(resp, req, panel) {
893 var i, matched, newID,
895 form = document.getElementById('nav-menu-meta'),
896 pattern = new RegExp('menu-item\\[(\[^\\]\]*)', 'g'),
897 items = resp.match(/<li>.*<\/li>/g);
900 $('.categorychecklist', panel).html( '<li><p>' + navMenuL10n.noResultsFound + '</p></li>' );
901 $('img.waiting', panel).hide();
907 // make a unique DB ID number
908 matched = pattern.exec(items[i]);
909 if ( matched && matched[1] ) {
911 while( form.elements['menu-item[' + newID + '][menu-item-type]'] || takenIDs[ newID ] ) {
915 takenIDs[newID] = true;
916 if ( newID != matched[1] ) {
917 items[i] = items[i].replace(new RegExp('menu-item\\[' + matched[1] + '\\]', 'g'), 'menu-item[' + newID + ']');
922 $('.categorychecklist', panel).html( items.join('') );
923 $('img.waiting', panel).hide();
926 removeMenuItem : function(el) {
927 var children = el.childMenuItems();
929 el.addClass('deleting').animate({
933 var ins = $('#menu-instructions');
935 children.shiftDepthClass(-1).updateParentMenuItemDBId();
936 if( ! ins.siblings().length )
937 ins.removeClass('menu-instructions-inactive');
941 depthToPx : function(depth) {
942 return depth * api.options.menuItemDepthPerLevel;
945 pxToDepth : function(px) {
946 return Math.floor(px / api.options.menuItemDepthPerLevel);
951 $(document).ready(function(){ wpNavMenu.init(); });