# polylang/trunk/js/build/nav-menu.js

Polylang, version trunk. 323 lines.

- Page: https://pluginprobe.com/plugins/polylang/trunk/code/js/build/nav-menu.js
- Raw: https://pluginprobe.com/plugins/polylang/trunk/raw/js/build/nav-menu.js
- Modified: 2026-09-14T16:30:38+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/polylang/trunk/code/js/build/nav-menu.js#L10-L20`.

```javascript
/**
 * Handles the options in the language switcher nav menu metabox.
 */

const pllNavMenu = {
	/**
	 * The element wrapping the menu elements.
	 *
	 * @member {HTMLElement|null}
	 */
	wrapper: null,

	/**
	 * Init.
	 */
	init: () => {
		if ( document.readyState !== 'loading' ) {
			pllNavMenu.ready();
		} else {
			document.addEventListener( 'DOMContentLoaded', pllNavMenu.ready );
		}
	},

	/**
	 * Called when the DOM is ready. Attaches the events to the wrapper.
	 */
	ready: () => {
		pllNavMenu.wrapper = document.getElementById( 'menu-to-edit' );

		if ( ! pllNavMenu.wrapper ) {
			return;
		}

		pllNavMenu.wrapper.addEventListener( 'click', pllNavMenu.printMetabox );
		pllNavMenu.wrapper.addEventListener( 'change', pllNavMenu.manageRowsAndValues );
	},

	printMetabox: {
		/**
		 * Event callback that prints our checkboxes in the language switcher.
		 *
		 * @param {Event} event The event.
		 */
		handleEvent: event => {
			if ( ! event.target.classList.contains( 'item-edit' ) ) {
				// Not clicking on a Edit arrow button.
				return;
			}

			const metabox = event.target.closest( '.menu-item' )?.querySelector( '.menu-item-settings' );

			if ( ! metabox?.id ) {
				// Should not happen.
				return;
			}

			if ( ! metabox.querySelectorAll( 'input[value="#pll_switcher"][type=text]' ).length ) {
				// Not our metabox, or already replaced.
				return;
			}

			// Remove default fields we don't need.
			[ ...metabox.children ].forEach( el => {
				if ( 'P' === el.nodeName && ! el.classList.contains( 'field-move' ) ) {
					el.remove();
				}
			} );

			const thus = pllNavMenu.printMetabox;
			const itemId = Number( metabox.id.replace( 'menu-item-settings-', '' ) );

			metabox.append( thus.createHiddenInput( 'title', itemId, pll_data.title ) );

			metabox.append( thus.createHiddenInput( 'url', itemId, '#pll_switcher' ) );
			metabox.append( thus.createHiddenInput( 'pll-detect', itemId, 1 ) );

			const menuValues =
				typeof pll_data.val[ itemId ] !== 'undefined' ? pll_data.val[ itemId ] : {}; // eslint-disable-line security/detect-object-injection -- Data generated by PLL_Admin_Nav_Menu::admin_enqueue_scripts() in src/admin/admin-nav-menu.php.

			// Create a global wrapper for our settings.
			const settingsWrapper = thus.createElement( 'div', {
				class: 'polylang-language-switcher-menu-content',
			} );
			metabox.prepend( settingsWrapper );

			// Wrap sections in `fieldset` tags.
			let sectionWrapper = settingsWrapper; // This 1st value will never be used.

			// Add our settings.
			for ( const [ optionName, optionData ] of Object.entries( pll_data.data ) ) {
				const optionValue =
					typeof menuValues[ optionName ] !== 'undefined' // eslint-disable-line security/detect-object-injection -- Data generated by PLL_Admin_Nav_Menu::admin_enqueue_scripts() in src/admin/admin-nav-menu.php.
						? menuValues[ optionName ] // eslint-disable-line security/detect-object-injection -- Data generated by PLL_Admin_Nav_Menu::admin_enqueue_scripts() in src/admin/admin-nav-menu.php.
						: optionData.default;

				// Create a section with title.
				if ( optionData.section ) {
					sectionWrapper = thus.createElement( 'fieldset' );
					settingsWrapper.append( sectionWrapper );

					const sectionTitle = thus.createElement( 'legend' );
					sectionTitle.innerText = optionData.section;
					sectionWrapper.append( sectionTitle );
				}

				// Create the row wrapper.
				const inputWrapper = thus.createWrapper( optionData, menuValues );
				sectionWrapper.append( inputWrapper );

				// Create the label.
				const label = thus.createElement( 'label', {
					for: `edit-menu-item-${ optionName }-${ itemId }`,
				} );
				label.innerText = ` ${ optionData.label } `;
				inputWrapper.append( label );

				// Create the input.
				if ( optionData.choices ) {
					const input = thus.createSelectInput(
						optionName,
						itemId,
						optionValue,
						optionData.choices
					);
					label.append( input );
				} else {
					const input = thus.createCheckboxInput( optionName, itemId, optionValue );
					label.prepend( input );
				}
			}
		},

		/**
		 * Creates and returns a `<input type="hidden"/>` element.
		 *
		 * @param {string}        id     An identifier for this input. It will be part of the final `id` attribute.
		 * @param {number}        itemId The ID of the menu element (post ID).
		 * @param {string|number} value  The input's value.
		 * @return {HTMLElement} The input element.
		 */
		createHiddenInput: ( id, itemId, value ) => {
			return pllNavMenu.printMetabox.createInput( id, itemId, value, 'hidden' );
		},

		/**
		 * Creates and returns a wrapper row.
		 *
		 * @param {Object} optionData           A field definition.
		 * @param {Object} {optionData.hide_if} Optional. "Hide row" conditions.
		 * @param {Object} menuValues           The input values.
		 * @return {HTMLElement} The input element, wrapped in a `<p>` tag.
		 */
		createWrapper: ( optionData, menuValues ) => {
			const wrapperAtts = { class: 'description' };

			if ( ! optionData.hide_if ) {
				return pllNavMenu.printMetabox.createElement( 'p', wrapperAtts );
			}

			for ( const [ conditionName, conditionValue ] of Object.entries( optionData.hide_if ) ) {
				// eslint-disable-next-line security/detect-object-injection -- Data generated by PLL_Admin_Nav_Menu::admin_enqueue_scripts() in src/admin/admin-nav-menu.php.
				if ( typeof pll_data.data[ conditionName ] === 'undefined' ) {
					// Should not happen.
					continue;
				}

				const menuValue =
					typeof menuValues[ conditionName ] !== 'undefined' // eslint-disable-line security/detect-object-injection -- Data generated by PLL_Admin_Nav_Menu::admin_enqueue_scripts() in src/admin/admin-nav-menu.php.
						? menuValues[ conditionName ] // eslint-disable-line security/detect-object-injection -- Data generated by PLL_Admin_Nav_Menu::admin_enqueue_scripts() in src/admin/admin-nav-menu.php.
						: pll_data.data[ conditionName ].default; // eslint-disable-line security/detect-object-injection -- Data generated by PLL_Admin_Nav_Menu::admin_enqueue_scripts() in src/admin/admin-nav-menu.php.
				wrapperAtts.class += ` pll-hidden-if-${ conditionName }-${ conditionValue }`;

				if ( conditionValue === menuValue ) {
					wrapperAtts.class += ` pll-hidden-by-${ conditionName }`;
				}
			}

			return pllNavMenu.printMetabox.createElement( 'p', wrapperAtts );
		},

		/**
		 * Creates and returns a `<input type="checkbox"/>` element.
		 *
		 * @param {string}         id     An identifier for this input. It will be part of the final `id` attribute.
		 * @param {number}         itemId The ID of the menu element (post ID).
		 * @param {boolean|number} value  The option value.
		 * @return {HTMLElement} The input element.
		 */
		createCheckboxInput: ( id, itemId, value ) => {
			const input = pllNavMenu.printMetabox.createInput( id, itemId, 1, 'checkbox' );
			input.setAttribute( 'data-key', id );
			if ( value ) {
				input.checked = true;
			}
			return input;
		},

		/**
		 * Creates and returns a `<select/>` element.
		 *
		 * @param {string}        id      An identifier for this input. It will be part of the final `id` attribute.
		 * @param {number}        itemId  The ID of the menu element (post ID).
		 * @param {string|number} value   The option value.
		 * @param {Object}        choices Choices.
		 * @return {HTMLElement} The input element.
		 */
		createSelectInput: ( id, itemId, value, choices ) => {
			const input = pllNavMenu.printMetabox.createElement( 'select', {
				id: `edit-menu-item-${ id }-${ itemId }`,
				name: `menu-item-${ id }[${ itemId }]`,
				'data-key': id,
			} );
			for ( const [ optionValue, optionLabel ] of Object.entries( choices ) ) {
				const atts = { value: optionValue };
				if ( value === optionValue ) {
					atts.selected = 'selected';
				}
				const option = pllNavMenu.printMetabox.createElement( 'option', atts );
				option.innerText = optionLabel;
				input.append( option );
			}
			return input;
		},

		/**
		 * Creates and returns a `<input/>` element.
		 *
		 * @param {string}        id     An identifier for this input. It will be part of the final `id` attribute.
		 * @param {number}        itemId The ID of the menu element (post ID).
		 * @param {string|number} value  The input's value.
		 * @param {string}        type   The type of input.
		 * @return {HTMLElement} The input element.
		 */
		createInput: ( id, itemId, value, type ) => {
			return pllNavMenu.printMetabox.createElement( 'input', {
				type,
				id: `edit-menu-item-${ id }-${ itemId }`,
				name: `menu-item-${ id }[${ itemId }]`,
				value,
			} );
		},

		/**
		 * Creates and returns an element.
		 *
		 * @param {string} type   Element's type.
		 * @param {Object} {atts} Optional. Element's attributes.
		 * @return {HTMLElement} The element.
		 */
		createElement: ( type, atts = {} ) => {
			const el = document.createElement( type );
			for ( const [ key, value ] of Object.entries( atts ) ) {
				el.setAttribute( key, value );
			}
			return el;
		},
	},

	manageRowsAndValues: {
		/**
		 * Event callback that hides rows and forbids disabling both flag and name/code.
		 *
		 * @param {Event} event The event.
		 */
		handleEvent: event => {
			let value = '';

			if ( 'SELECT' === event.target.nodeName ) {
				value = event.target.value;
			} else if ( 'INPUT' === event.target.nodeName && 'checkbox' === event.target.type ) {
				value = event.target.checked;
			} else {
				return;
			}

			const wrapper = event.target.closest( '.polylang-language-switcher-menu-content' );

			if ( ! wrapper ) {
				return;
			}

			// Hide rows.
			const key = event.target.getAttribute( 'data-key' );

			if ( ! key ) {
				return;
			}

			const valueSelector = typeof value === 'string' ? value.replaceAll( ':', '\\:' ) : value; // For flag format.
			wrapper
				.querySelectorAll(
					`[class*="pll-hidden-if-${ key }-"]:not(.pll-hidden-if-${ key }-${ valueSelector })`
				)
				.forEach( input => {
					input.classList.remove( `pll-hidden-by-${ key }` );
				} );
			wrapper.querySelectorAll( `.pll-hidden-if-${ key }-${ valueSelector }` ).forEach( input => {
				input.classList.add( `pll-hidden-by-${ key }` );
			} );

			// Forbid disabling both flag and name/code.
			if ( 'show_labels' === key && '' === value ) {
				const otherInput = wrapper.querySelector( '[data-key="show_flags"]' );

				if ( otherInput && true !== otherInput.checked ) {
					otherInput.checked = true;
					otherInput.dispatchEvent( new Event( 'change', { bubbles: true } ) );
				}
			} else if ( 'show_flags' === key && false === value ) {
				const otherInput = wrapper.querySelector( '[data-key="show_labels"]' );

				if ( otherInput && '' === otherInput.value ) {
					otherInput.value = 'names';
					otherInput.dispatchEvent( new Event( 'change', { bubbles: true } ) );
				}
			}
		},
	},
};

pllNavMenu.init();


```
