/** * 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 `` 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 `
` 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 `` 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 `` 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 `` 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();