/wp-admin/js/customize-nav-menus.js
JavaScript | 1625 lines | 1115 code | 218 blank | 292 comment | 188 complexity | 074e06ecc789adcc5c08459a7252c714 MD5 | raw file
- /* global _wpCustomizeNavMenusSettings, wpNavMenu, console */
- ( function( api, wp, $ ) {
- 'use strict';
- /**
- * Set up wpNavMenu for drag and drop.
- */
- wpNavMenu.originalInit = wpNavMenu.init;
- wpNavMenu.options.menuItemDepthPerLevel = 20;
- wpNavMenu.options.sortableItems = '> .customize-control-nav_menu_item';
- wpNavMenu.options.targetTolerance = 10;
- wpNavMenu.init = function() {
- this.jQueryExtensions();
- };
- api.Menus = api.Menus || {};
- // Link settings.
- api.Menus.data = {
- itemTypes: [],
- l10n: {},
- settingTransport: 'refresh',
- phpIntMax: 0,
- defaultSettingValues: {
- nav_menu: {},
- nav_menu_item: {}
- },
- locationSlugMappedToName: {}
- };
- if ( 'undefined' !== typeof _wpCustomizeNavMenusSettings ) {
- $.extend( api.Menus.data, _wpCustomizeNavMenusSettings );
- }
- /**
- * Newly-created Nav Menus and Nav Menu Items have negative integer IDs which
- * serve as placeholders until Save & Publish happens.
- *
- * @return {number}
- */
- api.Menus.generatePlaceholderAutoIncrementId = function() {
- return -Math.ceil( api.Menus.data.phpIntMax * Math.random() );
- };
- /**
- * wp.customize.Menus.AvailableItemModel
- *
- * A single available menu item model. See PHP's WP_Customize_Nav_Menu_Item_Setting class.
- *
- * @constructor
- * @augments Backbone.Model
- */
- api.Menus.AvailableItemModel = Backbone.Model.extend( $.extend(
- {
- id: null // This is only used by Backbone.
- },
- api.Menus.data.defaultSettingValues.nav_menu_item
- ) );
- /**
- * wp.customize.Menus.AvailableItemCollection
- *
- * Collection for available menu item models.
- *
- * @constructor
- * @augments Backbone.Model
- */
- api.Menus.AvailableItemCollection = Backbone.Collection.extend({
- model: api.Menus.AvailableItemModel,
- sort_key: 'order',
- comparator: function( item ) {
- return -item.get( this.sort_key );
- },
- sortByField: function( fieldName ) {
- this.sort_key = fieldName;
- this.sort();
- }
- });
- api.Menus.availableMenuItems = new api.Menus.AvailableItemCollection( api.Menus.data.availableMenuItems );
- /**
- * wp.customize.Menus.AvailableMenuItemsPanelView
- *
- * View class for the available menu items panel.
- *
- * @constructor
- * @augments wp.Backbone.View
- * @augments Backbone.View
- */
- api.Menus.AvailableMenuItemsPanelView = wp.Backbone.View.extend({
- el: '#available-menu-items',
- events: {
- 'input #menu-items-search': 'debounceSearch',
- 'keyup #menu-items-search': 'debounceSearch',
- 'focus .menu-item-tpl': 'focus',
- 'click .menu-item-tpl': '_submit',
- 'click #custom-menu-item-submit': '_submitLink',
- 'keypress #custom-menu-item-name': '_submitLink',
- 'keydown': 'keyboardAccessible'
- },
- // Cache current selected menu item.
- selected: null,
- // Cache menu control that opened the panel.
- currentMenuControl: null,
- debounceSearch: null,
- $search: null,
- searchTerm: '',
- rendered: false,
- pages: {},
- sectionContent: '',
- loading: false,
- initialize: function() {
- var self = this;
- if ( ! api.panel.has( 'nav_menus' ) ) {
- return;
- }
- this.$search = $( '#menu-items-search' );
- this.sectionContent = this.$el.find( '.accordion-section-content' );
- this.debounceSearch = _.debounce( self.search, 500 );
- _.bindAll( this, 'close' );
- // If the available menu items panel is open and the customize controls are
- // interacted with (other than an item being deleted), then close the
- // available menu items panel. Also close on back button click.
- $( '#customize-controls, .customize-section-back' ).on( 'click keydown', function( e ) {
- var isDeleteBtn = $( e.target ).is( '.item-delete, .item-delete *' ),
- isAddNewBtn = $( e.target ).is( '.add-new-menu-item, .add-new-menu-item *' );
- if ( $( 'body' ).hasClass( 'adding-menu-items' ) && ! isDeleteBtn && ! isAddNewBtn ) {
- self.close();
- }
- } );
- // Clear the search results.
- $( '.clear-results' ).on( 'click keydown', function( event ) {
- if ( event.type === 'keydown' && ( 13 !== event.which && 32 !== event.which ) ) { // "return" or "space" keys only
- return;
- }
- event.preventDefault();
- $( '#menu-items-search' ).val( '' ).focus();
- event.target.value = '';
- self.search( event );
- } );
- this.$el.on( 'input', '#custom-menu-item-name.invalid, #custom-menu-item-url.invalid', function() {
- $( this ).removeClass( 'invalid' );
- });
- // Load available items if it looks like we'll need them.
- api.panel( 'nav_menus' ).container.bind( 'expanded', function() {
- if ( ! self.rendered ) {
- self.initList();
- self.rendered = true;
- }
- });
- // Load more items.
- this.sectionContent.scroll( function() {
- var totalHeight = self.$el.find( '.accordion-section.open .accordion-section-content' ).prop( 'scrollHeight' ),
- visibleHeight = self.$el.find( '.accordion-section.open' ).height();
- if ( ! self.loading && $( this ).scrollTop() > 3 / 4 * totalHeight - visibleHeight ) {
- var type = $( this ).data( 'type' ),
- object = $( this ).data( 'object' );
- if ( 'search' === type ) {
- if ( self.searchTerm ) {
- self.doSearch( self.pages.search );
- }
- } else {
- self.loadItems( type, object );
- }
- }
- });
- // Close the panel if the URL in the preview changes
- api.previewer.bind( 'url', this.close );
- self.delegateEvents();
- },
- // Search input change handler.
- search: function( event ) {
- var $searchSection = $( '#available-menu-items-search' ),
- $otherSections = $( '#available-menu-items .accordion-section' ).not( $searchSection );
- if ( ! event ) {
- return;
- }
- if ( this.searchTerm === event.target.value ) {
- return;
- }
- if ( '' !== event.target.value && ! $searchSection.hasClass( 'open' ) ) {
- $otherSections.fadeOut( 100 );
- $searchSection.find( '.accordion-section-content' ).slideDown( 'fast' );
- $searchSection.addClass( 'open' );
- $searchSection.find( '.clear-results' )
- .prop( 'tabIndex', 0 )
- .addClass( 'is-visible' );
- } else if ( '' === event.target.value ) {
- $searchSection.removeClass( 'open' );
- $otherSections.show();
- $searchSection.find( '.clear-results' )
- .prop( 'tabIndex', -1 )
- .removeClass( 'is-visible' );
- }
- this.searchTerm = event.target.value;
- this.pages.search = 1;
- this.doSearch( 1 );
- },
- // Get search results.
- doSearch: function( page ) {
- var self = this, params,
- $section = $( '#available-menu-items-search' ),
- $content = $section.find( '.accordion-section-content' ),
- itemTemplate = wp.template( 'available-menu-item' );
- if ( self.currentRequest ) {
- self.currentRequest.abort();
- }
- if ( page < 0 ) {
- return;
- } else if ( page > 1 ) {
- $section.addClass( 'loading-more' );
- $content.attr( 'aria-busy', 'true' );
- wp.a11y.speak( api.Menus.data.l10n.itemsLoadingMore );
- } else if ( '' === self.searchTerm ) {
- $content.html( '' );
- wp.a11y.speak( '' );
- return;
- }
- $section.addClass( 'loading' );
- self.loading = true;
- params = {
- 'customize-menus-nonce': api.settings.nonce['customize-menus'],
- 'wp_customize': 'on',
- 'search': self.searchTerm,
- 'page': page
- };
- self.currentRequest = wp.ajax.post( 'search-available-menu-items-customizer', params );
- self.currentRequest.done(function( data ) {
- var items;
- if ( 1 === page ) {
- // Clear previous results as it's a new search.
- $content.empty();
- }
- $section.removeClass( 'loading loading-more' );
- $content.attr( 'aria-busy', 'false' );
- $section.addClass( 'open' );
- self.loading = false;
- items = new api.Menus.AvailableItemCollection( data.items );
- self.collection.add( items.models );
- items.each( function( menuItem ) {
- $content.append( itemTemplate( menuItem.attributes ) );
- } );
- if ( 20 > items.length ) {
- self.pages.search = -1; // Up to 20 posts and 20 terms in results, if <20, no more results for either.
- } else {
- self.pages.search = self.pages.search + 1;
- }
- if ( items && page > 1 ) {
- wp.a11y.speak( api.Menus.data.l10n.itemsFoundMore.replace( '%d', items.length ) );
- } else if ( items && page === 1 ) {
- wp.a11y.speak( api.Menus.data.l10n.itemsFound.replace( '%d', items.length ) );
- }
- });
- self.currentRequest.fail(function( data ) {
- // data.message may be undefined, for example when typing slow and the request is aborted.
- if ( data.message ) {
- $content.empty().append( $( '<p class="nothing-found"></p>' ).text( data.message ) );
- wp.a11y.speak( data.message );
- }
- self.pages.search = -1;
- });
- self.currentRequest.always(function() {
- $section.removeClass( 'loading loading-more' );
- $content.attr( 'aria-busy', 'false' );
- self.loading = false;
- self.currentRequest = null;
- });
- },
- // Render the individual items.
- initList: function() {
- var self = this;
- // Render the template for each item by type.
- _.each( api.Menus.data.itemTypes, function( itemType ) {
- self.pages[ itemType.type + ':' + itemType.object ] = 0;
- self.loadItems( itemType.type, itemType.object ); // @todo we need to combine these Ajax requests.
- } );
- },
- // Load available menu items.
- loadItems: function( type, object ) {
- var self = this, params, request, itemTemplate, availableMenuItemContainer;
- itemTemplate = wp.template( 'available-menu-item' );
- if ( -1 === self.pages[ type + ':' + object ] ) {
- return;
- }
- availableMenuItemContainer = $( '#available-menu-items-' + type + '-' + object );
- availableMenuItemContainer.find( '.accordion-section-title' ).addClass( 'loading' );
- self.loading = true;
- params = {
- 'customize-menus-nonce': api.settings.nonce['customize-menus'],
- 'wp_customize': 'on',
- 'type': type,
- 'object': object,
- 'page': self.pages[ type + ':' + object ]
- };
- request = wp.ajax.post( 'load-available-menu-items-customizer', params );
- request.done(function( data ) {
- var items, typeInner;
- items = data.items;
- if ( 0 === items.length ) {
- if ( 0 === self.pages[ type + ':' + object ] ) {
- availableMenuItemContainer
- .addClass( 'cannot-expand' )
- .removeClass( 'loading' )
- .find( '.accordion-section-title > button' )
- .prop( 'tabIndex', -1 );
- }
- self.pages[ type + ':' + object ] = -1;
- return;
- }
- items = new api.Menus.AvailableItemCollection( items ); // @todo Why is this collection created and then thrown away?
- self.collection.add( items.models );
- typeInner = availableMenuItemContainer.find( '.accordion-section-content' );
- items.each(function( menuItem ) {
- typeInner.append( itemTemplate( menuItem.attributes ) );
- });
- self.pages[ type + ':' + object ] += 1;
- });
- request.fail(function( data ) {
- if ( typeof console !== 'undefined' && console.error ) {
- console.error( data );
- }
- });
- request.always(function() {
- availableMenuItemContainer.find( '.accordion-section-title' ).removeClass( 'loading' );
- self.loading = false;
- });
- },
- // Adjust the height of each section of items to fit the screen.
- itemSectionHeight: function() {
- var sections, totalHeight, accordionHeight, diff;
- totalHeight = window.innerHeight;
- sections = this.$el.find( '.accordion-section:not( #available-menu-items-search ) .accordion-section-content' );
- accordionHeight = 46 * ( 2 + sections.length ) - 13; // Magic numbers.
- diff = totalHeight - accordionHeight;
- if ( 120 < diff && 290 > diff ) {
- sections.css( 'max-height', diff );
- }
- },
- // Highlights a menu item.
- select: function( menuitemTpl ) {
- this.selected = $( menuitemTpl );
- this.selected.siblings( '.menu-item-tpl' ).removeClass( 'selected' );
- this.selected.addClass( 'selected' );
- },
- // Highlights a menu item on focus.
- focus: function( event ) {
- this.select( $( event.currentTarget ) );
- },
- // Submit handler for keypress and click on menu item.
- _submit: function( event ) {
- // Only proceed with keypress if it is Enter or Spacebar
- if ( 'keypress' === event.type && ( 13 !== event.which && 32 !== event.which ) ) {
- return;
- }
- this.submit( $( event.currentTarget ) );
- },
- // Adds a selected menu item to the menu.
- submit: function( menuitemTpl ) {
- var menuitemId, menu_item;
- if ( ! menuitemTpl ) {
- menuitemTpl = this.selected;
- }
- if ( ! menuitemTpl || ! this.currentMenuControl ) {
- return;
- }
- this.select( menuitemTpl );
- menuitemId = $( this.selected ).data( 'menu-item-id' );
- menu_item = this.collection.findWhere( { id: menuitemId } );
- if ( ! menu_item ) {
- return;
- }
- this.currentMenuControl.addItemToMenu( menu_item.attributes );
- $( menuitemTpl ).find( '.menu-item-handle' ).addClass( 'item-added' );
- },
- // Submit handler for keypress and click on custom menu item.
- _submitLink: function( event ) {
- // Only proceed with keypress if it is Enter.
- if ( 'keypress' === event.type && 13 !== event.which ) {
- return;
- }
- this.submitLink();
- },
- // Adds the custom menu item to the menu.
- submitLink: function() {
- var menuItem,
- itemName = $( '#custom-menu-item-name' ),
- itemUrl = $( '#custom-menu-item-url' );
- if ( ! this.currentMenuControl ) {
- return;
- }
- if ( '' === itemName.val() ) {
- itemName.addClass( 'invalid' );
- return;
- } else if ( '' === itemUrl.val() || 'http://' === itemUrl.val() ) {
- itemUrl.addClass( 'invalid' );
- return;
- }
- menuItem = {
- 'title': itemName.val(),
- 'url': itemUrl.val(),
- 'type': 'custom',
- 'type_label': api.Menus.data.l10n.custom_label,
- 'object': ''
- };
- this.currentMenuControl.addItemToMenu( menuItem );
- // Reset the custom link form.
- itemUrl.val( 'http://' );
- itemName.val( '' );
- },
- // Opens the panel.
- open: function( menuControl ) {
- this.currentMenuControl = menuControl;
- this.itemSectionHeight();
- $( 'body' ).addClass( 'adding-menu-items' );
- // Collapse all controls.
- _( this.currentMenuControl.getMenuItemControls() ).each( function( control ) {
- control.collapseForm();
- } );
- this.$el.find( '.selected' ).removeClass( 'selected' );
- this.$search.focus();
- },
- // Closes the panel
- close: function( options ) {
- options = options || {};
- if ( options.returnFocus && this.currentMenuControl ) {
- this.currentMenuControl.container.find( '.add-new-menu-item' ).focus();
- }
- this.currentMenuControl = null;
- this.selected = null;
- $( 'body' ).removeClass( 'adding-menu-items' );
- $( '#available-menu-items .menu-item-handle.item-added' ).removeClass( 'item-added' );
- this.$search.val( '' );
- },
- // Add a few keyboard enhancements to the panel.
- keyboardAccessible: function( event ) {
- var isEnter = ( 13 === event.which ),
- isEsc = ( 27 === event.which ),
- isBackTab = ( 9 === event.which && event.shiftKey ),
- isSearchFocused = $( event.target ).is( this.$search );
- // If enter pressed but nothing entered, don't do anything
- if ( isEnter && ! this.$search.val() ) {
- return;
- }
- if ( isSearchFocused && isBackTab ) {
- this.currentMenuControl.container.find( '.add-new-menu-item' ).focus();
- event.preventDefault(); // Avoid additional back-tab.
- } else if ( isEsc ) {
- this.close( { returnFocus: true } );
- }
- }
- });
- /**
- * wp.customize.Menus.MenusPanel
- *
- * Customizer panel for menus. This is used only for screen options management.
- * Note that 'menus' must match the WP_Customize_Menu_Panel::$type.
- *
- * @constructor
- * @augments wp.customize.Panel
- */
- api.Menus.MenusPanel = api.Panel.extend({
- attachEvents: function() {
- api.Panel.prototype.attachEvents.call( this );
- var panel = this,
- panelMeta = panel.container.find( '.panel-meta' ),
- help = panelMeta.find( '.customize-help-toggle' ),
- content = panelMeta.find( '.customize-panel-description' ),
- options = $( '#screen-options-wrap' ),
- button = panelMeta.find( '.customize-screen-options-toggle' );
- button.on( 'click keydown', function( event ) {
- if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
- return;
- }
- event.preventDefault();
- // Hide description
- if ( content.not( ':hidden' ) ) {
- content.slideUp( 'fast' );
- help.attr( 'aria-expanded', 'false' );
- }
- if ( 'true' === button.attr( 'aria-expanded' ) ) {
- button.attr( 'aria-expanded', 'false' );
- panelMeta.removeClass( 'open' );
- panelMeta.removeClass( 'active-menu-screen-options' );
- options.slideUp( 'fast' );
- } else {
- button.attr( 'aria-expanded', 'true' );
- panelMeta.addClass( 'open' );
- panelMeta.addClass( 'active-menu-screen-options' );
- options.slideDown( 'fast' );
- }
- return false;
- } );
- // Help toggle
- help.on( 'click keydown', function( event ) {
- if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
- return;
- }
- event.preventDefault();
- if ( 'true' === button.attr( 'aria-expanded' ) ) {
- button.attr( 'aria-expanded', 'false' );
- help.attr( 'aria-expanded', 'true' );
- panelMeta.addClass( 'open' );
- panelMeta.removeClass( 'active-menu-screen-options' );
- options.slideUp( 'fast' );
- content.slideDown( 'fast' );
- }
- } );
- },
- /**
- * Show/hide/save screen options (columns). From common.js.
- */
- ready: function() {
- var panel = this;
- this.container.find( '.hide-column-tog' ).click( function() {
- var $t = $( this ), column = $t.val();
- if ( $t.prop( 'checked' ) ) {
- panel.checked( column );
- } else {
- panel.unchecked( column );
- }
- panel.saveManageColumnsState();
- });
- this.container.find( '.hide-column-tog' ).each( function() {
- var $t = $( this ), column = $t.val();
- if ( $t.prop( 'checked' ) ) {
- panel.checked( column );
- } else {
- panel.unchecked( column );
- }
- });
- },
- saveManageColumnsState: _.debounce( function() {
- var panel = this;
- if ( panel._updateHiddenColumnsRequest ) {
- panel._updateHiddenColumnsRequest.abort();
- }
- panel._updateHiddenColumnsRequest = wp.ajax.post( 'hidden-columns', {
- hidden: panel.hidden(),
- screenoptionnonce: $( '#screenoptionnonce' ).val(),
- page: 'nav-menus'
- } );
- panel._updateHiddenColumnsRequest.always( function() {
- panel._updateHiddenColumnsRequest = null;
- } );
- }, 2000 ),
- checked: function( column ) {
- this.container.addClass( 'field-' + column + '-active' );
- },
- unchecked: function( column ) {
- this.container.removeClass( 'field-' + column + '-active' );
- },
- hidden: function() {
- return $( '.hide-column-tog' ).not( ':checked' ).map( function() {
- var id = this.id;
- return id.substring( 0, id.length - 5 );
- }).get().join( ',' );
- }
- } );
- /**
- * wp.customize.Menus.MenuSection
- *
- * Customizer section for menus. This is used only for lazy-loading child controls.
- * Note that 'nav_menu' must match the WP_Customize_Menu_Section::$type.
- *
- * @constructor
- * @augments wp.customize.Section
- */
- api.Menus.MenuSection = api.Section.extend({
- /**
- * @since Menu Customizer 0.3
- *
- * @param {String} id
- * @param {Object} options
- */
- initialize: function( id, options ) {
- var section = this;
- api.Section.prototype.initialize.call( section, id, options );
- section.deferred.initSortables = $.Deferred();
- },
- /**
- *
- */
- ready: function() {
- var section = this;
- if ( 'undefined' === typeof section.params.menu_id ) {
- throw new Error( 'params.menu_id was not defined' );
- }
- /*
- * Since newly created sections won't be registered in PHP, we need to prevent the
- * preview's sending of the activeSections to result in this control
- * being deactivated when the preview refreshes. So we can hook onto
- * the setting that has the same ID and its presence can dictate
- * whether the section is active.
- */
- section.active.validate = function() {
- if ( ! api.has( section.id ) ) {
- return false;
- }
- return !! api( section.id ).get();
- };
- section.populateControls();
- section.navMenuLocationSettings = {};
- section.assignedLocations = new api.Value( [] );
- api.each(function( setting, id ) {
- var matches = id.match( /^nav_menu_locations\[(.+?)]/ );
- if ( matches ) {
- section.navMenuLocationSettings[ matches[1] ] = setting;
- setting.bind( function() {
- section.refreshAssignedLocations();
- });
- }
- });
- section.assignedLocations.bind(function( to ) {
- section.updateAssignedLocationsInSectionTitle( to );
- });
- section.refreshAssignedLocations();
- api.bind( 'pane-contents-reflowed', function() {
- // Skip menus that have been removed.
- if ( ! section.container.parent().length ) {
- return;
- }
- section.container.find( '.menu-item .menu-item-reorder-nav button' ).attr({ 'tabindex': '0', 'aria-hidden': 'false' });
- section.container.find( '.menu-item.move-up-disabled .menus-move-up' ).attr({ 'tabindex': '-1', 'aria-hidden': 'true' });
- section.container.find( '.menu-item.move-down-disabled .menus-move-down' ).attr({ 'tabindex': '-1', 'aria-hidden': 'true' });
- section.container.find( '.menu-item.move-left-disabled .menus-move-left' ).attr({ 'tabindex': '-1', 'aria-hidden': 'true' });
- section.container.find( '.menu-item.move-right-disabled .menus-move-right' ).attr({ 'tabindex': '-1', 'aria-hidden': 'true' });
- } );
- },
- populateControls: function() {
- var section = this, menuNameControlId, menuAutoAddControlId, menuControl, menuNameControl, menuAutoAddControl;
- // Add the control for managing the menu name.
- menuNameControlId = section.id + '[name]';
- menuNameControl = api.control( menuNameControlId );
- if ( ! menuNameControl ) {
- menuNameControl = new api.controlConstructor.nav_menu_name( menuNameControlId, {
- params: {
- type: 'nav_menu_name',
- content: '<li id="customize-control-' + section.id.replace( '[', '-' ).replace( ']', '' ) + '-name" class="customize-control customize-control-nav_menu_name"></li>', // @todo core should do this for us; see #30741
- label: api.Menus.data.l10n.menuNameLabel,
- active: true,
- section: section.id,
- priority: 0,
- settings: {
- 'default': section.id
- }
- }
- } );
- api.control.add( menuNameControl.id, menuNameControl );
- menuNameControl.active.set( true );
- }
- // Add the menu control.
- menuControl = api.control( section.id );
- if ( ! menuControl ) {
- menuControl = new api.controlConstructor.nav_menu( section.id, {
- params: {
- type: 'nav_menu',
- content: '<li id="customize-control-' + section.id.replace( '[', '-' ).replace( ']', '' ) + '" class="customize-control customize-control-nav_menu"></li>', // @todo core should do this for us; see #30741
- section: section.id,
- priority: 998,
- active: true,
- settings: {
- 'default': section.id
- },
- menu_id: section.params.menu_id
- }
- } );
- api.control.add( menuControl.id, menuControl );
- menuControl.active.set( true );
- }
- // Add the control for managing the menu auto_add.
- menuAutoAddControlId = section.id + '[auto_add]';
- menuAutoAddControl = api.control( menuAutoAddControlId );
- if ( ! menuAutoAddControl ) {
- menuAutoAddControl = new api.controlConstructor.nav_menu_auto_add( menuAutoAddControlId, {
- params: {
- type: 'nav_menu_auto_add',
- content: '<li id="customize-control-' + section.id.replace( '[', '-' ).replace( ']', '' ) + '-auto-add" class="customize-control customize-control-nav_menu_auto_add"></li>', // @todo core should do this for us
- label: '',
- active: true,
- section: section.id,
- priority: 999,
- settings: {
- 'default': section.id
- }
- }
- } );
- api.control.add( menuAutoAddControl.id, menuAutoAddControl );
- menuAutoAddControl.active.set( true );
- }
- },
- /**
- *
- */
- refreshAssignedLocations: function() {
- var section = this,
- menuTermId = section.params.menu_id,
- currentAssignedLocations = [];
- _.each( section.navMenuLocationSettings, function( setting, themeLocation ) {
- if ( setting() === menuTermId ) {
- currentAssignedLocations.push( themeLocation );
- }
- });
- section.assignedLocations.set( currentAssignedLocations );
- },
- /**
- * @param {array} themeLocations
- */
- updateAssignedLocationsInSectionTitle: function( themeLocationSlugs ) {
- var section = this,
- $title;
- $title = section.container.find( '.accordion-section-title:first' );
- $title.find( '.menu-in-location' ).remove();
- _.each( themeLocationSlugs, function( themeLocationSlug ) {
- var $label, locationName;
- $label = $( '<span class="menu-in-location"></span>' );
- locationName = api.Menus.data.locationSlugMappedToName[ themeLocationSlug ];
- $label.text( api.Menus.data.l10n.menuLocation.replace( '%s', locationName ) );
- $title.append( $label );
- });
- section.container.toggleClass( 'assigned-to-menu-location', 0 !== themeLocationSlugs.length );
- },
- onChangeExpanded: function( expanded, args ) {
- var section = this;
- if ( expanded ) {
- wpNavMenu.menuList = section.container.find( '.accordion-section-content:first' );
- wpNavMenu.targetList = wpNavMenu.menuList;
- // Add attributes needed by wpNavMenu
- $( '#menu-to-edit' ).removeAttr( 'id' );
- wpNavMenu.menuList.attr( 'id', 'menu-to-edit' ).addClass( 'menu' );
- _.each( api.section( section.id ).controls(), function( control ) {
- if ( 'nav_menu_item' === control.params.type ) {
- control.actuallyEmbed();
- }
- } );
- if ( 'resolved' !== section.deferred.initSortables.state() ) {
- wpNavMenu.initSortables(); // Depends on menu-to-edit ID being set above.
- section.deferred.initSortables.resolve( wpNavMenu.menuList ); // Now MenuControl can extend the sortable.
- // @todo Note that wp.customize.reflowPaneContents() is debounced, so this immediate change will show a slight flicker while priorities get updated.
- api.control( 'nav_menu[' + String( section.params.menu_id ) + ']' ).reflowMenuItems();
- }
- }
- api.Section.prototype.onChangeExpanded.call( section, expanded, args );
- }
- });
- /**
- * wp.customize.Menus.NewMenuSection
- *
- * Customizer section for new menus.
- * Note that 'new_menu' must match the WP_Customize_New_Menu_Section::$type.
- *
- * @constructor
- * @augments wp.customize.Section
- */
- api.Menus.NewMenuSection = api.Section.extend({
- /**
- * Add behaviors for the accordion section.
- *
- * @since Menu Customizer 0.3
- */
- attachEvents: function() {
- var section = this;
- this.container.on( 'click', '.add-menu-toggle', function() {
- if ( section.expanded() ) {
- section.collapse();
- } else {
- section.expand();
- }
- });
- },
- /**
- * Update UI to reflect expanded state.
- *
- * @since 4.1.0
- *
- * @param {Boolean} expanded
- */
- onChangeExpanded: function( expanded ) {
- var section = this,
- button = section.container.find( '.add-menu-toggle' ),
- content = section.container.find( '.new-menu-section-content' ),
- customizer = section.container.closest( '.wp-full-overlay-sidebar-content' );
- if ( expanded ) {
- button.addClass( 'open' );
- button.attr( 'aria-expanded', 'true' );
- content.slideDown( 'fast', function() {
- customizer.scrollTop( customizer.height() );
- });
- } else {
- button.removeClass( 'open' );
- button.attr( 'aria-expanded', 'false' );
- content.slideUp( 'fast' );
- content.find( '.menu-name-field' ).removeClass( 'invalid' );
- }
- }
- });
- /**
- * wp.customize.Menus.MenuLocationControl
- *
- * Customizer control for menu locations (rendered as a <select>).
- * Note that 'nav_menu_location' must match the WP_Customize_Nav_Menu_Location_Control::$type.
- *
- * @constructor
- * @augments wp.customize.Control
- */
- api.Menus.MenuLocationControl = api.Control.extend({
- initialize: function( id, options ) {
- var control = this,
- matches = id.match( /^nav_menu_locations\[(.+?)]/ );
- control.themeLocation = matches[1];
- api.Control.prototype.initialize.call( control, id, options );
- },
- ready: function() {
- var control = this, navMenuIdRegex = /^nav_menu\[(-?\d+)]/;
- // @todo It would be better if this was added directly on the setting itself, as opposed to the control.
- control.setting.validate = function( value ) {
- return parseInt( value, 10 );
- };
- // Add/remove menus from the available options when they are added and removed.
- api.bind( 'add', function( setting ) {
- var option, menuId, matches = setting.id.match( navMenuIdRegex );
- if ( ! matches || false === setting() ) {
- return;
- }
- menuId = matches[1];
- option = new Option( displayNavMenuName( setting().name ), menuId );
- control.container.find( 'select' ).append( option );
- });
- api.bind( 'remove', function( setting ) {
- var menuId, matches = setting.id.match( navMenuIdRegex );
- if ( ! matches ) {
- return;
- }
- menuId = parseInt( matches[1], 10 );
- if ( control.setting() === menuId ) {
- control.setting.set( '' );
- }
- control.container.find( 'option[value=' + menuId + ']' ).remove();
- });
- api.bind( 'change', function( setting ) {
- var menuId, matches = setting.id.match( navMenuIdRegex );
- if ( ! matches ) {
- return;
- }
- menuId = parseInt( matches[1], 10 );
- if ( false === setting() ) {
- if ( control.setting() === menuId ) {
- control.setting.set( '' );
- }
- control.container.find( 'option[value=' + menuId + ']' ).remove();
- } else {
- control.container.find( 'option[value=' + menuId + ']' ).text( displayNavMenuName( setting().name ) );
- }
- });
- }
- });
- /**
- * wp.customize.Menus.MenuItemControl
- *
- * Customizer control for menu items.
- * Note that 'menu_item' must match the WP_Customize_Menu_Item_Control::$type.
- *
- * @constructor
- * @augments wp.customize.Control
- */
- api.Menus.MenuItemControl = api.Control.extend({
- /**
- * @inheritdoc
- */
- initialize: function( id, options ) {
- var control = this;
- api.Control.prototype.initialize.call( control, id, options );
- control.active.validate = function() {
- var value, section = api.section( control.section() );
- if ( section ) {
- value = section.active();
- } else {
- value = false;
- }
- return value;
- };
- },
- /**
- * @since Menu Customizer 0.3
- *
- * Override the embed() method to do nothing,
- * so that the control isn't embedded on load,
- * unless the containing section is already expanded.
- */
- embed: function() {
- var control = this,
- sectionId = control.section(),
- section;
- if ( ! sectionId ) {
- return;
- }
- section = api.section( sectionId );
- if ( ( section && section.expanded() ) || api.settings.autofocus.control === control.id ) {
- control.actuallyEmbed();
- }
- },
- /**
- * This function is called in Section.onChangeExpanded() so the control
- * will only get embedded when the Section is first expanded.
- *
- * @since Menu Customizer 0.3
- */
- actuallyEmbed: function() {
- var control = this;
- if ( 'resolved' === control.deferred.embedded.state() ) {
- return;
- }
- control.renderContent();
- control.deferred.embedded.resolve(); // This triggers control.ready().
- },
- /**
- * Set up the control.
- */
- ready: function() {
- if ( 'undefined' === typeof this.params.menu_item_id ) {
- throw new Error( 'params.menu_item_id was not defined' );
- }
- this._setupControlToggle();
- this._setupReorderUI();
- this._setupUpdateUI();
- this._setupRemoveUI();
- this._setupLinksUI();
- this._setupTitleUI();
- },
- /**
- * Show/hide the settings when clicking on the menu item handle.
- */
- _setupControlToggle: function() {
- var control = this;
- this.container.find( '.menu-item-handle' ).on( 'click', function( e ) {
- e.preventDefault();
- e.stopPropagation();
- var menuControl = control.getMenuControl();
- if ( menuControl.isReordering || menuControl.isSorting ) {
- return;
- }
- control.toggleForm();
- } );
- },
- /**
- * Set up the menu-item-reorder-nav
- */
- _setupReorderUI: function() {
- var control = this, template, $reorderNav;
- template = wp.template( 'menu-item-reorder-nav' );
- // Add the menu item reordering elements to the menu item control.
- control.container.find( '.item-controls' ).after( template );
- // Handle clicks for up/down/left-right on the reorder nav.
- $reorderNav = control.container.find( '.menu-item-reorder-nav' );
- $reorderNav.find( '.menus-move-up, .menus-move-down, .menus-move-left, .menus-move-right' ).on( 'click', function() {
- var moveBtn = $( this );
- moveBtn.focus();
- var isMoveUp = moveBtn.is( '.menus-move-up' ),
- isMoveDown = moveBtn.is( '.menus-move-down' ),
- isMoveLeft = moveBtn.is( '.menus-move-left' ),
- isMoveRight = moveBtn.is( '.menus-move-right' );
- if ( isMoveUp ) {
- control.moveUp();
- } else if ( isMoveDown ) {
- control.moveDown();
- } else if ( isMoveLeft ) {
- control.moveLeft();
- } else if ( isMoveRight ) {
- control.moveRight();
- }
- moveBtn.focus(); // Re-focus after the container was moved.
- } );
- },
- /**
- * Set up event handlers for menu item updating.
- */
- _setupUpdateUI: function() {
- var control = this,
- settingValue = control.setting();
- control.elements = {};
- control.elements.url = new api.Element( control.container.find( '.edit-menu-item-url' ) );
- control.elements.title = new api.Element( control.container.find( '.edit-menu-item-title' ) );
- control.elements.attr_title = new api.Element( control.container.find( '.edit-menu-item-attr-title' ) );
- control.elements.target = new api.Element( control.container.find( '.edit-menu-item-target' ) );
- control.elements.classes = new api.Element( control.container.find( '.edit-menu-item-classes' ) );
- control.elements.xfn = new api.Element( control.container.find( '.edit-menu-item-xfn' ) );
- control.elements.description = new api.Element( control.container.find( '.edit-menu-item-description' ) );
- // @todo allow other elements, added by plugins, to be automatically picked up here; allow additional values to be added to setting array.
- _.each( control.elements, function( element, property ) {
- element.bind(function( value ) {
- if ( element.element.is( 'input[type=checkbox]' ) ) {
- value = ( value ) ? element.element.val() : '';
- }
- var settingValue = control.setting();
- if ( settingValue && settingValue[ property ] !== value ) {
- settingValue = _.clone( settingValue );
- settingValue[ property ] = value;
- control.setting.set( settingValue );
- }
- });
- if ( settingValue ) {
- if ( ( property === 'classes' || property === 'xfn' ) && _.isArray( settingValue[ property ] ) ) {
- element.set( settingValue[ property ].join( ' ' ) );
- } else {
- element.set( settingValue[ property ] );
- }
- }
- });
- control.setting.bind(function( to, from ) {
- var itemId = control.params.menu_item_id,
- followingSiblingItemControls = [],
- childrenItemControls = [],
- menuControl;
- if ( false === to ) {
- menuControl = api.control( 'nav_menu[' + String( from.nav_menu_term_id ) + ']' );
- control.container.remove();
- _.each( menuControl.getMenuItemControls(), function( otherControl ) {
- if ( from.menu_item_parent === otherControl.setting().menu_item_parent && otherControl.setting().position > from.position ) {
- followingSiblingItemControls.push( otherControl );
- } else if ( otherControl.setting().menu_item_parent === itemId ) {
- childrenItemControls.push( otherControl );
- }
- });
- // Shift all following siblings by the number of children this item has.
- _.each( followingSiblingItemControls, function( followingSiblingItemControl ) {
- var value = _.clone( followingSiblingItemControl.setting() );
- value.position += childrenItemControls.length;
- followingSiblingItemControl.setting.set( value );
- });
- // Now move the children up to be the new subsequent siblings.
- _.each( childrenItemControls, function( childrenItemControl, i ) {
- var value = _.clone( childrenItemControl.setting() );
- value.position = from.position + i;
- value.menu_item_parent = from.menu_item_parent;
- childrenItemControl.setting.set( value );
- });
- menuControl.debouncedReflowMenuItems();
- } else {
- // Update the elements' values to match the new setting properties.
- _.each( to, function( value, key ) {
- if ( control.elements[ key] ) {
- control.elements[ key ].set( to[ key ] );
- }
- } );
- control.container.find( '.menu-item-data-parent-id' ).val( to.menu_item_parent );
- // Handle UI updates when the position or depth (parent) change.
- if ( to.position !== from.position || to.menu_item_parent !== from.menu_item_parent ) {
- control.getMenuControl().debouncedReflowMenuItems();
- }
- }
- });
- },
- /**
- * Set up event handlers for menu item deletion.
- */
- _setupRemoveUI: function() {
- var control = this, $removeBtn;
- // Configure delete button.
- $removeBtn = control.container.find( '.item-delete' );
- $removeBtn.on( 'click', function() {
- // Find an adjacent element to add focus to when this menu item goes away
- var addingItems = true, $adjacentFocusTarget, $next, $prev;
- if ( ! $( 'body' ).hasClass( 'adding-menu-items' ) ) {
- addingItems = false;
- }
- $next = control.container.nextAll( '.customize-control-nav_menu_item:visible' ).first();
- $prev = control.container.prevAll( '.customize-control-nav_menu_item:visible' ).first();
- if ( $next.length ) {
- $adjacentFocusTarget = $next.find( false === addingItems ? '.item-edit' : '.item-delete' ).first();
- } else if ( $prev.length ) {
- $adjacentFocusTarget = $prev.find( false === addingItems ? '.item-edit' : '.item-delete' ).first();
- } else {
- $adjacentFocusTarget = control.container.nextAll( '.customize-control-nav_menu' ).find( '.add-new-menu-item' ).first();
- }
- control.container.slideUp( function() {
- control.setting.set( false );
- wp.a11y.speak( api.Menus.data.l10n.itemDeleted );
- $adjacentFocusTarget.focus(); // keyboard accessibility
- } );
- } );
- },
- _setupLinksUI: function() {
- var $origBtn;
- // Configure original link.
- $origBtn = this.container.find( 'a.original-link' );
- $origBtn.on( 'click', function( e ) {
- e.preventDefault();
- api.previewer.previewUrl( e.target.toString() );
- } );
- },
- /**
- * Update item handle title when changed.
- */
- _setupTitleUI: function() {
- var control = this;
- control.setting.bind( function( item ) {
- if ( ! item ) {
- return;
- }
- var titleEl = control.container.find( '.menu-item-title' ),
- titleText = item.title || api.Menus.data.l10n.untitled;
- if ( item._invalid ) {
- titleText = api.Menus.data.l10n.invalidTitleTpl.replace( '%s', titleText );
- }
- // Don't update to an empty title.
- if ( item.title ) {
- titleEl
- .text( titleText )
- .removeClass( 'no-title' );
- } else {
- titleEl
- .text( titleText )
- .addClass( 'no-title' );
- }
- } );
- },
- /**
- *
- * @returns {number}
- */
- getDepth: function() {
- var control = this, setting = control.setting(), depth = 0;
- if ( ! setting ) {
- return 0;
- }
- while ( setting && setting.menu_item_parent ) {
- depth += 1;
- control = api.control( 'nav_menu_item[' + setting.menu_item_parent + ']' );
- if ( ! control ) {
- break;
- }
- setting = control.setting();
- }
- return depth;
- },
- /**
- * Amend the control's params with the data necessary for the JS template just in time.
- */
- renderContent: function() {
- var control = this,
- settingValue = control.setting(),
- containerClasses;
- control.params.title = settingValue.title || '';
- control.params.depth = control.getDepth();
- control.container.data( 'item-depth', control.params.depth );
- containerClasses = [
- 'menu-item',
- 'menu-item-depth-' + String( control.params.depth ),
- 'menu-item-' + settingValue.object,
- 'menu-item-edit-inactive'
- ];
- if ( settingValue._invalid ) {
- containerClasses.push( 'menu-item-invalid' );
- control.params.title = api.Menus.data.l10n.invalidTitleTpl.replace( '%s', control.params.title );
- } else if ( 'draft' === settingValue.status ) {
- containerClasses.push( 'pending' );
- control.params.title = api.Menus.data.pendingTitleTpl.replace( '%s', control.params.title );
- }
- control.params.el_classes = containerClasses.join( ' ' );
- control.params.item_type_label = settingValue.type_label;
- control.params.item_type = settingValue.type;
- control.params.url = settingValue.url;
- control.params.target = settingValue.target;
- control.params.attr_title = settingValue.attr_title;
- control.params.classes = _.isArray( settingValue.classes ) ? settingValue.classes.join( ' ' ) : settingValue.classes;
- control.params.attr_title = settingValue.attr_title;
- control.params.xfn = settingValue.xfn;
- control.params.description = settingValue.description;
- control.params.parent = settingValue.menu_item_parent;
- control.params.original_title = settingValue.original_title || '';
- control.container.addClass( control.params.el_classes );
- api.Control.prototype.renderContent.call( control );
- },
- /***********************************************************************
- * Begin public API methods
- **********************************************************************/
- /**
- * @return {wp.customize.controlConstructor.nav_menu|null}
- */
- getMenuControl: function() {
- var control = this, settingValue = control.setting();
- if ( settingValue && settingValue.nav_menu_term_id ) {
- return api.control( 'nav_menu[' + settingValue.nav_menu_term_id + ']' );
- } else {
- return null;
- }
- },
- /**
- * Expand the accordion section containing a control
- */
- expandControlSection: function() {
- var $section = this.container.closest( '.accordion-section' );
- if ( ! $section.hasClass( 'open' ) ) {
- $section.find( '.accordion-section-title:first' ).trigger( 'click' );
- }
- },
- /**
- * Expand the menu item form control.
- *
- * @since 4.5.0 Added params.completeCallback.
- *
- * @param {Object} [params] - Optional params.
- * @param {Function} [params.completeCallback] - Function to call when the form toggle has finished animating.
- */
- expandForm: function( params ) {
- this.toggleForm( true, params );
- },
- /**
- * Collapse the menu item form control.
- *
- * @since 4.5.0 Added params.completeCallback.
- *
- * @param {Object} [params] - Optional params.
- * @param {Function} [params.completeCallback] - Function to call when the form toggle has finished animating.
- */
- collapseForm: function( params ) {
- this.toggleForm( false, params );
- },
- /**
- * Expand or collapse the menu item control.
- *
- * @since 4.5.0 Added params.completeCallback.
- *
- * @param {boolean} [showOrHide] - If not supplied, will be inverse of current visibility
- * @param {Object} [params] - Optional params.
- * @param {Function} [params.completeCallback] - Function to call when the form toggle has finished animating.
- */
- toggleForm: function( showOrHide, params ) {
- var self = this, $menuitem, $inside, complete;
- $menuitem = this.container;
- $inside = $menuitem.find( '.menu-item-settings:first' );
- if ( 'undefined' === typeof showOrHide ) {
- showOrHide = ! $inside.is( ':visible' );
- }
- // Already expanded or collapsed.
- if ( $inside.is( ':visible' ) === showOrHide ) {
- if ( params && params.completeCallback ) {
- params.completeCallback();
- }
- return;
- }
- if ( showOrHide ) {
- // Close all other menu item controls before expanding this one.
- api.control.each( function( otherControl ) {
- if ( self.params.type === otherControl.params.type && self !== otherControl ) {
- otherControl.collapseForm();
- }
- } );
- complete = function() {
- $menuitem
- .removeClass( 'menu-item-edit-inactive' )
- .addClass( 'menu-item-edit-active' );
- self.container.trigger( 'expanded' );
- if ( params && params.completeCallback ) {
- params.completeCallback();
- }
- };
- $menuitem.find( '.item-edit' ).attr( 'aria-expanded', 'true' );
- $inside.slideDown( 'fast', complete );
- self.container.trigger( 'expand' );
- } else {
- complete = function() {
- $menuitem
- .addClass( 'menu-item-edit-inactive' )
- .removeClass( 'menu-item-edit-active' );
- self.container.trigger( 'collapsed' );
- if ( params && params.completeCallback ) {
- params.completeCallback();
- }
- };
- self.container.trigger( 'collapse' );
- $menuitem.find( '.item-edit' ).attr( 'aria-expanded', 'false' );
- $inside.slideUp( 'fast', complete );
- }
- },
- /**
- * Expand the containing menu section, expand the form, and focus on
- * the first input in the control.
- *
- * @since 4.5.0 Added params.completeCallback.
- *
- * @param {Object} [params] - Params object.
- * @param {Function} [params.completeCallback] - Optional callback function when focus has completed.
- */
- focus: function( params ) {
- params = params || {};
- var control = this, originalCompleteCallback = params.completeCallback;
- control.expandControlSection();
- params.completeCallback = function() {
- var focusable;
- // Note that we can't use :focusable due to a jQuery UI issue. See: https://github.com/jquery/jquery-ui/pull/1583
- focusable = control.container.find( '.menu-item-settings' ).find( 'input, select, textarea, button, object, a[href], [tabindex]' ).filter( ':visible' );
- focusable.first().focus();
- if ( originalCompleteCallback ) {
- originalCompleteCallback();
- }
- };
- control.expandForm( params );
- },
- /**
- * Move menu item up one in the menu.
- */
- moveUp: function() {
- this._changePosition( -1 );
- wp.a11y.speak( api.Menus.data.l10n.movedUp );
- },
- /**
- * Move menu item up one in the menu.
- */
- moveDown: function() {
- this._changePosition( 1 );
- wp.a11y.speak( api.Menus.data.l10n.movedDown );
- },
- /**
- * Move menu item and all children up one level of depth.
- */
- moveLeft: function() {
- this._changeDepth( -1 );
- wp.a11y.speak( api.Menus.data.l10n.movedLeft );
- },
- /**
- * Move menu item and children one level deeper, as a submenu of the previous item.
- */
- moveRight: function() {
- this._changeDepth( 1 );
- wp.a11y.speak( api.Menus.data.l10n.movedRight );
- },
- /**
- * Note that this will trigger a UI update, causing child items to
- * move as well and cardinal order class names to be updated.
- *
- * @private
- *
- * @param {Number} offset 1|-1
- */
- _changePosition: function( offset ) {
- var control = this,
- adjacentSetting,
- settingValue = _.clone( control.setting() ),
- siblingSettings = [],
- realPosition;
- if ( 1 !== offset && -1 !== offset ) {
- throw new Error( 'Offset changes by 1 are only supported.' );
- }
- // Skip moving deleted items.
- if ( ! control.setting() ) {
- return;
- }
- // Locate the other items under the same parent (siblings).
- _( control.getMenuControl().getMenuItemControls() ).each(function( otherControl ) {
- if ( otherControl.setting().menu_item_parent === settingValue.menu_item_parent ) {
- siblingSettings.push( otherControl.setting );
- }
- });
- siblingSettings.sort(function( a, b ) {
- return a().position - b().position;
- });
- realPosition = _.indexOf( siblingSettings, control.setting );
- if ( -1 === realPosition ) {
- throw new Error( 'Expected setting to be among siblings.' );
- }
- // Skip doing anything if the item is already at the edge in the desired direction.
- if ( ( realPosition === 0 && offset < 0 ) || ( realPosition === siblingSettings.length - 1 && offset > 0 ) ) {
- // @todo Should we allow a menu item to be moved up to break it out of a parent? Adopt with previous or following parent?
- return;
- }
- // Update any adjacent menu item setting to take on this item's position.
- adjacentSetting = siblingSettings[ realPosition + offset ];
- if ( adjacentSetting ) {
- adjacentSetting.set( $.extend(
- _.clone( adjacentSetting() ),
- {
- position: settingValue.position
- }
- ) );
- }
- settingValue.position += offset;
- control.setting.set( settingValue );
- },
- /**
- * Note that this will trigger a UI update, causing child items to
- * move as well and cardinal order class names to be updated.
- *
- * @private
- *
- * @param {Number} offset 1|-1
- */
- _changeDepth: function( offset ) {
- if ( 1 !== offset && -1 !== offset ) {
- throw new Error( 'Offset changes by 1 are only supported.' );
- }
- var control = this,
- settingValue = _.clone( control.setting() ),
- siblingControls = [],
- realPosition,
- siblingControl,
- parentControl;
- // Locate the other items under the same parent (siblings).
- _( control.getMenuControl().getMenuItemControls() ).each(function( otherControl ) {
- if ( otherControl.setting().menu_item_parent === settingValue.menu_item_parent ) {
- siblingControls.push( otherControl );
- }
- });
- siblingControls.sort(function( a, b ) {
- return a.setting().position - b.setting().position;
- });
- realPosition = _.indexOf( siblingControls, control );
- if ( -1 === realPosition ) {
- throw new Error( 'Expected control to be among siblings.' );
- }
- if ( -1 === offset ) {
- // Skip moving left an item that is already at the top level.
- if ( ! settingValue.menu_item_parent ) {
- return;
- }
- parentControl = api.control( 'nav_menu_item[' + set