PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.16.1
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.16.1
6.35 6.34 6.33.1 6.33 6.32.1 6.32 6.31 6.25 6.25.1 6.26 6.26.1 6.27 6.28 6.29 6.3 6.3.1 6.3.2 6.30 6.4 6.4.1 6.4.2 6.5 6.5.1 6.5.2 6.5.3 All 141 releases
← All changes | js/admin/style.js +269 -421 6.336.16.1 View file →
@@ -2,8 +2,9 @@
2 2 * This script file handles style settings in the Lite plugin.
3 3 * Pro-specific features are in the style-settings.js file in Pro.
4 4 */
5 5 ( function() {
6 + /* globals wp, frmDom, frmAdminBuild */
6 7 'use strict';
7 8
8 9 if ( ! document.getElementById( 'frm_active_style_form' ) ) {
9 10 return;
@@ -8,10 +9,10 @@
8 9 if ( ! document.getElementById( 'frm_active_style_form' ) ) {
9 10 return;
10 11 }
11 12
12 - const { __, sprintf } = wp.i18n;
13 - const state = {
13 + const { __, sprintf } = wp.i18n;
14 + const state = {
14 15 showingSampleForm: document.getElementById( 'frm_active_style_form' ).classList.contains( 'frm_hidden' ), // boolean
15 16 unsavedChanges: false, // boolean
16 17 autoId: 0, // Number
17 18 // Track the value of the selected style ID on page (on the list page).
@@ -19,11 +20,11 @@
19 20 // This way when you switch back to the initial value it doesn't count as a change.
20 21 initialSelectedStyleValue: false // String|false
21 22 };
22 23 const { div, span, a, labelledTextInput, tag, svg, success } = frmDom;
23 - const { onClickPreventDefault } = frmDom.util;
24 - const { maybeCreateModal, footerButton } = frmDom.modal;
25 - const { doJsonPost } = frmDom.ajax;
24 + const { onClickPreventDefault } = frmDom.util;
25 + const { maybeCreateModal, footerButton } = frmDom.modal;
26 + const { doJsonPost } = frmDom.ajax;
26 27
27 28 const isListPage = document.getElementsByClassName( 'frm-style-card' ).length > 0;
28 29 if ( isListPage ) {
29 30 initListPage();
@@ -36,9 +37,9 @@
36 37 /**
37 38 * These are shared events for both the edit/list views like the sample form toggle.
38 39 * This includes preview events, but also the update button click event handling for both views.
39 40 *
40 - * @return {void}
41 + * @returns {void}
41 42 */
42 43 function initCommonEventListeners() {
43 44 document.addEventListener( 'click', handleCommonClickEvents );
44 45 window.addEventListener( 'beforeunload', maybeConfirmExit );
@@ -47,9 +48,9 @@
47 48
48 49 /**
49 50 * Initialize common functions required for the preview in both the edit and list views.
50 51 *
51 - * @return {void}
52 + * @returns {void}
52 53 */
53 54 function initPreview() {
54 55 initFloatingLabels();
55 56 fillMissingSignatureValidationFunction();
@@ -61,103 +62,15 @@
61 62 document.getElementById( 'frm_style_sidebar' ).classList.add( 'wp-core-ui' );
62 63
63 64 jQuery( document ).on( 'input change', 'input[data-frmrange]', initSliderPreview );
64 65
65 - initOptionLayoutPreview();
66 66 }
67 67
68 68 /**
69 - * Sync the radio/checkbox option layout (alignment) style settings to the preview form in real time.
70 - *
71 - * The layout is applied to the front end through a container class, so changing the style setting
72 - * does not re-render the preview form. This swaps the container class on preview fields that use the
73 - * style setting. The styler preview marks those fields with .frm-default-option-align, so fields with
74 - * their own alignment override are left untouched.
75 - *
76 - * @return {void}
77 - */
78 - function initOptionLayoutPreview() {
79 - bindOptionLayoutSelect( document.getElementById( 'frm_radio_align' ), '.frm_radio' );
80 - bindOptionLayoutSelect( document.getElementById( 'frm_check_align' ), '.frm_checkbox' );
81 - }
82 -
83 - /**
84 - * Listen for changes on an option layout style setting and update the preview to match.
85 - *
86 - * @param {HTMLSelectElement|null} select The style setting dropdown.
87 - * @param {string} optionSelector The single option selector ('.frm_radio' or '.frm_checkbox').
88 - * @return {void}
89 - */
90 - function bindOptionLayoutSelect( select, optionSelector ) {
91 - if ( ! select ) {
92 - return;
93 - }
94 -
95 - select.addEventListener( 'change', () => {
96 - updatePreviewOptionLayout( optionSelector, select.value );
97 - } );
98 - }
99 -
100 - /**
101 - * Replace the alignment container class on preview fields that are using the style setting.
102 - *
103 - * @param {string} optionSelector The single option selector ('.frm_radio' or '.frm_checkbox').
104 - * @param {string} newAlign The newly selected style alignment value.
105 - * @return {void}
106 - */
107 - function updatePreviewOptionLayout( optionSelector, newAlign ) {
108 - const newClass = optionLayoutAlignToClass( newAlign );
109 -
110 - if ( ! newClass ) {
111 - return;
112 - }
113 -
114 - const activeForm = document.getElementById( 'frm_active_style_form' );
115 - if ( ! activeForm ) {
116 - return;
117 - }
118 -
119 - const alignClasses = [ 'vertical_radio', 'horizontal_radio', 'frm_two_col', 'frm_three_col', 'frm_four_col' ];
120 - const containers = new Set();
121 -
122 - activeForm.querySelectorAll( optionSelector ).forEach( option => {
123 - const container = option.closest( '.frm_form_field' );
124 -
125 - // Only fields using the style setting (no override) are marked in the styler preview.
126 - if ( container?.classList.contains( 'frm-default-option-align' ) ) {
127 - containers.add( container );
128 - }
129 - } );
130 -
131 - containers.forEach( container => {
132 - container.classList.remove( ...alignClasses );
133 - container.classList.add( newClass );
134 - } );
135 - }
136 -
137 - /**
138 - * Map an option layout style value to its front-end container class.
139 - *
140 - * @param {string} align The style alignment value.
141 - * @return {string} The matching container class.
142 - */
143 - function optionLayoutAlignToClass( align ) {
144 - if ( 'inline' === align ) {
145 - return 'horizontal_radio';
146 - }
147 -
148 - if ( 'block' === align ) {
149 - return 'vertical_radio';
150 - }
151 -
152 - return align;
153 - }
154 -
155 - /**
156 69 * Initialize the slider functionality in the style preview.
157 70 *
158 71 * @param {HTMLElement} event
159 - * @return {void}
72 + * @returns {void}
160 73 */
161 74 function initSliderPreview( event ) {
162 75 const wrapper = event.target.closest( '.frm_range_container' );
163 76 if ( null === wrapper ) {
@@ -169,9 +82,9 @@
169 82 /**
170 83 * Add the wp-core-ui class to the #wp-auth-check-wrap element.
171 84 * As this style isn't included on the body for the styler, the close button on the auth modal wasn't getting styled properly.
172 85 *
173 - * @return {void}
86 + * @returns {void}
174 87 */
175 88 function fixWpAuthModal() {
176 89 const authWrap = document.getElementById( 'wp-auth-check-wrap' );
177 90 if ( authWrap ) {
@@ -179,9 +92,9 @@
179 92 }
180 93 }
181 94
182 95 /**
183 - * @return {void}
96 + * @returns {void}
184 97 */
185 98 function initListPage() {
186 99 document.addEventListener( 'click', handleClickEventsForListPage );
187 100 // Add a timeout so Pro has a chance to add a filter first.
@@ -188,10 +101,10 @@
188 101 // 0 does not always work in Google Chrome, so use 1.
189 102 setTimeout( addHamburgerMenusToCards, 1 );
190 103 initDatepickerSample();
191 104
192 - const enableToggle = document.getElementById( 'frm_enable_styling' );
193 - const styleIdInput = getStyleIdInput();
105 + const enableToggle = document.getElementById( 'frm_enable_styling' );
106 + const styleIdInput = getStyleIdInput();
194 107 state.initialSelectedStyleValue = styleIdInput.value;
195 108
196 109 enableToggle.addEventListener( 'change', handleEnableStylingToggleChange );
197 110
@@ -212,16 +125,16 @@
212 125
213 126 /**
214 127 * Handle pagination click events.
215 128 *
216 - * @return {void}
129 + * @returns {void}
217 130 */
218 131 function initStyleCardPagination() {
219 132 document.querySelectorAll( '.frm-style-card-pagination' ).forEach(
220 133 pagination => {
221 - const wrapper = pagination.closest( '.frm-style-card-wrapper' );
134 + const wrapper = pagination.closest( '.frm-style-card-wrapper' );
222 135 const showAllAnchor = pagination.querySelector( '.frm-show-all-styles' );
223 - let showingAll = false;
136 + let showingAll = false;
224 137
225 138 onClickPreventDefault(
226 139 showAllAnchor,
227 140 () => {
@@ -237,9 +150,9 @@
237 150
238 151 wrapper.querySelectorAll( '.frm-style-card:nth-child(3) ~ .frm-style-card' ).forEach(
239 152 card => card.classList.add( 'frm_hidden' )
240 153 );
241 - const hiddenCount = wrapper.querySelectorAll( '.frm-style-card.frm_hidden' ).length;
154 + const hiddenCount = wrapper.querySelectorAll( '.frm-style-card.frm_hidden' ).length;
242 155 /* translators: %d: The number of hidden items to show. */
243 156 showAllAnchor.textContent = sprintf( __( 'Show all (%d)', 'formidable' ), hiddenCount );
244 157 }
245 158 );
@@ -247,10 +160,10 @@
247 160 );
248 161 }
249 162
250 163 /**
251 - * @param {string} labelPosition
252 - * @return {void} Changes the label position in the preview.
164 + * @param {String} labelPosition
165 + * @returns {void}
253 166 */
254 167 function changeLabelPositionsInPreview( labelPosition ) {
255 168 const input = tag( 'input' );
256 169 input.value = labelPosition;
@@ -257,9 +170,9 @@
257 170 setPosClass.bind( input )();
258 171 }
259 172
260 173 /**
261 - * @return {HTMLElement} The active style card.
174 + * @returns {HTMLElement}
262 175 */
263 176 function getActiveCard() {
264 177 return document.querySelector( '.frm-active-style-card' );
265 178 }
@@ -269,9 +182,9 @@
269 182 * The style ID value associated with the selected style card gets cleared.
270 183 * This is because disabling styles is linked to the custom_style option as well.
271 184 *
272 185 * @param {Event} event
273 - * @return {void}
186 + * @returns {void}
274 187 */
275 188 function handleEnableStylingToggleChange( event ) {
276 189 const stylesEnabled = event.target.checked;
277 190
@@ -303,21 +216,21 @@
303 216 * All settings on the list page are mapped to the value of the styleIdInput.
304 217 * We track the value on load with state.initialSelectedStyleValue.
305 218 * Only consider unsaved changes on the page when this variable is no longer set to the original value.
306 219 *
307 - * @return {void}
220 + * @returns {void}
308 221 */
309 222 function trackListPageChange() {
310 - const styleIdInput = getStyleIdInput();
223 + const styleIdInput = getStyleIdInput();
311 224 state.unsavedChanges = styleIdInput.value !== state.initialSelectedStyleValue;
312 225 }
313 226
314 227 /**
315 228 * @param {boolean} on
316 - * @return {void}
229 + * @returns {void}
317 230 */
318 231 function toggleFormidableStylingInPreviewForms( on ) {
319 - const preview = document.getElementById( 'frm_style_preview' );
232 + const preview = document.getElementById( 'frm_style_preview' );
320 233 const activeCard = getActiveCard();
321 234
322 235 let selector = '.frm_forms';
323 236 if ( ! on ) {
@@ -332,9 +245,9 @@
332 245 );
333 246 }
334 247
335 248 /**
336 - * @return {HTMLElement} The style ID input element.
249 + * @returns {HTMLElement}
337 250 */
338 251 function getStyleIdInput() {
339 252 return document.getElementById( 'frm_style_list_form' ).querySelector( '[name="style_id"]' );
340 253 }
@@ -340,12 +253,12 @@
340 253 }
341 254
342 255 /**
343 256 * @param {Event} event
344 - * @return {void}
257 + * @returns {void}
345 258 */
346 259 function handleCommonClickEvents( event ) {
347 - const { target } = event;
260 + const target = event.target;
348 261
349 262 if ( 'frm_toggle_sample_form' === target.id || target.closest( '#frm_toggle_sample_form' ) ) {
350 263 toggleSampleForm();
351 264 return;
@@ -358,20 +271,20 @@
358 271 }
359 272
360 273 if ( target.classList.contains( 'frm-edit-style' ) || null !== target.closest( '.frm-edit-style' ) || 'frm_edit_style' === target.id ) {
361 274 modifyStylerUrl( target );
275 + return;
362 276 }
363 277 }
364 278
365 279 /**
366 280 * This function is used to update the form action when switching from the advanced settings and quick-settings.
367 - *
368 281 * @param {Object} target The submit button event target
369 282 * @return {void}
370 283 */
371 284 function switchAdvancedSettingsFormAction( target ) {
372 285 const form = document.querySelector( '#frm_styling_form' );
373 - if ( ! form ) {
286 + if ( null === form ) {
374 287 return;
375 288 }
376 289 if ( target.closest( 'a#frm_style_back_to_quick_settings' ) ) {
377 290 form.action = form.action.replace( '&section=advanced-settings', '' );
@@ -382,9 +295,9 @@
382 295 }
383 296 }
384 297
385 298 /**
386 - * @return {void}
299 + * @returns {void}
387 300 */
388 301 function disablePreviewSubmitButtons() {
389 302 const preview = document.getElementById( 'frm_style_preview' );
390 303 preview.querySelectorAll( 'form' ).forEach(
@@ -393,9 +306,9 @@
393 306 /**
394 307 * Prevent form submit event.
395 308 *
396 309 * @param {Event} event
397 - * @return {false} Prevents the default action and stops the event from bubbling.
310 + * @returns {false}
398 311 */
399 312 event => {
400 313 event.preventDefault();
401 314 event.stopPropagation();
@@ -406,15 +319,16 @@
406 319 }
407 320
408 321 /**
409 322 * @param {Event} event
410 - * @return {void}
323 + * @returns {void}
411 324 */
412 325 function handleClickEventsForListPage( event ) {
413 - const { target } = event;
326 + const target = event.target;
414 327
415 328 if ( target.classList.contains( 'frm-style-card' ) || target.closest( '.frm-style-card' ) ) {
416 329 handleStyleCardClick( event );
330 + return;
417 331 }
418 332 }
419 333
420 334 /**
@@ -421,12 +335,12 @@
421 335 * When a style card is clicked, the preview is updated.
422 336 * If the Update button is clicked after selecting a style card, the active card will be saved as the target form's style.
423 337 *
424 338 * @param {Event} event
425 - * @return {void}
339 + * @returns {void}
426 340 */
427 341 function handleStyleCardClick( event ) {
428 - const { target } = event;
342 + const target = event.target;
429 343
430 344 if ( target.closest( '.dropdown' ) ) {
431 345 // Ignore the hamburger menu inside of the card.
432 346 return;
@@ -431,9 +345,9 @@
431 345 // Ignore the hamburger menu inside of the card.
432 346 return;
433 347 }
434 348
435 - const card = target.classList.contains( 'frm-style-card' ) ? target : target.closest( '.frm-style-card' );
349 + const card = target.classList.contains( 'frm-style-card' ) ? target : target.closest( '.frm-style-card' );
436 350 const cardIsLocked = card.classList.contains( 'frm-locked-style' );
437 351
438 352 if ( cardIsLocked ) {
439 353 maybeCreateStyleTemplateModal( card );
@@ -439,11 +353,11 @@
439 353 maybeCreateStyleTemplateModal( card );
440 354 return; // Exit early as we're not actually selecting a locked template for preview.
441 355 }
442 356
443 - const previewArea = document.getElementById( 'frm_style_preview' );
444 - const activeCard = document.querySelector( '.frm-active-style-card' );
445 - const sampleForm = document.getElementById( 'frm_sample_form' ).querySelector( '.frm_forms' );
357 + const previewArea = document.getElementById( 'frm_style_preview' );
358 + const activeCard = document.querySelector( '.frm-active-style-card' );
359 + const sampleForm = document.getElementById( 'frm_sample_form' ).querySelector( '.frm_forms' );
446 360 const styleIdInput = getStyleIdInput();
447 361
448 362 disableLabelTransitions();
449 363
@@ -459,16 +373,18 @@
459 373
460 374 sampleForm.classList.remove( activeCard.dataset.classname );
461 375 sampleForm.classList.add( card.dataset.classname );
462 376
463 - // cardIsLocked is always false here due to early return above.
464 - styleIdInput.value = card.dataset.styleId;
465 - trackListPageChange();
377 + if ( ! cardIsLocked ) {
378 + // Don't update the form when a locked card is clicked.
379 + styleIdInput.value = card.dataset.styleId;
380 + trackListPageChange();
381 + }
466 382
467 383 setTimeout( enableLabelTransitions, 1 );
468 384
469 385 // We want to toggle the edit button so you can only leave the page to edit the style if it's active (to avoid unsaved changes).
470 - const editButton = document.getElementById( 'frm_edit_style' );
386 + const editButton = document.getElementById( 'frm_edit_style' );
471 387 const showEditButton = null !== card.querySelector( '.frm-style-card-info' ); // Only the "Applied style" has card info.
472 388 editButton.classList.toggle( 'frm_hidden', ! showEditButton );
473 389
474 390 changeLabelPositionsInPreview( card.dataset.labelPosition );
@@ -473,21 +389,21 @@
473 389
474 390 changeLabelPositionsInPreview( card.dataset.labelPosition );
475 391
476 392 // Trigger an action here so Pro can handle template preview updates on card click.
477 - const hookName = 'frm_style_card_click';
478 - const hookArgs = { card, styleIdInput };
393 + const hookName = 'frm_style_card_click';
394 + const hookArgs = { card, styleIdInput };
479 395 wp.hooks.doAction( hookName, hookArgs );
480 396 }
481 397
482 398 /**
483 399 * @param {HTMLElement} card
484 - * @return {HTMLElement} The modal element.
400 + * @returns {HTMLElement}
485 401 */
486 402 function maybeCreateStyleTemplateModal( card ) {
487 - const titleElement = card.querySelector( '.frm-style-card-title' );
403 + const titleElement = card.querySelector( '.frm-style-card-title' );
488 404 const templateTitle = titleElement.textContent;
489 - const modal = maybeCreateModal(
405 + const modal = maybeCreateModal(
490 406 'frm_style_template_modal',
491 407 {
492 408 content: getStyleTemplateModalContent( card ),
493 409 footer: getStyleTemplateModalFooter( card )
@@ -498,15 +414,15 @@
498 414 }
499 415
500 416 /**
501 417 * @param {HTMLElement} card
502 - * @return {HTMLElement} The modal content element.
418 + * @returns {HTMLElement}
503 419 */
504 420 function getStyleTemplateModalContent( card ) {
505 421 const children = [];
506 422
507 423 children.push(
508 - div( {
424 + div({
509 425 className: 'frm_warning_style',
510 426 children: [
511 427 span(
512 428 /* translators: %s: The required license type (ie. Plus, Business, or Elite) */
@@ -511,48 +427,48 @@
511 427 span(
512 428 /* translators: %s: The required license type (ie. Plus, Business, or Elite) */
513 429 sprintf( __( 'Access to this style requires the %s plan.', 'formidable' ), card.dataset.requires )
514 430 ),
515 - a( {
431 + a({
516 432 text: getUpgradeNowText(),
517 433 href: card.dataset.upgradeUrl,
518 434 target: '_blank'
519 - } )
435 + })
520 436 ]
521 - } )
437 + })
522 438 );
523 439
524 - return div( { children } );
440 + return div({ children });
525 441 }
526 442
527 443 /**
528 444 * @param {HTMLElement} card
529 - * @return {HTMLElement} The modal footer element.
445 + * @returns {HTMLElement}
530 446 */
531 447 function getStyleTemplateModalFooter( card ) {
532 - const viewDemoSiteButton = footerButton( {
448 + const viewDemoSiteButton = footerButton({
533 449 text: __( 'Learn More', 'formidable' ),
534 450 buttonType: 'secondary'
535 - } );
451 + });
536 452 viewDemoSiteButton.href = card.dataset.upgradeUrl;
537 453 viewDemoSiteButton.target = '_blank';
538 454
539 - const primaryActionButton = footerButton( {
455 + let primaryActionButton = footerButton({
540 456 text: getUpgradeNowText(),
541 457 buttonType: 'primary'
542 - } );
458 + });
543 459
544 460 primaryActionButton.classList.remove( 'dismiss' );
545 461 primaryActionButton.setAttribute( 'href', card.dataset.upgradeUrl );
546 462 primaryActionButton.target = '_blank';
547 463
548 - return div( {
464 + return div({
549 465 children: [ viewDemoSiteButton, primaryActionButton ]
550 - } );
466 + });
551 467 }
552 468
553 469 /**
554 - * @return {string} The upgrade now text.
470 + * @returns {String}
555 471 */
556 472 function getUpgradeNowText() {
557 473 return __( 'Upgrade Now', 'formidable' );
558 474 }
@@ -560,9 +476,9 @@
560 476 /**
561 477 * Track an unsaved change on the edit page.
562 478 * This is included in the frmStylerFunctions global so unsaved changes can be tracked in Pro as well.
563 479 *
564 - * @return {void}
480 + * @returns {void}
565 481 */
566 482 function trackUnsavedChange() {
567 483 state.unsavedChanges = true;
568 484 }
@@ -570,9 +486,9 @@
570 486 /**
571 487 * Possibly prevent leaving the page if there are unsaved changes.
572 488 *
573 489 * @param {Event} event
574 - * @return {void}
490 + * @returns {void}
575 491 */
576 492 function maybeConfirmExit( event ) {
577 493 if ( ! state.unsavedChanges ) {
578 494 return;
@@ -584,9 +500,9 @@
584 500
585 501 /**
586 502 * Floating labels have a transition style. Turn it off temporarily when switching between cards to avoid a transition between two different style classes.
587 503 *
588 - * @return {void}
504 + * @returns {void}
589 505 */
590 506 function disableLabelTransitions() {
591 507 setLabelTransitionStyle( 'none' );
592 508 }
@@ -591,9 +507,9 @@
591 507 setLabelTransitionStyle( 'none' );
592 508 }
593 509
594 510 /**
595 - * @return {void}
511 + * @returns {void}
596 512 */
597 513 function enableLabelTransitions() {
598 514 setLabelTransitionStyle( '' );
599 515 }
@@ -598,10 +514,10 @@
598 514 setLabelTransitionStyle( '' );
599 515 }
600 516
601 517 /**
602 - * @param {string} value
603 - * @return {void}
518 + * @param {String} value
519 + * @returns {void}
604 520 */
605 521 function setLabelTransitionStyle( value ) {
606 522 document.getElementById( 'frm_style_preview' ).querySelectorAll( '.frm_inside_container' ).forEach(
607 523 container => container.querySelector( 'label' ).style.transition = value
@@ -608,9 +524,9 @@
608 524 );
609 525 }
610 526
611 527 /**
612 - * @return {void}
528 + * @returns {void}
613 529 */
614 530 function toggleSampleForm() {
615 531 state.showingSampleForm = ! state.showingSampleForm;
616 532 document.getElementById( 'frm_active_style_form' ).classList.toggle( 'frm_hidden', state.showingSampleForm );
@@ -617,9 +533,9 @@
617 533 document.getElementById( 'frm_toggle_sample_form' ).querySelector( 'span' ).textContent = state.showingSampleForm ? __( 'View my form', 'formidable' ) : __( 'View sample form', 'formidable' );
618 534 }
619 535
620 536 /**
621 - * @return {void}
537 + * @returns {void}
622 538 */
623 539 function handleUpdateClick() {
624 540 state.unsavedChanges = false; // Prevent the saved changes pop up from triggering when submitting the form.
625 541
@@ -640,9 +556,9 @@
640 556 * Maybe modify an anchor's URL on click.
641 557 * If the sample form toggle is active, we want to pass that as a query parameter so we know to default to the sample form on load.
642 558 *
643 559 * @param {HTMLElement} clickTarget
644 - * @return {void}
560 + * @returns {void}
645 561 */
646 562 function modifyStylerUrl( clickTarget ) {
647 563 if ( ! state.showingSampleForm ) {
648 564 // Don't change the URL if it is not a sample form.
@@ -649,15 +565,15 @@
649 565 return;
650 566 }
651 567
652 568 const anchor = clickTarget.hasAttribute( 'href' ) ? clickTarget : clickTarget.querySelector( 'a[href]' );
653 - anchor.setAttribute( 'href', `${ anchor.getAttribute( 'href' ) }&sample=1` );
569 + anchor.setAttribute( 'href', anchor.getAttribute( 'href' ) + '&sample=1' );
654 570 }
655 571
656 572 /**
657 573 * Add menu dropdowns to style cards dynamically on load.
658 574 *
659 - * @return {void}
575 + * @returns {void}
660 576 */
661 577 function addHamburgerMenusToCards() {
662 578 const cards = Array.from( document.getElementsByClassName( 'frm-style-card' ) );
663 579 cards.forEach( card => maybeAddMenuToCard( card ) );
@@ -663,10 +579,10 @@
663 579 cards.forEach( card => maybeAddMenuToCard( card ) );
664 580 }
665 581
666 582 /**
667 - * @param {HTMLElement} card
668 - * @return {void}
583 + * @param {HTMLlement} card
584 + * @returns {void}
669 585 */
670 586 function maybeAddMenuToCard( card ) {
671 587 if ( ! shouldAddMenuToCard( card ) ) {
672 588 return;
@@ -671,16 +587,16 @@
671 587 if ( ! shouldAddMenuToCard( card ) ) {
672 588 return;
673 589 }
674 590
675 - card.append( getHamburgerMenu( card.dataset ) );
591 + card.appendChild( getHamburgerMenu( card.dataset ) );
676 592 }
677 593
678 594 /**
679 595 * Avoid adding a menu to an upsell card or a template card.
680 596 *
681 - * @param {HTMLElement} card
682 - * @return {boolean} Whether to add a menu to the card.
597 + * @param {HTMLlement} card
598 + * @returns {boolean}
683 599 */
684 600 function shouldAddMenuToCard( card ) {
685 601 return 'frm_template_style_cards_wrapper' !== card.parentNode.id || ! card.classList.contains( 'frm-locked-style' );
686 602 }
@@ -685,9 +601,9 @@
685 601 return 'frm_template_style_cards_wrapper' !== card.parentNode.id || ! card.classList.contains( 'frm-locked-style' );
686 602 }
687 603
688 604 /**
689 - * @return {void}
605 + * @returns {void}
690 606 */
691 607 function addHamburgerMenuForEditPage() {
692 608 const styleName = document.getElementById( 'frm_style_name' );
693 609 if ( ! styleName ) {
@@ -695,9 +611,9 @@
695 611 }
696 612
697 613 const styleId = document.getElementById( 'frm_styling_form' ).querySelector( 'input[name="ID"]' ).value;
698 614
699 - const hamburgerMenu = getHamburgerMenu( { styleId } );
615 + const hamburgerMenu = getHamburgerMenu({ styleId });
700 616 hamburgerMenu.classList.add( 'alignright' );
701 617 styleName.parentNode.insertBefore( hamburgerMenu, styleName );
702 618 }
703 619
@@ -704,34 +620,34 @@
704 620 /**
705 621 * Get a dropdown and the "hamburger" stacked dot menu trigger for a single style card.
706 622 *
707 623 * @param {DOMStringMap} data {
708 - * @type {string} editUrl
709 - * @type {string} styleId
710 - * @type {string} labelPosition
711 - * @type {string} classname
624 + * @type {String} editUrl
625 + * @type {String} styleId
626 + * @type {String} labelPosition
627 + * @type {String} classname
712 628 * }
713 - * @return {HTMLElement} The hamburger menu element.
629 + * @returns {HTMLElement}
714 630 */
715 631 function getHamburgerMenu( data ) {
716 - const hamburgerMenu = a( {
632 + const hamburgerMenu = a({
717 633 className: 'frm-dropdown-toggle dropdown-toggle',
718 - child: svg( { href: '#frm_thick_more_vert_icon' } )
719 - } );
720 - hamburgerMenu.setAttribute( 'data-bs-toggle', 'dropdown' );
721 - hamburgerMenu.setAttribute( 'data-bs-container', 'body' );
634 + child: svg({ href: '#frm_thick_more_vert_icon' })
635 + });
636 + hamburgerMenu.setAttribute( 'data-toggle', 'dropdown' );
637 + hamburgerMenu.setAttribute( 'data-container', 'body' );
722 638 hamburgerMenu.setAttribute( 'role', 'button' );
723 639 hamburgerMenu.setAttribute( 'tabindex', 0 );
724 640
725 - const isTemplate = data.templateKey !== undefined;
641 + const isTemplate = 'undefined' !== typeof data.templateKey;
726 642 let dropdownMenuOptions = [];
727 643
728 644 if ( isListPage ) {
729 - const applyOption = a( {
645 + const applyOption = a({
730 646 text: isTemplate ? __( 'Install and apply', 'formidable' ) : __( 'Apply', 'formidable' )
731 - } );
647 + });
732 648 addIconToOption( applyOption, 'frm_save_icon' );
733 - dropdownMenuOptions.push( { anchor: applyOption, type: 'apply' } );
649 + dropdownMenuOptions.push({ anchor: applyOption, type: 'apply' });
734 650 onClickPreventDefault( applyOption, handleApplyOptionClick );
735 651 }
736 652
737 653 if ( ! isTemplate ) {
@@ -736,19 +652,19 @@
736 652
737 653 if ( ! isTemplate ) {
738 654 if ( 'string' === typeof data.editUrl ) {
739 655 // The Edit option is not included on the Edit page.
740 - const editOption = a( {
656 + const editOption = a({
741 657 text: __( 'Edit', 'formidable' ),
742 658 href: data.editUrl
743 - } );
659 + });
744 660 addIconToOption( editOption, 'frm_pencil_icon' );
745 - dropdownMenuOptions.push( { anchor: editOption, type: 'edit' } );
661 + dropdownMenuOptions.push({ anchor: editOption, type: 'edit' });
746 662 }
747 663
748 - const resetOption = a( {
664 + const resetOption = a({
749 665 text: __( 'Reset to Defaults', 'formidable' )
750 - } );
666 + });
751 667 addIconToOption( resetOption, 'frm_repeater_icon' );
752 668 onClickPreventDefault( resetOption, () => confirmResetStyle( data.styleId ) );
753 669
754 670 dropdownMenuOptions.push(
@@ -756,10 +672,10 @@
756 672 { anchor: resetOption, type: 'reset' }
757 673 );
758 674 }
759 675
760 - const hookName = 'frm_style_card_dropdown_options';
761 - const hookArgs = { data, addIconToOption, isTemplate };
676 + const hookName = 'frm_style_card_dropdown_options';
677 + const hookArgs = { data, addIconToOption, isTemplate };
762 678 dropdownMenuOptions = wp.hooks.applyFilters( hookName, dropdownMenuOptions, hookArgs );
763 679
764 680 if ( isListPage && ! isTemplate ) {
765 681 maybeAddDuplicateUpsell( dropdownMenuOptions );
@@ -764,33 +680,33 @@
764 680 if ( isListPage && ! isTemplate ) {
765 681 maybeAddDuplicateUpsell( dropdownMenuOptions );
766 682 }
767 683
768 - const dropdownMenu = div( {
684 + const dropdownMenu = div({
769 685 // Use dropdown-menu-right to avoid an overlapping issue with the card to the right (where the # of forms would appear above the menu).
770 686 className: 'frm-dropdown-menu frm-style-options-menu frm-p-1',
771 687 children: dropdownMenuOptions.map( wrapDropdownItem )
772 - } );
688 + });
773 689
774 690 const isRtl = document.body.classList.contains( 'rtl' );
775 - dropdownMenu.classList.add( `dropdown-menu-${ isRtl ? 'left' : 'right' }` );
691 + dropdownMenu.classList.add( 'dropdown-menu-' + ( isRtl ? 'left' : 'right' ) );
776 692
777 693 dropdownMenu.setAttribute( 'role', 'menu' );
778 694
779 - return div( {
695 + return div({
780 696 className: 'dropdown frm_wrap', // The .frm_wrap class prevents a blue outline on the active dropdown trigger.
781 697 children: [ hamburgerMenu, dropdownMenu ]
782 - } );
698 + });
783 699 }
784 700
785 701 /**
786 702 * @param {Array} dropdownMenuOptions
787 - * @return {void}
703 + * @returns {void}
788 704 */
789 705 function maybeAddDuplicateUpsell( dropdownMenuOptions ) {
790 706 let duplicateOptionExists = false;
791 707 for ( let i = 0; i < dropdownMenuOptions.length; ++i ) {
792 - if ( dropdownMenuOptions[ i ].type === 'duplicate' ) {
708 + if ( dropdownMenuOptions[i].type === 'duplicate' ) {
793 709 duplicateOptionExists = true;
794 710 break;
795 711 }
796 712 }
@@ -798,12 +714,12 @@
798 714 if ( duplicateOptionExists ) {
799 715 return;
800 716 }
801 717
802 - const duplicateUpsell = a( {
718 + const duplicateUpsell = a({
803 719 text: __( 'Duplicate', 'formidable' ),
804 720 className: 'frm_noallow'
805 - } );
721 + });
806 722 addIconToOption( duplicateUpsell, 'frm_clone_icon' );
807 723 onClickPreventDefault( duplicateUpsell, () => document.getElementById( 'frm_new_style_trigger' ).click() );
808 724 const upsellOption = { anchor: duplicateUpsell, type: 'duplicate' };
809 725 dropdownMenuOptions.splice( 3, 0, upsellOption );
@@ -810,13 +726,13 @@
810 726 }
811 727
812 728 /**
813 729 * @param {Event} event
814 - * @return {void}
730 + * @returns {void}
815 731 */
816 732 function handleApplyOptionClick( event ) {
817 733 const option = event.target;
818 - const card = option.closest( '.frm-style-card' );
734 + const card = option.closest( '.frm-style-card' );
819 735 if ( ! card ) {
820 736 return;
821 737 }
822 738
@@ -824,14 +740,14 @@
824 740 handleUpdateClick();
825 741 }
826 742
827 743 /**
828 - * @param {string} styleId
829 - * @return {HTMLElement} The rename option element.
744 + * @param {String} styleId
745 + * @returns {HTMLElement}
830 746 */
831 747 function getRenameOption( styleId ) {
832 748 const renameOption = a( __( 'Rename', 'formidable' ) );
833 - addIconToOption( renameOption, 'frm_signature2_icon' );
749 + addIconToOption( renameOption, 'frm_signature_icon' );
834 750
835 751 let titleTarget;
836 752
837 753 // Depending on the page we're pulling the text from an existing element on the page.
@@ -859,11 +775,11 @@
859 775 return renameOption;
860 776 }
861 777
862 778 /**
863 - * @param {string} id
779 + * @param {String} id
864 780 * @param {Object} args
865 - * @return {HTMLElement} The modal element.
781 + * @returns {HTMLElement}
866 782 */
867 783 function stylerModal( id, args ) {
868 784 const modal = maybeCreateModal( id, args );
869 785 // Include both wp-core-ui and frm-white-body on the modal.
@@ -876,11 +792,11 @@
876 792 /**
877 793 * Get modal content with just a "Style Name" input.
878 794 * This is used for New style, Duplicate style, and for Rename style.
879 795 *
880 - * @param {string} context
881 - * @param {string|undefined} value
882 - * @return {HTMLElement} The modal content element.
796 + * @param {String} context
797 + * @param {String|undefined} value
798 + * @returns {HTMLElement}
883 799 */
884 800 function getStyleInputNameModalContent( context, value ) {
885 801 // Create a form so we can listen to Enter key presses that trigger a form submit event.
886 802 const form = tag(
@@ -885,9 +801,9 @@
885 801 // Create a form so we can listen to Enter key presses that trigger a form submit event.
886 802 const form = tag(
887 803 'form',
888 804 {
889 - child: labelledTextInput( `frm_${ context }_style_name_input`, __( 'Style name', 'formidable' ), 'style_name' )
805 + child: labelledTextInput( 'frm_' + context + '_style_name_input', __( 'Style name', 'formidable' ), 'style_name' )
890 806 }
891 807 );
892 808 form.addEventListener(
893 809 'submit',
@@ -892,9 +808,9 @@
892 808 form.addEventListener(
893 809 'submit',
894 810 /**
895 811 * @param {Event} event
896 - * @return {false} Prevents the default action and stops the event from bubbling.
812 + * @returns {false}
897 813 */
898 814 event => {
899 815 // Prevent the form in the modal from submitting and trigger the click button in the modal footer instead.
900 816 event.preventDefault();
@@ -904,9 +820,9 @@
904 820
905 821 return false;
906 822 }
907 823 );
908 - const content = div( { child: form } );
824 + const content = div({ child: form });
909 825 content.style.padding = '20px';
910 826 content.querySelector( 'label' ).style.lineHeight = 1.5;
911 827
912 828 const styleNameInput = content.querySelector( 'input' );
@@ -931,32 +847,32 @@
931 847 return content;
932 848 }
933 849
934 850 /**
935 - * @param {string} styleId
936 - * @return {HTMLElement} The modal footer element.
851 + * @param {String} styleId
852 + * @returns {HTMLELement}
937 853 */
938 - function getRenameStyleModalFooter( styleId ) {
939 - const cancelButton = footerButton( { text: __( 'Cancel', 'formidable' ), buttonType: 'cancel' } );
854 + function getRenameStyleModalFooter( styleId ) {
855 + const cancelButton = footerButton({ text: __( 'Cancel', 'formidable' ), buttonType: 'cancel' });
940 856 cancelButton.classList.add( 'dismiss' );
941 857
942 - const renameButton = footerButton( { text: __( 'Rename style', 'formidable' ), buttonType: 'primary' } );
858 + const renameButton = footerButton({ text: __( 'Rename style', 'formidable' ), buttonType: 'primary' });
943 859 onClickPreventDefault( renameButton, () => renameStyle( styleId ) );
944 860
945 - return div( {
861 + return div({
946 862 children: [ cancelButton, renameButton ]
947 - } );
863 + });
948 864 }
949 865
950 866 /**
951 867 * Call frm_rename_style action when the rename style button is clicked in rename modal.
952 868 *
953 - * @param {string} styleId
954 - * @return {void}
869 + * @param {String} styleId
870 + * @returns {void}
955 871 */
956 872 function renameStyle( styleId ) {
957 873 const styleNameInput = document.getElementById( 'frm_rename_style_name_input' );
958 - const newStyleName = styleNameInput.value;
874 + const newStyleName = styleNameInput.value;
959 875
960 876 if ( '' === newStyleName ) {
961 877 // Avoid setting an empty name.
962 878 // The button gets disabled on an input event when the name is empty.
@@ -962,27 +878,9 @@
962 878 // The button gets disabled on an input event when the name is empty.
963 879 return;
964 880 }
965 881
966 - if ( ! styleId || '0' === String( styleId ) ) {
967 - // A new or duplicated style has no ID yet, so there is nothing to rename on the server.
968 - // Update the post_title input (which overrides $_GET['style_name'] on save) and the
969 - // visible style name instead of calling the rename_style endpoint.
970 - const postTitleInput = document.querySelector( 'input[name="frm_style_setting[post_title]"]' );
971 - if ( postTitleInput ) {
972 - postTitleInput.value = newStyleName;
973 - }
974 -
975 - const styleNameElement = document.getElementById( 'frm_style_name' );
976 - if ( styleNameElement ) {
977 - styleNameElement.textContent = newStyleName;
978 - }
979 -
980 - success( __( 'Style has been renamed successfully', 'formidable' ) );
981 - return;
982 - }
983 -
984 - const formData = new FormData();
882 + const formData = new FormData();
985 883 formData.append( 'style_id', styleId );
986 884 formData.append( 'style_name', newStyleName );
987 885 doJsonPost( 'rename_style', formData ).then(
988 886 /**
@@ -990,9 +888,9 @@
990 888 *
991 889 * If on the list page, update the style card after renaming a style.
992 890 * On the edit page, update the style name element instead.
993 891 *
994 - * @return {void}
892 + * @returns {void}
995 893 */
996 894 () => {
997 895 success( __( 'Style has been renamed successfully', 'formidable' ) );
998 896
@@ -1007,24 +905,33 @@
1007 905 );
1008 906 }
1009 907
1010 908 /**
1011 - * @param {string} styleId
1012 - * @param {string} newStyleName
1013 - * @return {void}
909 + * @param {String} styleId
910 + * @param {String} newStyleName
911 + * @returns {void}
1014 912 */
1015 913 function updateStyleNameInCard( styleId, newStyleName ) {
1016 - const card = getCardByStyleId( styleId );
914 + const card = getCardByStyleId( styleId );
1017 915 const titleElement = card.querySelector( '.frm-style-card-title' );
1018 916 titleElement.textContent = newStyleName;
1019 917 }
1020 918
1021 919 /**
1022 - * @param {string} styleId
1023 - * @return {HTMLElement} The card element.
920 + * @param {String} templateKey
921 + * @returns {HTMLElement}
1024 922 */
923 + function getTemplateCard( templateKey ) {
924 + const templateCard = document.getElementById( 'frm_template_style_cards_wrapper' ).querySelector( '.frm-style-card[data-template-key="' + templateKey + '"]' );
925 + return templateCard;
926 + }
927 +
928 + /**
929 + * @param {String} styleId
930 + * @returns {HTMLElement}
931 + */
1025 932 function getCardByStyleId( styleId ) {
1026 - const defaultCard = document.querySelector( `#frm_default_style_cards_wrapper > div[data-style-id="${ styleId }"]` );
933 + const defaultCard = document.querySelector( '#frm_default_style_cards_wrapper > div[data-style-id="' + styleId + '"]' );
1027 934 if ( defaultCard ) {
1028 935 return defaultCard;
1029 936 }
1030 937 return Array.from( document.getElementById( 'frm_custom_style_cards_wrapper' ).children ).find( card => card.dataset.styleId === styleId );
@@ -1031,19 +938,19 @@
1031 938 }
1032 939
1033 940 /**
1034 941 * @param {HTMLElement} option
1035 - * @param {string} iconId
1036 - * @return {void}
942 + * @param {String} iconId
943 + * @returns {void}
1037 944 */
1038 945 function addIconToOption( option, iconId ) {
1039 - const icon = frmDom.svg( { href: `#${ iconId }` } );
946 + const icon = frmDom.svg({ href: '#' + iconId });
1040 947 option.insertBefore( icon, option.firstChild );
1041 948 }
1042 949
1043 950 /**
1044 - * @param {string} styleId
1045 - * @return {void}
951 + * @param {String} styleId
952 + * @returns {void}
1046 953 */
1047 954 function confirmResetStyle( styleId ) {
1048 955 stylerModal(
1049 956 'frm_reset_style_modal',
@@ -1055,9 +962,9 @@
1055 962 );
1056 963 }
1057 964
1058 965 /**
1059 - * @return {HTMLElement} The modal content element.
966 + * @returns {HTMLElement}
1060 967 */
1061 968 function getResetStyleModalContent() {
1062 969 const content = div( __( 'Reset this style back to the default?', 'formidable' ) );
1063 970 content.style.padding = '20px';
@@ -1064,23 +971,23 @@
1064 971 return content;
1065 972 }
1066 973
1067 974 /**
1068 - * @param {string} styleId
1069 - * @return {HTMLElement} The modal footer element.
975 + * @param {String} styleId
976 + * @returns {HTMLElement}
1070 977 */
1071 978 function getResetStyleModalFooter( styleId ) {
1072 - const cancelButton = footerButton( {
979 + const cancelButton = footerButton({
1073 980 text: __( 'Cancel', 'formidable' ),
1074 981 buttonType: 'cancel'
1075 - } );
982 + });
1076 983 cancelButton.classList.add( 'dismiss' );
1077 - const resetButton = footerButton( {
984 + const resetButton = footerButton({
1078 985 text: __( 'Reset style', 'formidable' ),
1079 986 buttonType: 'primary'
1080 - } );
987 + });
1081 988 onClickPreventDefault( resetButton, () => resetStyle( styleId ) );
1082 - return div( { children: [ cancelButton, resetButton ] } );
989 + return div({ children: [ cancelButton, resetButton ] });
1083 990 }
1084 991
1085 992 /**
1086 993 * Handle reset dropdown action.
@@ -1085,10 +992,10 @@
1085 992 /**
1086 993 * Handle reset dropdown action.
1087 994 * This function handles the front end routing for the reset action as reset works differently for edit and list views.
1088 995 *
1089 - * @param {string} styleId
1090 - * @return {void}
996 + * @param {String} styleId
997 + * @returns {void}
1091 998 */
1092 999 function resetStyle( styleId ) {
1093 1000 if ( isListPage ) {
1094 1001 resetStyleOnListPage( styleId );
@@ -1099,10 +1006,10 @@
1099 1006
1100 1007 /**
1101 1008 * Make a POST request to reset the style then reload the CSS and reset the card styles.
1102 1009 *
1103 - * @param {string} styleId
1104 - * @return {void}
1010 + * @param {String} styleId
1011 + * @returns {void}
1105 1012 */
1106 1013 function resetStyleOnListPage( styleId ) {
1107 1014 const formData = new FormData();
1108 1015 formData.append( 'style_id', styleId );
@@ -1125,12 +1032,12 @@
1125 1032
1126 1033 /**
1127 1034 * Reset the style in-page (without actually updating it).
1128 1035 *
1129 - * @return {void}
1036 + * @returns {void}
1130 1037 */
1131 1038 function resetStyleOnEditPage() {
1132 - jQuery.ajax( {
1039 + jQuery.ajax({
1133 1040 type: 'POST',
1134 1041 url: ajaxurl,
1135 1042 data: {
1136 1043 action: 'frm_settings_reset',
@@ -1136,9 +1043,9 @@
1136 1043 action: 'frm_settings_reset',
1137 1044 nonce: frmGlobal.nonce
1138 1045 },
1139 1046 success: syncEditPageAfterResetAction
1140 - } );
1047 + });
1141 1048 }
1142 1049
1143 1050 /**
1144 1051 * Handle reset success on edit page.
@@ -1146,9 +1053,9 @@
1146 1053 *
1147 1054 * @todo Stop triggering change events with jQuery. And remove the other jQuery as well.
1148 1055 *
1149 1056 * @param {Object} response
1150 - * @return {void}
1057 + * @returns {void}
1151 1058 */
1152 1059 function syncEditPageAfterResetAction( response ) {
1153 1060 let defaultValues = response.replace( /^\s+|\s+$/g, '' );
1154 1061 if ( defaultValues.indexOf( '{' ) === 0 ) {
@@ -1155,9 +1062,9 @@
1155 1062 defaultValues = JSON.parse( defaultValues );
1156 1063 }
1157 1064
1158 1065 for ( const key in defaultValues ) {
1159 - let targetInput = document.querySelector( `input[name$="[${ key }]"], select[name$="[${ key }]"]` );
1066 + let targetInput = document.querySelector( 'input[name$="[' + key + ']"], select[name$="[' + key + ']"]' );
1160 1067 if ( ! targetInput ) {
1161 1068 continue;
1162 1069 }
1163 1070
@@ -1162,9 +1069,9 @@
1162 1069 }
1163 1070
1164 1071 if ( 'radio' === targetInput.getAttribute( 'type' ) ) {
1165 1072 // Reset the repeater icon dropdown.
1166 - targetInput = document.querySelector( `input[name$="[${ key }]"][value="${ defaultValues[ key ] }"]` );
1073 + targetInput = document.querySelector( 'input[name$="[' + key + ']"][value="' + defaultValues[ key ] + '"]' );
1167 1074 if ( targetInput ) {
1168 1075 targetInput.checked = true;
1169 1076 jQuery( targetInput ).trigger( 'change' );
1170 1077 }
@@ -1178,9 +1085,8 @@
1178 1085 jQuery( targetInput ).trigger( 'change' );
1179 1086 }
1180 1087 }
1181 1088
1182 - resetCustomCSSEditor();
1183 1089 jQuery( '#frm_submit_style, #frm_auto_width' ).prop( 'checked', false );
1184 1090 jQuery( document.getElementById( 'frm_fieldset' ) ).trigger( 'change' );
1185 1091 showStyleResetSuccessMessage();
1186 1092 }
@@ -1185,26 +1091,11 @@
1185 1091 showStyleResetSuccessMessage();
1186 1092 }
1187 1093
1188 1094 /**
1189 - * Reset the custom CSS editor.
1190 - *
1191 - * @return {void}
1192 - */
1193 - function resetCustomCSSEditor() {
1194 - const checkbox = document.getElementById( 'frm_enable_single_style_custom_css' );
1195 - const editorWrapper = document.getElementById( 'frm_single_style_custom_css_editor' );
1196 - if ( ! checkbox || ! editorWrapper ) {
1197 - return;
1198 - }
1199 - checkbox.checked = false;
1200 - editorWrapper.classList.add( 'frm_hidden' );
1201 - }
1202 -
1203 - /**
1204 1095 * Reload Formidable CSS after a style is reset so the preview updates immediately without needing to reload the page.
1205 1096 *
1206 - * @return {void}
1097 + * @returns {void}
1207 1098 */
1208 1099 function reloadCSSAfterStyleReset() {
1209 1100 const style = document.getElementById( 'frm-custom-theme-css' );
1210 1101 if ( ! style ) {
@@ -1211,27 +1102,27 @@
1211 1102 return;
1212 1103 }
1213 1104
1214 1105 const newStyle = document.createElement( 'link' );
1215 - newStyle.rel = 'stylesheet';
1216 - newStyle.type = 'text/css';
1217 - newStyle.href = `${ style.href }&key=${ getAutoId() }`; // Make the URL unique so the old stylesheet doesn't get picked up by cache.
1106 + newStyle.rel = 'stylesheet';
1107 + newStyle.type = 'text/css';
1108 + newStyle.href = style.href + '&key=' + getAutoId(); // Make the URL unique so the old stylesheet doesn't get picked up by cache.
1218 1109
1219 1110 // Listen for the new style to load before removing the old style to avoid having no styles while the new style is loading.
1220 1111 newStyle.addEventListener(
1221 1112 'load',
1222 1113 () => {
1223 - style.remove();
1114 + style.parentNode.removeChild( style );
1224 1115 newStyle.id = 'frm-custom-theme-css'; // Assign the old ID to the new style so it can be removed in the next reset action.
1225 1116 }
1226 1117 );
1227 1118
1228 - const head = document.getElementsByTagName( 'HEAD' )[ 0 ];
1229 - head.append( newStyle );
1119 + const head = document.getElementsByTagName( 'HEAD' )[0];
1120 + head.appendChild( newStyle );
1230 1121 }
1231 1122
1232 1123 /**
1233 - * @return {number} The auto ID.
1124 + * @returns {Number}
1234 1125 */
1235 1126 function getAutoId() {
1236 1127 return ++state.autoId;
1237 1128 }
@@ -1236,29 +1127,30 @@
1236 1127 return ++state.autoId;
1237 1128 }
1238 1129
1239 1130 /**
1240 - * @param {Object} data
1241 - * @param {HTMLElement} data.anchor
1242 - * @param {string} data.type
1243 - * @return {HTMLElement} The dropdown item element.
1131 + * @param {Object} data {
1132 + * @type {Element} anchor
1133 + * @type {String} type
1134 + * }
1135 + * @returns {Element}
1244 1136 */
1245 - function wrapDropdownItem( { anchor, type } ) {
1246 - return div( {
1247 - className: `dropdown-item frm-${ type }-style`,
1137 + function wrapDropdownItem({ anchor, type }) {
1138 + return div({
1139 + className: 'dropdown-item frm-' + type + '-style',
1248 1140 child: anchor
1249 - } );
1141 + });
1250 1142 }
1251 1143
1252 1144 /**
1253 1145 * This gets triggered through a hook called in frmAdminBuild.styleInit() from formidable_admin.js.
1254 1146 *
1255 - * @return {void}
1147 + * @returns {void}
1256 1148 */
1257 1149 function initEditPage() {
1258 - const { debounce } = frmDom.util;
1150 + const { debounce } = frmDom.util;
1259 1151 const debouncedPreviewUpdate = debounce( () => changeStyling(), 100 );
1260 - const debouncedColorChange = debounce( ( event, value ) => {
1152 + const debouncedColorChange = debounce(( event, value ) => {
1261 1153 /**
1262 1154 * Fires on style colorpicker change.
1263 1155 *
1264 1156 * @param {Event} data.event The color change event.
@@ -1269,94 +1161,37 @@
1269 1161
1270 1162 const debouncedTextSquishCheck = debounce( textSquishCheck, 300 );
1271 1163 initPosClass(); // It's important that this gets called before we add event listeners because it triggers change events.
1272 1164
1273 - [ 'frm_field_height', 'frm_field_font_size', 'frm_field_pad' ].forEach( selector => {
1165 + ['frm_field_height', 'frm_field_font_size', 'frm_field_pad'].forEach( selector => {
1274 1166 document.getElementById( selector ).addEventListener( 'change', debouncedTextSquishCheck );
1275 - } );
1167 + });
1276 1168
1277 - function detectColorFormat( value ) {
1278 - if ( value.startsWith( 'rgba' ) ) {
1279 - return 'rgba';
1280 - }
1281 - if ( value.startsWith( 'rgb' ) ) {
1282 - return 'rgb';
1283 - }
1284 - if ( value.startsWith( 'hsla' ) ) {
1285 - return 'hsla';
1286 - }
1287 - if ( value.startsWith( 'hsl' ) ) {
1288 - return 'hsl';
1289 - }
1290 - return 'hex';
1291 - }
1292 -
1293 - const nativeInputValueDescriptor = Object.getOwnPropertyDescriptor( HTMLInputElement.prototype, 'value' );
1294 -
1295 - jQuery( 'input.hex' ).each( function() {
1296 - this.dataset.colorFormat = detectColorFormat( this.value );
1297 -
1298 - // Prevent iris from overwriting non-hex formats with hex during color picking.
1299 - const input = this;
1300 - Object.defineProperty( input, 'value', {
1301 - get() {
1302 - return nativeInputValueDescriptor.get.call( this );
1303 - },
1304 - set( val ) {
1305 - const format = input.dataset.colorFormat;
1306 - if ( format && 'hex' !== format && /^#[0-9a-f]{3,8}$/i.test( val ) ) {
1307 - return;
1308 - }
1309 - nativeInputValueDescriptor.set.call( this, val );
1310 - },
1311 - configurable: true
1312 - } );
1313 - } ).on( 'keyup', function() {
1314 - const newFormat = detectColorFormat( this.value );
1315 - if ( this.dataset.colorFormat !== newFormat ) {
1316 - this.dataset.colorFormat = newFormat;
1317 - const container = this.closest( '.wp-picker-container' );
1318 - if ( container ) {
1319 - container.querySelector( '.wp-color-result-text' ).textContent = this.value;
1320 - }
1321 - debouncedColorChange( { target: this }, this.value );
1322 - }
1323 - } ).wpColorPicker( {
1324 - change( event, ui ) {
1325 - const input = event.target;
1326 - const format = input.dataset.colorFormat || 'hex';
1327 - let color;
1328 -
1169 + jQuery( 'input.hex' ).wpColorPicker({
1170 + change: function( event, ui ) {
1171 + let color = jQuery( this ).wpColorPicker( 'color' );
1329 1172 trackUnsavedChange();
1330 -
1331 1173 if ( ui.color._alpha < 1 ) {
1332 - input.dataset.colorFormat = 'rgba';
1174 + // If there's transparency, use RGBA
1333 1175 color = ui.color.toCSS( 'rgba' );
1334 - } else if ( 'hex' === format ) {
1335 - color = ui.color.toString();
1336 - } else {
1337 - color = ui.color.toCSS( format );
1338 1176 }
1339 -
1340 1177 debouncedColorChange( event, color );
1341 1178
1342 - input.value = color;
1343 -
1344 - if ( null !== input.getAttribute( 'data-alpha-color-type' ) ) {
1179 + if ( null !== event.target.getAttribute( 'data-alpha-color-type' ) ) {
1345 1180 debouncedPreviewUpdate();
1346 1181 return;
1347 1182 }
1348 1183
1349 - debouncedPreviewUpdate();
1184 + jQuery( event.target ).val( color ).trigger( 'change' );
1350 1185 }
1351 - } );
1186 + });
1352 1187 jQuery( '.wp-color-result-text' ).text( function( _, oldText ) {
1353 1188 const container = jQuery( this ).closest( '.wp-picker-container' );
1354 - if ( container !== undefined && container[ 0 ].parentElement.classList.contains( 'frm-colorpicker' ) ) {
1355 - return container[ 0 ].querySelector( '.wp-color-picker' ).value;
1189 + if ( 'undefined' !== typeof container && container[0].parentElement.classList.contains( 'frm-colorpicker' ) ) {
1190 + return container[0].querySelector( '.wp-color-picker' ).value;
1356 1191 }
1357 1192 return oldText === 'Select Color' ? 'Select' : oldText;
1358 - } );
1193 + });
1359 1194 jQuery( '#frm_styling_form .styling_settings, #frm_styling_form .frm-field-shape, #frm_styling_form input[name="frm_style_setting[post_content][base_font_size]"]' ).on( 'change', debouncedPreviewUpdate );
1360 1195
1361 1196 // This is really only necessary for Pro. But if Pro is not up to date to initialize the datepicker in the sample form, it should still work because it's initialized here.
1362 1197 initDatepickerSample();
@@ -1370,17 +1205,17 @@
1370 1205 /**
1371 1206 * Sends an AJAX POST request for new CSS to use for the preview.
1372 1207 * This is called whenever a style setting is changed, generally using debouncedPreviewUpdate to avoid simultaneous requests.
1373 1208 *
1374 - * @return {void}
1209 + * @returns {void}
1375 1210 */
1376 1211 function changeStyling() {
1377 1212 const styleInputs = Array.from( document.getElementById( 'frm_style_sidebar' ).querySelectorAll( 'input, select, textarea' ) ).filter(
1378 1213 input => 'style_name' === input.name || 0 === input.name.indexOf( 'frm_style_setting[post_content]' )
1379 1214 );
1380 - const locStr = JSON.stringify( jQuery( styleInputs ).serializeArray() );
1215 + const locStr = JSON.stringify( jQuery( styleInputs ).serializeArray() );
1381 1216
1382 - jQuery.ajax( {
1217 + jQuery.ajax({
1383 1218 type: 'POST',
1384 1219 url: ajaxurl,
1385 1220 data: {
1386 1221 action: 'frm_change_styling',
@@ -1386,24 +1221,24 @@
1386 1221 action: 'frm_change_styling',
1387 1222 nonce: frmGlobal.nonce,
1388 1223 frm_style_setting: locStr
1389 1224 },
1390 - success: css => {
1225 + success: ( css ) => {
1391 1226 handleChangeStylingSuccess( css );
1392 1227 setSelectPlaceholderColor();
1393 1228 }
1394 - } );
1229 + });
1395 1230 }
1396 1231
1397 1232 /**
1398 1233 * Update the CSS used for the preview on the edit page when a styling input has been updated.
1399 1234 *
1400 - * @param {string} css The response from the frm_change_styling request.
1401 - * @return {void}
1235 + * @param {String} css The response from the frm_change_styling request.
1236 + * @returns {void}
1402 1237 */
1403 1238 function handleChangeStylingSuccess( css ) {
1404 1239 // Validate the string response. A valid output will include rules with .with_frm_style
1405 - if ( ! css.includes( '.with_frm_style' ) ) {
1240 + if ( -1 === css.indexOf( '.with_frm_style' ) ) {
1406 1241 // Handle error (possibly a permission error, or an outdated nonce).
1407 1242 alert( css );
1408 1243 return;
1409 1244 }
@@ -1413,17 +1248,17 @@
1413 1248 /**
1414 1249 * Possibly pop up with a warning that "text will not display correctly if the field height is too small relative to the field padding and text size".
1415 1250 * This can be triggered when modifying font size, height, and padding.
1416 1251 *
1417 - * @return {void}
1252 + * @returns {void}
1418 1253 */
1419 1254 function textSquishCheck() {
1420 1255 if ( null !== frmDom.util.getCookie( 'frm-style-text-squish-check' ) ) {
1421 1256 return;
1422 1257 }
1423 - const height = document.getElementById( 'frm_field_height' ).value.replace( /\D/g, '' );
1258 + const height = document.getElementById( 'frm_field_height' ).value.replace( /\D/g, '' );
1424 1259 const paddingEntered = document.getElementById( 'frm_field_pad' ).value.split( ' ' );
1425 - const paddingCount = paddingEntered.length;
1260 + const paddingCount = paddingEntered.length;
1426 1261
1427 1262 frmDom.util.setCookie( 'frm-style-text-squish-check', 1, 30 );
1428 1263
1429 1264 // If too many or too few padding entries, leave now
@@ -1432,12 +1267,12 @@
1432 1267 }
1433 1268
1434 1269 const size = document.getElementById( 'frm_field_font_size' ).value.replace( /\D/g, '' );
1435 1270 // Get the top and bottom padding from entered values
1436 - const paddingTop = paddingEntered[ 0 ].replace( /\D/g, '' );
1437 - let paddingBottom = paddingTop;
1271 + const paddingTop = paddingEntered[0].replace( /\D/g, '' );
1272 + let paddingBottom = paddingTop;
1438 1273 if ( paddingCount >= 3 ) {
1439 - paddingBottom = paddingEntered[ 2 ].replace( /\D/g, '' );
1274 + paddingBottom = paddingEntered[2].replace( /\D/g, '' );
1440 1275 }
1441 1276
1442 1277 // Check if there is enough space for text
1443 1278 const textSpace = height - size - paddingTop - paddingBottom - 3;
@@ -1449,16 +1284,16 @@
1449 1284 /**
1450 1285 * When the Collapse icons are updated, sync the dropdown.
1451 1286 * Otherwise the previously selected value will still appear as the selected value.
1452 1287 *
1453 - * @return {void}
1288 + * @returns {void}
1454 1289 */
1455 1290 jQuery( document ).on( 'change', '.frm-dropdown-menu input[type="radio"]', function() {
1456 1291 trackUnsavedChange();
1457 1292
1458 - const radio = this;
1293 + const radio = this;
1459 1294 const btnGrp = radio.closest( '.btn-group' );
1460 - const btnId = btnGrp.getAttribute( 'id' );
1295 + const btnId = btnGrp.getAttribute( 'id' );
1461 1296
1462 1297 const select = document.getElementById( btnId.replace( '_select', '' ) );
1463 1298 if ( select ) {
1464 1299 select.value = radio.value;
@@ -1463,9 +1298,9 @@
1463 1298 if ( select ) {
1464 1299 select.value = radio.value;
1465 1300 }
1466 1301
1467 - jQuery( btnGrp ).children( 'button' ).html( `${ radio.nextElementSibling.innerHTML } <b class="caret"></b>` );
1302 + jQuery( btnGrp ).children( 'button' ).html( radio.nextElementSibling.innerHTML + ' <b class="caret"></b>' );
1468 1303
1469 1304 const activeItem = btnGrp.querySelector( '.dropdown-item.active' );
1470 1305 if ( activeItem ) {
1471 1306 activeItem.classList.remove( 'active' );
@@ -1471,9 +1306,9 @@
1471 1306 activeItem.classList.remove( 'active' );
1472 1307 }
1473 1308
1474 1309 radio.closest( '.dropdown-item' ).classList.add( 'active' );
1475 - } );
1310 + });
1476 1311
1477 1312 if ( frm_admin_js.requireAccordionTitleClickListener ) {
1478 1313 document.querySelectorAll( '.styling_settings h3.accordion-section-title' ).forEach( el => {
1479 1314 el.addEventListener( 'click', event => {
@@ -1479,17 +1314,33 @@
1479 1314 el.addEventListener( 'click', event => {
1480 1315 if ( ! event.target.closest( 'button' ) ) {
1481 1316 el.querySelector( 'button' ).click();
1482 1317 }
1483 - } );
1484 - } );
1318 + });
1319 + });
1485 1320 }
1486 1321 }
1487 1322
1488 1323 /**
1324 + * @param {Event} event
1325 + */
1326 + function maybeCollapseSettings( event ) {
1327 + let expanded;
1328 + const sectionParent = event.target.parentElement;
1329 + if ( event.type === 'keydown' ) {
1330 + expanded = sectionParent.classList.toggle( 'open' );
1331 + jQuery( sectionParent.querySelector( '.accordion-section-content' ) ).toggle( ! expanded ).slideToggle( 150 ); // Animate toggle as in click/enter.
1332 + } else {
1333 + expanded = sectionParent.classList.contains( 'open' );
1334 + }
1335 +
1336 + event.target.setAttribute( 'aria-expanded', expanded );
1337 + }
1338 +
1339 + /**
1489 1340 * @param {HTMLElement} input
1490 1341 * @param {HTMLElement} container
1491 - * @return {void}
1342 + * @returns {void}
1492 1343 */
1493 1344 function checkFloatingLabelsForStyles( input, container ) {
1494 1345 if ( ! container ) {
1495 1346 container = input.closest( '.frm_inside_container' );
@@ -1509,16 +1360,18 @@
1509 1360 if ( firstOpt.hasAttribute( 'data-label' ) ) {
1510 1361 firstOpt.textContent = firstOpt.getAttribute( 'data-label' );
1511 1362 firstOpt.removeAttribute( 'data-label' );
1512 1363 }
1513 - } else if ( firstOpt.textContent ) {
1514 - firstOpt.setAttribute( 'data-label', firstOpt.textContent );
1515 - firstOpt.textContent = '';
1364 + } else {
1365 + if ( firstOpt.textContent ) {
1366 + firstOpt.setAttribute( 'data-label', firstOpt.textContent );
1367 + firstOpt.textContent = '';
1368 + }
1516 1369 }
1517 1370 }
1518 1371
1519 1372 /**
1520 - * @return {void}
1373 + * @returns {void}
1521 1374 */
1522 1375 function initPosClass() {
1523 1376 const positionSetting = document.getElementById( 'frm_position' );
1524 1377
@@ -1532,13 +1385,13 @@
1532 1385
1533 1386 /**
1534 1387 * Update label container classes when the label "Position" setting is changed.
1535 1388 *
1536 - * @return {void}
1389 + * @returns {void}
1537 1390 */
1538 1391 function setPosClass() {
1539 1392 /*jshint validthis:true */
1540 - let { value } = this;
1393 + let value = this.value;
1541 1394 if ( value === 'none' ) {
1542 1395 value = 'top';
1543 1396 } else if ( value === 'no_label' ) {
1544 1397 value = 'none';
@@ -1544,23 +1397,23 @@
1544 1397 value = 'none';
1545 1398 }
1546 1399
1547 1400 document.getElementById( 'frm_style_preview' ).querySelectorAll( '.frm_form_field.frm-default-label-position, #frm_sample_form .frm_form_field' ).forEach( container => {
1548 - const input = container.querySelector( ':scope > input, :scope > select, :scope > textarea' ); // Fields that support floating label should have a directly child input/textarea/select.
1401 + const input = container.querySelector( ':scope > input, :scope > select, :scope > textarea' ); // Fields that support floating label should have a directly child input/textarea/select.
1549 1402 const shouldForceTopStyling = 'inside' === value && ( ! input || 'hidden' === input.type ); // We do not want file upload to use floating labels, or inline datepickers, which both use hidden inputs.
1550 - const currentValue = shouldForceTopStyling ? 'top' : value;
1403 + const currentValue = shouldForceTopStyling ? 'top' : value;
1551 1404
1552 1405 container.classList.remove( 'frm_top_container', 'frm_left_container', 'frm_right_container', 'frm_none_container', 'frm_inside_container' );
1553 - container.classList.add( `frm_${ currentValue }_container` );
1406 + container.classList.add( 'frm_' + currentValue + '_container' );
1554 1407
1555 1408 if ( 'inside' === currentValue ) {
1556 1409 checkFloatingLabelsForStyles( input, container );
1557 1410 }
1558 - } );
1411 + });
1559 1412 }
1560 1413
1561 1414 /**
1562 - * @return {void}
1415 + * @returns {void}
1563 1416 */
1564 1417 function initFloatingLabels() {
1565 1418 [ 'focus', 'blur', 'change' ].forEach(
1566 1419 eventName => frmDom.util.documentOn(
@@ -1576,12 +1429,12 @@
1576 1429 * The signature add on expects that validateFormSubmit is callable.
1577 1430 * Without this, drawing in a signature field triggers a "Uncaught ReferenceError: frmFrontForm is not defined" error.
1578 1431 * We don't want the validation to actually triggr, so just fill in an empty function.
1579 1432 *
1580 - * @return {void}
1433 + * @returns {void}
1581 1434 */
1582 1435 function fillMissingSignatureValidationFunction() {
1583 - if ( window.__FRMSIG === undefined || window.frmFrontForm !== undefined ) {
1436 + if ( 'undefined' === typeof window.__FRMSIG || 'undefined' !== typeof window.frmFrontForm ) {
1584 1437 return;
1585 1438 }
1586 1439
1587 1440 window.frmFrontForm = { validateFormSubmit: () => {} };
@@ -1589,19 +1442,14 @@
1589 1442
1590 1443 /**
1591 1444 * Enable the datepicker in the sample form preview.
1592 1445 *
1593 - * @return {void}
1446 + * @returns {void}
1594 1447 */
1595 1448 function initDatepickerSample() {
1596 - // If flatpickr is defined, then is a recent version of Pro which handles the datepicker preview as it's a PRO feature.
1597 - if ( 'undefined' !== typeof flatpickr ) {
1598 - return;
1599 - }
1600 -
1601 1449 const $sample = jQuery( '#datepicker_sample' );
1602 1450 if ( $sample.length && 'function' === typeof $sample.datepicker ) {
1603 - $sample.datepicker( { changeMonth: true, changeYear: true } );
1451 + $sample.datepicker({ changeMonth: true, changeYear: true });
1604 1452 }
1605 1453 }
1606 1454
1607 1455 /**
@@ -1619,10 +1467,10 @@
1619 1467 return;
1620 1468 }
1621 1469
1622 1470 // Function to change the color of a select element
1623 - const changeSelectColor = select => {
1624 - if ( select.options[ select.selectedIndex ]?.classList.contains( 'frm-select-placeholder' ) ) {
1471 + const changeSelectColor = ( select ) => {
1472 + if ( select.options[select.selectedIndex] && select.options[select.selectedIndex].classList.contains( 'frm-select-placeholder' ) ) {
1625 1473 select.style.setProperty( 'color', textColorDisabled, 'important' );
1626 1474 } else {
1627 1475 select.style.color = '';
1628 1476 }
@@ -1628,15 +1476,15 @@
1628 1476 }
1629 1477 };
1630 1478
1631 1479 // Use a loop to iterate through each select element
1632 - selects.forEach( select => {
1480 + selects.forEach( ( select ) => {
1633 1481 // Apply the color change to each select element
1634 1482 changeSelectColor( select );
1635 1483
1636 1484 // Add an event listener for future changes
1637 1485 select.addEventListener( 'change', () => changeSelectColor( select ) );
1638 - } );
1486 + });
1639 1487 }
1640 1488
1641 1489 // Hook into the styleInit function in formidable_admin.js
1642 1490 wp.hooks.addAction( 'frm_style_editor_init', 'formidable', initEditPage );