/** * WordPress Administration Navigation Menu * Interface JS functions * * @version 2.0.0 * * @package WordPress * @subpackage Administration */ var WPNavMenuHandler = function () { var $ = jQuery, activeHovering = false, currentDropzone = null, customLinkNameInput, customLinkURLInput, customLinkNameDefault, customLinkURLDefault, autoCompleteData = {}, formatAutocompleteResponse = function( resultRow, pos, total, queryTerm ) { if ( resultRow && resultRow[0] ) { var data = $.parseJSON(resultRow[0]); if ( data.post_title ) { if ( data.ID && data.post_type ) autoCompleteData[data.post_title] = {ID: data.ID, object_type: data.post_type}; return data.post_title; } } }, formatAutocompleteResult = function( resultRow, pos, total, queryTerm ) { if ( resultRow && resultRow[0] ) { var data = $.parseJSON(resultRow[0]); if ( data.post_title ) return data.post_title; } }, getListDataFromID = function(menuItemID, parentEl) { if ( ! menuItemID ) return false; parentEl = parentEl || document; var fields = [ 'menu-item-db-id', 'menu-item-object-id', 'menu-item-object', 'menu-item-parent-id', 'menu-item-position', 'menu-item-type', 'menu-item-append', 'menu-item-title', 'menu-item-url', 'menu-item-description', 'menu-item-attr-title', 'menu-item-target', 'menu-item-classes', 'menu-item-xfn' ], itemData = {}, inputs = parentEl.getElementsByTagName('input'), i = inputs.length, j, menuID = document.getElementById('nav-menu-meta-object-id').value; while ( i-- ) { j = fields.length; while ( j-- ) { if ( inputs[i] && inputs[i].name && 'menu-item[' + menuItemID + '][' + fields[j] + ']' == inputs[i].name ) { itemData[fields[j]] = inputs[i].value; } } } return itemData; }, getParentMenuItemDBId = function() { var allInputs = this.getElementsByTagName('input'), i = allInputs.length, j, parentEl, parentInputs; while( i-- ) { if ( -1 != allInputs[i].name.indexOf('menu-item-parent-id[' + parseInt(this.id.replace('menu-item-', ''), 10) + ']') ) { /* This LI element is not in a submenu */ if ( ! this.parentNode.className || -1 == this.parentNode.className.indexOf('sub-menu') ) { allInputs[i].value = 0; /* This LI is in a submenu, so need to get the parent's object ID (which is different from the parent's DB ID, the ID in its attributes) */ } else if ( 'LI' == this.parentNode.parentNode.nodeName && -1 != this.parentNode.parentNode.id.indexOf('menu-item-') ) { parentEl = this.parentNode.parentNode; parentInputs = parentEl.getElementsByTagName('input'); j = parentInputs.length; while ( j-- ) { if ( parentInputs[j].name && -1 != parentInputs[j].name.indexOf('menu-item-object-id[' + parseInt(parentEl.id.replace('menu-item-', ''), 10) + ']') ) { allInputs[i].value = parseInt(parentInputs[j].value, 10); break; } } } break; } } }, /** * Get the parent element with the matching class, but go no higher than the form. * * @param DOM-element el The descendant element up from which we'll be searching * @param string parentClass The class name of the desired parent element. * @return DOM-element The parent element. */ getParentWrapper = function( el, parentClass ) { var form = document.getElementById('nav-menu-meta'), i; while ( el.parentNode && ( ! el.className || -1 == el.className.indexOf(parentClass) ) && el.parentNode != form ) { el = el.parentNode; } return el; }, makeDroppable = function(el) { var that = this; $(el).droppable({ accept: '.menu li', tolerance: 'pointer', drop: function(e, ui) { that.eventOnDrop(ui.draggable[0], this, ui, e); }, over: function(e,ui) { that.eventOnDragOver(ui.draggable[0], this, ui, e); }, out: function(e, ui) { that.eventOnDragOut(ui.draggable[0], this, ui, e); } }); }, menuList, setupListItemsDragAndDrop = function(list) { if ( ! list ) return; var dummyListItem = document.getElementById(list.id + '-dummy-list-item'), menuListItems = list.getElementsByTagName('li'), i = menuListItems.length; if ( ! dummyListItem ) { dummyListItem = document.createElement('li'); dummyListItem.id = list.id + '-dummy-list-item'; list.appendChild(dummyListItem); this.setupListItemDragAndDrop(dummyListItem); } while ( i-- ) this.setupListItemDragAndDrop(menuListItems[i]); }; return { // Functions that run on init. init : function() { menuList = document.getElementById('menu-to-edit'); this.attachMenuEditListeners(); this.attachMenuMetaListeners(document.getElementById('nav-menu-meta')); this.attachTabsPanelListeners(); // init drag and drop setupListItemsDragAndDrop.call(this, menuList); postboxes.add_postbox_toggles('nav-menus'); }, attachMenuEditListeners : function() { var that = this; $('#update-nav-menu').bind('click', function(e) { if ( e.target && e.target.className ) { if ( -1 != e.target.className.indexOf('item-edit') ) { return that.eventOnClickEditLink(e.target); } else if ( -1 != e.target.className.indexOf('menu-delete') ) { return that.eventOnClickMenuDelete(e.target); } else if ( -1 != e.target.className.indexOf('item-delete') ) { return that.eventOnClickMenuItemDelete(e.target); } } }); }, attachMenuMetaListeners : function(formEL) { if ( ! formEL ) return; var that = this; // set default value for custom link name customLinkNameInput = document.getElementById('custom-menu-item-name'); customLinkURLInput = document.getElementById('custom-menu-item-url'); if ( customLinkNameInput ) { customLinkNameDefault = 'undefined' != typeof customLinkNameInput.defaultValue ? customLinkNameInput.defaultValue : customLinkNameInput.getAttribute('value'); customLinkURLDefault = 'undefined' != typeof customLinkURLInput.defaultValue ? customLinkURLInput.defaultValue : customLinkURLInput.getAttribute('value'); $(customLinkNameInput).bind('focus', function(e) { this.value = customLinkNameDefault == this.value ? '' : this.value; }); $(customLinkNameInput).bind('blur', function(e) { this.value = '' == this.value ? customLinkNameDefault : this.value; }); } // auto-suggest for the quick-search boxes $('input.quick-search').each(function(i, el) { that.setupQuickSearchEventListeners(el); }); $(formEL).bind('submit', function(e) { return that.eventSubmitMetaForm.call(that, this, e); }); }, attachTabsPanelListeners : function() { $('#menu-settings-column').bind('click', function(e) { if ( e.target && e.target.className && -1 != e.target.className.indexOf('menu-tab-link') ) { var activePanel, panelIdMatch = /#(.*)$/.exec(e.target.href), tabPanels, wrapper = getParentWrapper(e.target, 'inside'), inputs = wrapper ? wrapper.getElementsByTagName('input') : [], i = inputs.length; // upon changing tabs, we want to uncheck all checkboxes while( i-- ) inputs[i].checked = false; $('.tabs-panel', wrapper).each(function() { if ( this.className ) this.className = this.className.replace('tabs-panel-active', 'tabs-panel-inactive'); }); $('.tabs', wrapper).each(function() { this.className = this.className.replace('tabs', ''); }); e.target.parentNode.className += ' tabs'; if ( panelIdMatch && panelIdMatch[1] ) { activePanel = document.getElementById(panelIdMatch[1]); if ( activePanel ) { activePanel.className = activePanel.className.replace('tabs-panel-inactive', 'tabs-panel-active'); } } return false; } else if ( e.target && e.target.className && -1 != e.target.className.indexOf('select-all') ) { var selectAreaMatch = /#(.*)$/.exec(e.target.href); if ( selectAreaMatch && selectAreaMatch[1] ) { $('#' + selectAreaMatch[1] + ' .tabs-panel-active input[type=checkbox]').attr('checked', 'checked'); return false; } } }); }, setupListItemDragAndDrop : function(el) { var defLists = el.getElementsByTagName('dl'), dropZone = this.makeListItemDropzone(el), i = defLists.length; makeDroppable.call(this, dropZone); this.makeListItemDraggable(el); while( i-- ) { makeDroppable.call(this, defLists[i]); } }, /** * Set up quick-search input fields' events. * * @param object el The input element. */ setupQuickSearchEventListeners : function(el) { var that = this; $(el).autocomplete( ajaxurl + '?action=menu-quick-search&type=' + el.name, { delay: 500, formatItem: formatAutocompleteResponse, formatResult: formatAutocompleteResult, minchars: 2, multiple: false } ).bind('blur', function(e) { var changedData = autoCompleteData[this.value], inputEl = this; if ( changedData ) { $.post( ajaxurl + '?action=menu-quick-search&type=get-post-item&response-format=markup', changedData, function(r) { that.processQuickSearchQueryResponse.call(that, r, changedData); autoCompleteData[inputEl.value] = false; } ); } }); }, eventOnClickEditLink : function(clickedEl) { var activeEdit, matchedSection = /#(.*)$/.exec(clickedEl.href); if ( matchedSection && matchedSection[1] ) { activeEdit = document.getElementById(matchedSection[1]); if ( activeEdit ) { if ( -1 != activeEdit.className.indexOf('menu-item-edit-inactive') ) { activeEdit.className = activeEdit.className.replace('menu-item-edit-inactive', 'menu-item-edit-active'); } else { activeEdit.className = activeEdit.className.replace('menu-item-edit-active', 'menu-item-edit-inactive'); } return false; } } }, eventOnClickMenuDelete : function(clickedEl) { // Delete warning AYS if ( confirm( navMenuL10n.warnDeleteMenu ) ) { return true; } else { return false; } }, eventOnClickMenuItemDelete : function(clickedEl) { var itemID, matchedSection, that = this; // Delete warning AYS if ( confirm( navMenuL10n.warnDeleteMenuItem ) ) { matchedSection = /_wpnonce=([a-zA-Z0-9]*)$/.exec(clickedEl.href); if ( matchedSection && matchedSection[1] ) { itemID = parseInt(clickedEl.id.replace('delete-', ''), 10); $.post( ajaxurl, { action:'delete-menu-item', 'menu-item':itemID, '_wpnonce':matchedSection[1] }, function (resp) { if ( '1' == resp ) that.removeMenuItem(document.getElementById('menu-item-' + itemID)); } ); return false; } return true; } else { return false; } }, /** * Callback for the drag over action when dragging a list item. * * @param object draggedEl The DOM element being dragged * @param object dropEl The DOM element on top of which we're dropping. */ eventOnDragOver : function(draggedEl, dropEl) { activeHovering = true; currentDropzone = dropEl; dropEl.className += ' sortable-placeholder'; }, /** * Callback for the drag out action when dragging a list item. * * @param object draggedEl The DOM element being dragged * @param object dropEl The DOM element on top of which we're dropping. */ eventOnDragOut : function(draggedEl, dropEl) { activeHovering = false; /* delay the disappearance of the droppable area so it doesn't flicker in and out */ (function(that) { setTimeout(function() { if ( that != currentDropzone || ( ! activeHovering && that.className && -1 != that.className.indexOf('sortable-placeholder') ) ) { that.className = that.className.replace(/sortable-placeholder/g, ''); } }, 800); })(dropEl); }, /** * Callback for the drop action when dragging and dropping a list item. * * @param object draggedEl The DOM element being dragged (and now dropped) * @param object dropEl The DOM element on top of which we're dropping. */ eventOnDrop : function(draggedEl, dropEl) { var dropIntoSublist = !! ( -1 == dropEl.className.indexOf('dropzone') ), subLists = dropEl.parentNode.getElementsByTagName('ul'), hasSublist = false, i = subLists.length, subList; activeHovering = false; dropEl.className = dropEl.className.replace(/sortable-placeholder/g, ''); if ( dropIntoSublist ) { while ( i-- ) { if ( subLists[i] && 1 != subLists[i].className.indexOf('sub-menu') ) { hasSublist = true; subList = subLists[i]; } } if ( ! hasSublist ) { subList = document.createElement('ul'); subList.className = 'sub-menu'; dropEl.parentNode.appendChild(subList); } subList.appendChild(draggedEl); } else { dropEl.parentNode.parentNode.insertBefore(draggedEl, dropEl.parentNode); } this.recalculateSortOrder(menuList); getParentMenuItemDBId.call(draggedEl); }, /** * Callback for the meta form submit action listener. * * @param object thisForm The submitted form. * @param object e The event object. */ eventSubmitMetaForm : function(thisForm, e) { var ancestor, inputs = thisForm.getElementsByTagName('input'), i = inputs.length, j, listItemData, listItemDBID, listItemDBIDMatch, params = {}, processMethod = function(){}, re = new RegExp('menu-item\\[(\[^\\]\]*)'); that = this; params['action'] = ''; while ( i-- ) { if ( // we're submitting a checked item inputs[i].name && -1 != inputs[i].name.indexOf('menu-item-object-id') && inputs[i].checked || ( // or we're dealing with a custom link 'undefined' != typeof inputs[i].id && 'custom-menu-item-url' == inputs[i].id && '' != inputs[i].value && 'http://' != inputs[i].value ) ) { params['action'] = 'add-menu-item'; processMethod = that.processAddMenuItemResponse; listItemDBIDMatch = re.exec(inputs[i].name); listItemDBID = 'undefined' == typeof listItemDBIDMatch[1] ? 0 : parseInt(listItemDBIDMatch[1], 10); listItemData = getListDataFromID(listItemDBID); for ( j in listItemData ) { params['menu-item[' + listItemDBID + '][' + j + ']'] = listItemData[j]; } ancestor = getParentWrapper(inputs[i], 'inside'); inputs[i].checked = false; // we're submitting a search term } else if ( '' == params['action'] && // give precedence to adding items '' != inputs[i].value && inputs[i].className && -1 != inputs[i].className.search(/quick-search\b[^-]/) ) { ancestor = getParentWrapper(inputs[i], 'inside'); params['action'] = 'menu-quick-search'; params['q'] = inputs[i].value; params['response-format'] = 'markup'; params['type'] = inputs[i].name; processMethod = that.processQuickSearchQueryResponse; } } if ( ancestor ) ancestor.className = ancestor.className + ' processing', params['menu'] = thisForm.elements['menu'].value; params['menu-settings-column-nonce'] = thisForm.elements['menu-settings-column-nonce'].value; $.post( ajaxurl, params, function(menuMarkup) { processMethod.call(that, menuMarkup, params); ancestor.className = ancestor.className.replace(/processing/g, ''); }); return false; }, makeListItemDraggable : function(el) { // make menu item draggable $(el).draggable({ handle: ' > dl', opacity: .8, addClasses: false, helper: 'clone', zIndex: 100 }); }, /** * Add the child element that acts as the dropzone for drag-n-drop. * * @param object el The parent object to which we'll prepend the dropzone. * @return object The dropzone DOM element. */ makeListItemDropzone : function(el) { if ( ! el ) return false; var divs = el.getElementsByTagName('div'), i = divs.length, dropZone = document.createElement('div'); while( i-- ) { if ( divs[i].className && -1 != divs[i].className.indexOf('dropzone') && ( el == divs[i].parentNode ) ) return divs[i]; } dropZone.className = 'dropzone'; el.insertBefore(dropZone, el.firstChild); return dropZone; }, /** * Process the add menu item request response into menu list item. * * @param string menuMarkup The text server response of menu item markup. * @param object req The request arguments. */ processAddMenuItemResponse : function( menuMarkup, req ) { if ( ! req ) req = {}; var dropZone, dummyListItem = document.getElementById(menuList.id + '-dummy-list-item'), i, listElements, wrap = document.createElement('ul'); wrap.innerHTML = menuMarkup; listElements = wrap.getElementsByTagName('li'); i = listElements.length; while ( i-- ) { this.setupListItemDragAndDrop(listElements[i]); if ( dummyListItem ) menuList.insertBefore(listElements[i], dummyListItem); else menuList.appendChild(listElements[i]); } this.recalculateSortOrder(menuList); /* set custom link form back to defaults */ if ( customLinkNameInput && customLinkURLInput ) { customLinkNameInput.value = customLinkNameDefault; customLinkURLInput.value = customLinkURLDefault; } }, /** * Process the quick search response into a search result * * @param string resp The server response to the query. * @param object req The request arguments. */ processQuickSearchQueryResponse : function(resp, req) { if ( ! req ) req = {}; var wrap = document.createElement('ul'), form = document.getElementById('nav-menu-meta'), i, items, matched, newID, pattern = new RegExp('menu-item\\[(\[^\\]\]*)'), resultList; // make a unique DB ID number matched = pattern.exec(resp); if ( matched && matched[1] ) { newID = matched[1]; while( form.elements['menu-item[' + newID + '][menu-item-type]'] ) { newID--; } if ( newID != matched[1] ) { resp = resp.replace(new RegExp('menu-item\\[' + matched[1] + '\\]', 'g'), 'menu-item[' + newID + ']'); } } wrap.innerHTML = resp; items = wrap.getElementsByTagName('li'); if ( items[0] && req.object_type ) { resultList = document.getElementById(req.object_type + '-search-checklist'); if ( resultList ) { resultList.innerHTML = ''; resultList.appendChild(items[0]); } } else if ( req.type ) { matched = /quick-search-posttype-([a-zA-Z_-]*)/.exec(req.type); if ( matched && matched[1] ) { resultList = document.getElementById(matched[1] + '-search-checklist'); if ( resultList ) { resultList.innerHTML = ''; i = items.length; while( i-- ) { resultList.appendChild(items[i]); } } } } }, recalculateSortOrder : function(parentEl) { var allInputs = parentEl.getElementsByTagName('input'), i, j = 0; for( i = 0; i < allInputs.length; i++ ) { if ( allInputs[i].name && -1 != allInputs[i].name.indexOf('menu-item-position') ) { allInputs[i].value = ++j; } } }, removeMenuItem : function(el) { if ( ! el ) return false; var subMenus = el.getElementsByTagName('ul'), subs, i; if ( subMenus[0] ) { subs = subMenus[0].getElementsByTagName('li'); for ( i = 0; i < subs.length; i++ ) { if ( subs[i].id && -1 != subs[i].id.indexOf('menu-item-') && subs[i].parentNode == subMenus[0] ) { el.parentNode.insertBefore(subs[i], el); } } } el.className += ' deleting'; $(el).fadeOut( 350 , function() { this.parentNode.removeChild(this); }); this.recalculateSortOrder(menuList); } } } var wpNavMenu = new WPNavMenuHandler(); jQuery(function() { wpNavMenu.init(); });