PluginProbe
Polylang / trunk
Polylang vtrunk
3.8.9 3.8.8 3.8.7 3.8.6 3.8.5 3.8.4 3.8.3 2.7 2.7.0.1 2.7.1 2.7.2 2.7.3 2.7.4 2.8 2.8.1 2.8.2 2.8.3 2.8.4 2.9 2.9.1 2.9.2 3.0 3.0.1 3.0.2 3.0.3 All 233 releases
polylang / js / build / nav-menu.js

nav-menu.js in Polylang trunk, at js/build/nav-menu.js

323 lines 10.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Handles the options in the language switcher nav menu metabox.
3 */
4
5 const pllNavMenu = {
6 /**
7 * The element wrapping the menu elements.
8 *
9 * @member {HTMLElement|null}
10 */
11 wrapper: null,
12
13 /**
14 * Init.
15 */
16 init: () => {
17 if ( document.readyState !== 'loading' ) {
18 pllNavMenu.ready();
19 } else {
20 document.addEventListener( 'DOMContentLoaded', pllNavMenu.ready );
21 }
22 },
23
24 /**
25 * Called when the DOM is ready. Attaches the events to the wrapper.
26 */
27 ready: () => {
28 pllNavMenu.wrapper = document.getElementById( 'menu-to-edit' );
29
30 if ( ! pllNavMenu.wrapper ) {
31 return;
32 }
33
34 pllNavMenu.wrapper.addEventListener( 'click', pllNavMenu.printMetabox );
35 pllNavMenu.wrapper.addEventListener( 'change', pllNavMenu.manageRowsAndValues );
36 },
37
38 printMetabox: {
39 /**
40 * Event callback that prints our checkboxes in the language switcher.
41 *
42 * @param {Event} event The event.
43 */
44 handleEvent: event => {
45 if ( ! event.target.classList.contains( 'item-edit' ) ) {
46 // Not clicking on a Edit arrow button.
47 return;
48 }
49
50 const metabox = event.target.closest( '.menu-item' )?.querySelector( '.menu-item-settings' );
51
52 if ( ! metabox?.id ) {
53 // Should not happen.
54 return;
55 }
56
57 if ( ! metabox.querySelectorAll( 'input[value="#pll_switcher"][type=text]' ).length ) {
58 // Not our metabox, or already replaced.
59 return;
60 }
61
62 // Remove default fields we don't need.
63 [ ...metabox.children ].forEach( el => {
64 if ( 'P' === el.nodeName && ! el.classList.contains( 'field-move' ) ) {
65 el.remove();
66 }
67 } );
68
69 const thus = pllNavMenu.printMetabox;
70 const itemId = Number( metabox.id.replace( 'menu-item-settings-', '' ) );
71
72 metabox.append( thus.createHiddenInput( 'title', itemId, pll_data.title ) );
73
74 metabox.append( thus.createHiddenInput( 'url', itemId, '#pll_switcher' ) );
75 metabox.append( thus.createHiddenInput( 'pll-detect', itemId, 1 ) );
76
77 const menuValues =
78 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.
79
80 // Create a global wrapper for our settings.
81 const settingsWrapper = thus.createElement( 'div', {
82 class: 'polylang-language-switcher-menu-content',
83 } );
84 metabox.prepend( settingsWrapper );
85
86 // Wrap sections in `fieldset` tags.
87 let sectionWrapper = settingsWrapper; // This 1st value will never be used.
88
89 // Add our settings.
90 for ( const [ optionName, optionData ] of Object.entries( pll_data.data ) ) {
91 const optionValue =
92 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.
93 ? 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.
94 : optionData.default;
95
96 // Create a section with title.
97 if ( optionData.section ) {
98 sectionWrapper = thus.createElement( 'fieldset' );
99 settingsWrapper.append( sectionWrapper );
100
101 const sectionTitle = thus.createElement( 'legend' );
102 sectionTitle.innerText = optionData.section;
103 sectionWrapper.append( sectionTitle );
104 }
105
106 // Create the row wrapper.
107 const inputWrapper = thus.createWrapper( optionData, menuValues );
108 sectionWrapper.append( inputWrapper );
109
110 // Create the label.
111 const label = thus.createElement( 'label', {
112 for: `edit-menu-item-${ optionName }-${ itemId }`,
113 } );
114 label.innerText = ` ${ optionData.label } `;
115 inputWrapper.append( label );
116
117 // Create the input.
118 if ( optionData.choices ) {
119 const input = thus.createSelectInput(
120 optionName,
121 itemId,
122 optionValue,
123 optionData.choices
124 );
125 label.append( input );
126 } else {
127 const input = thus.createCheckboxInput( optionName, itemId, optionValue );
128 label.prepend( input );
129 }
130 }
131 },
132
133 /**
134 * Creates and returns a `<input type="hidden"/>` element.
135 *
136 * @param {string} id An identifier for this input. It will be part of the final `id` attribute.
137 * @param {number} itemId The ID of the menu element (post ID).
138 * @param {string|number} value The input's value.
139 * @return {HTMLElement} The input element.
140 */
141 createHiddenInput: ( id, itemId, value ) => {
142 return pllNavMenu.printMetabox.createInput( id, itemId, value, 'hidden' );
143 },
144
145 /**
146 * Creates and returns a wrapper row.
147 *
148 * @param {Object} optionData A field definition.
149 * @param {Object} {optionData.hide_if} Optional. "Hide row" conditions.
150 * @param {Object} menuValues The input values.
151 * @return {HTMLElement} The input element, wrapped in a `<p>` tag.
152 */
153 createWrapper: ( optionData, menuValues ) => {
154 const wrapperAtts = { class: 'description' };
155
156 if ( ! optionData.hide_if ) {
157 return pllNavMenu.printMetabox.createElement( 'p', wrapperAtts );
158 }
159
160 for ( const [ conditionName, conditionValue ] of Object.entries( optionData.hide_if ) ) {
161 // 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.
162 if ( typeof pll_data.data[ conditionName ] === 'undefined' ) {
163 // Should not happen.
164 continue;
165 }
166
167 const menuValue =
168 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.
169 ? 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.
170 : 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.
171 wrapperAtts.class += ` pll-hidden-if-${ conditionName }-${ conditionValue }`;
172
173 if ( conditionValue === menuValue ) {
174 wrapperAtts.class += ` pll-hidden-by-${ conditionName }`;
175 }
176 }
177
178 return pllNavMenu.printMetabox.createElement( 'p', wrapperAtts );
179 },
180
181 /**
182 * Creates and returns a `<input type="checkbox"/>` element.
183 *
184 * @param {string} id An identifier for this input. It will be part of the final `id` attribute.
185 * @param {number} itemId The ID of the menu element (post ID).
186 * @param {boolean|number} value The option value.
187 * @return {HTMLElement} The input element.
188 */
189 createCheckboxInput: ( id, itemId, value ) => {
190 const input = pllNavMenu.printMetabox.createInput( id, itemId, 1, 'checkbox' );
191 input.setAttribute( 'data-key', id );
192 if ( value ) {
193 input.checked = true;
194 }
195 return input;
196 },
197
198 /**
199 * Creates and returns a `<select/>` element.
200 *
201 * @param {string} id An identifier for this input. It will be part of the final `id` attribute.
202 * @param {number} itemId The ID of the menu element (post ID).
203 * @param {string|number} value The option value.
204 * @param {Object} choices Choices.
205 * @return {HTMLElement} The input element.
206 */
207 createSelectInput: ( id, itemId, value, choices ) => {
208 const input = pllNavMenu.printMetabox.createElement( 'select', {
209 id: `edit-menu-item-${ id }-${ itemId }`,
210 name: `menu-item-${ id }[${ itemId }]`,
211 'data-key': id,
212 } );
213 for ( const [ optionValue, optionLabel ] of Object.entries( choices ) ) {
214 const atts = { value: optionValue };
215 if ( value === optionValue ) {
216 atts.selected = 'selected';
217 }
218 const option = pllNavMenu.printMetabox.createElement( 'option', atts );
219 option.innerText = optionLabel;
220 input.append( option );
221 }
222 return input;
223 },
224
225 /**
226 * Creates and returns a `<input/>` element.
227 *
228 * @param {string} id An identifier for this input. It will be part of the final `id` attribute.
229 * @param {number} itemId The ID of the menu element (post ID).
230 * @param {string|number} value The input's value.
231 * @param {string} type The type of input.
232 * @return {HTMLElement} The input element.
233 */
234 createInput: ( id, itemId, value, type ) => {
235 return pllNavMenu.printMetabox.createElement( 'input', {
236 type,
237 id: `edit-menu-item-${ id }-${ itemId }`,
238 name: `menu-item-${ id }[${ itemId }]`,
239 value,
240 } );
241 },
242
243 /**
244 * Creates and returns an element.
245 *
246 * @param {string} type Element's type.
247 * @param {Object} {atts} Optional. Element's attributes.
248 * @return {HTMLElement} The element.
249 */
250 createElement: ( type, atts = {} ) => {
251 const el = document.createElement( type );
252 for ( const [ key, value ] of Object.entries( atts ) ) {
253 el.setAttribute( key, value );
254 }
255 return el;
256 },
257 },
258
259 manageRowsAndValues: {
260 /**
261 * Event callback that hides rows and forbids disabling both flag and name/code.
262 *
263 * @param {Event} event The event.
264 */
265 handleEvent: event => {
266 let value = '';
267
268 if ( 'SELECT' === event.target.nodeName ) {
269 value = event.target.value;
270 } else if ( 'INPUT' === event.target.nodeName && 'checkbox' === event.target.type ) {
271 value = event.target.checked;
272 } else {
273 return;
274 }
275
276 const wrapper = event.target.closest( '.polylang-language-switcher-menu-content' );
277
278 if ( ! wrapper ) {
279 return;
280 }
281
282 // Hide rows.
283 const key = event.target.getAttribute( 'data-key' );
284
285 if ( ! key ) {
286 return;
287 }
288
289 const valueSelector = typeof value === 'string' ? value.replaceAll( ':', '\\:' ) : value; // For flag format.
290 wrapper
291 .querySelectorAll(
292 `[class*="pll-hidden-if-${ key }-"]:not(.pll-hidden-if-${ key }-${ valueSelector })`
293 )
294 .forEach( input => {
295 input.classList.remove( `pll-hidden-by-${ key }` );
296 } );
297 wrapper.querySelectorAll( `.pll-hidden-if-${ key }-${ valueSelector }` ).forEach( input => {
298 input.classList.add( `pll-hidden-by-${ key }` );
299 } );
300
301 // Forbid disabling both flag and name/code.
302 if ( 'show_labels' === key && '' === value ) {
303 const otherInput = wrapper.querySelector( '[data-key="show_flags"]' );
304
305 if ( otherInput && true !== otherInput.checked ) {
306 otherInput.checked = true;
307 otherInput.dispatchEvent( new Event( 'change', { bubbles: true } ) );
308 }
309 } else if ( 'show_flags' === key && false === value ) {
310 const otherInput = wrapper.querySelector( '[data-key="show_labels"]' );
311
312 if ( otherInput && '' === otherInput.value ) {
313 otherInput.value = 'names';
314 otherInput.dispatchEvent( new Event( 'change', { bubbles: true } ) );
315 }
316 }
317 },
318 },
319 };
320
321 pllNavMenu.init();
322
323