PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 5.5
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v5.5
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
formidable / js / admin / dom.js

dom.js in Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More 5.5, at js/admin/dom.js

633 lines 16.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 ( function() {
2 /** globals frmGlobal */
3
4 let __;
5
6 if ( 'undefined' === typeof wp || 'undefined' === typeof wp.i18n || 'function' !== typeof wp.i18n.__ ) {
7 __ = text => text;
8 } else {
9 __ = wp.i18n.__;
10 }
11
12 const modal = {
13 maybeCreateModal: ( id, { title, content, footer, width } = {}) => {
14 let modal = document.getElementById( id );
15
16 if ( ! modal ) {
17 modal = createEmptyModal( id );
18
19 const titleElement = div({
20 className: 'frm-modal-title'
21 });
22
23 if ( 'string' === typeof title ) {
24 titleElement.textContent = title;
25 }
26
27 const a = tag(
28 'a',
29 {
30 child: svg({ href: '#frm_close_icon' }),
31 className: 'dismiss'
32 }
33 );
34 const postbox = modal.querySelector( '.postbox' );
35
36 postbox.appendChild(
37 div({
38 className: 'frm_modal_top',
39 children: [
40 titleElement,
41 div({ child: a })
42 ]
43 })
44 );
45 postbox.appendChild(
46 div({ className: 'frm_modal_content' })
47 );
48 postbox.appendChild(
49 div({ className: 'frm_modal_footer' })
50 );
51 } else if ( 'string' === typeof title ) {
52 const titleElement = modal.querySelector( '.frm-modal-title' );
53 titleElement.textContent = title;
54 }
55
56 if ( ! content && ! footer ) {
57 makeModalIntoADialogAndOpen( modal, { width });
58 return modal;
59 }
60
61 const postbox = modal.querySelector( '.postbox' );
62 const modalHelper = getModalHelper( modal, postbox );
63
64 if ( content ) {
65 modalHelper( content, 'frm_modal_content' );
66 }
67
68 if ( footer ) {
69 modalHelper( footer, 'frm_modal_footer' );
70 }
71
72 makeModalIntoADialogAndOpen( modal );
73 return modal;
74 },
75 footerButton: args => {
76 const output = a( args );
77 output.setAttribute( 'role', 'button' );
78 output.setAttribute( 'tabindex', 0 );
79 if ( args.buttonType ) {
80 output.classList.add( 'button' );
81 switch ( args.buttonType ) {
82 case 'primary':
83 output.classList.add( 'button-primary', 'frm-button-primary' );
84 if ( ! args.noDismiss ) {
85 output.classList.add( 'dismiss' );
86 }
87 break;
88 case 'secondary':
89 output.classList.add( 'button-secondary', 'frm-button-secondary' );
90 output.style.marginRight = '10px';
91 break;
92 case 'cancel':
93 output.classList.add( 'button-secondary', 'frm-modal-cancel' );
94 break;
95 }
96 }
97 return output;
98 }
99 };
100
101 const ajax = {
102 doJsonFetch: async function( action ) {
103 const response = await fetch( ajaxurl + '?action=frm_' + action );
104 const json = await response.json();
105 if ( ! json.success ) {
106 return Promise.reject( json.data || 'JSON result is not successful' );
107 }
108 return Promise.resolve( json.data );
109 },
110 doJsonPost: async function( action, formData ) {
111 formData.append( 'nonce', frmGlobal.nonce );
112 const init = {
113 method: 'POST',
114 body: formData
115 };
116 const response = await fetch( ajaxurl + '?action=frm_' + action, init );
117 const json = await response.json();
118 if ( ! json.success ) {
119 return Promise.reject( json.data || 'JSON result is not successful' );
120 }
121 return Promise.resolve( 'undefined' !== typeof json.data ? json.data : json );
122 }
123 };
124
125 const multiselect = {
126 init: function() {
127 let $select, id, labelledBy;
128
129 $select = jQuery( this );
130 id = $select.is( '[id]' ) ? $select.attr( 'id' ).replace( '[]', '' ) : false;
131 labelledBy = id ? jQuery( '#for_' + id ) : false;
132 labelledBy = id && labelledBy.length ? 'aria-labelledby="' + labelledBy.attr( 'id' ) + '"' : '';
133
134 $select.multiselect({
135 templates: {
136 popupContainer: '<div class="multiselect-container frm-dropdown-menu"></div>',
137 option: '<button type="button" class="multiselect-option dropdown-item frm_no_style_button"></button>',
138 button: '<button type="button" class="multiselect dropdown-toggle btn" data-toggle="dropdown" ' + labelledBy + '><span class="multiselect-selected-text"></span> <b class="caret"></b></button>'
139 },
140 buttonContainer: '<div class="btn-group frm-btn-group dropdown" />',
141 nonSelectedText: '',
142 onDropdownShown: function( event ) {
143 const action = jQuery( event.currentTarget.closest( '.frm_form_action_settings, #frm-show-fields' ) );
144 if ( action.length ) {
145 jQuery( '#wpcontent' ).on( 'click', function() {
146 if ( jQuery( '.multiselect-container.frm-dropdown-menu' ).is( ':visible' ) ) {
147 jQuery( event.currentTarget ).removeClass( 'open' );
148 }
149 });
150 }
151 },
152 onChange: function( element, option ) {
153 $select.trigger( 'frm-multiselect-changed', element, option );
154 }
155 });
156 }
157 };
158
159 const bootstrap = {
160 setupBootstrapDropdowns( callback ) {
161 if ( ! window.bootstrap || ! window.bootstrap.Dropdown ) {
162 return;
163 }
164
165 window.bootstrap.Dropdown._getParentFromElement = getParentFromElement;
166 window.bootstrap.Dropdown.prototype._getParentFromElement = getParentFromElement;
167
168 function getParentFromElement( element ) {
169 let parent;
170 const selector = window.bootstrap.Util.getSelectorFromElement( element );
171
172 if ( selector ) {
173 parent = document.querySelector( selector );
174 }
175
176 const result = parent || element.parentNode;
177 const frmDropdownMenu = result.querySelector( '.frm-dropdown-menu' );
178
179 if ( ! frmDropdownMenu ) {
180 // Not a formidable dropdown, treat like Bootstrap does normally.
181 return result;
182 }
183
184 // Temporarily add dropdown-menu class so bootstrap can initialize.
185 frmDropdownMenu.classList.add( 'dropdown-menu' );
186 setTimeout(
187 function() {
188 frmDropdownMenu.classList.remove( 'dropdown-menu' );
189 },
190 0
191 );
192
193 if ( 'function' === typeof callback ) {
194 callback( frmDropdownMenu );
195 }
196
197 return result;
198 }
199 },
200 multiselect
201 };
202
203 const autocomplete = {
204 initSelectionAutocomplete: function() {
205 if ( jQuery.fn.autocomplete ) {
206 autocomplete.initAutocomplete( 'page' );
207 autocomplete.initAutocomplete( 'user' );
208 }
209 },
210 /**
211 * Init autocomplete.
212 *
213 * @since 4.10.01 Add container param to init autocomplete elements inside an element.
214 *
215 * @param {String} type Type of data. Accepts `page` or `user`.
216 * @param {String|Object} container Container class or element. Default is null.
217 */
218 initAutocomplete: function( type, container ) {
219 const basedUrlParams = '?action=frm_' + type + '_search&nonce=' + frmGlobal.nonce;
220 const elements = ! container ? jQuery( '.frm-' + type + '-search' ) : jQuery( container ).find( '.frm-' + type + '-search' );
221
222 elements.each( initAutocompleteForElement );
223
224 function initAutocompleteForElement() {
225 let urlParams = basedUrlParams;
226 const element = jQuery( this );
227
228 // Check if a custom post type is specific.
229 if ( element.attr( 'data-post-type' ) ) {
230 urlParams += '&post_type=' + element.attr( 'data-post-type' );
231 }
232
233 element.autocomplete({
234 delay: 100,
235 minLength: 0,
236 source: ajaxurl + urlParams,
237 change: autocomplete.selectBlank,
238 select: autocomplete.completeSelectFromResults,
239 focus: () => false,
240 position: {
241 my: 'left top',
242 at: 'left bottom',
243 collision: 'flip'
244 },
245 response: function( event, ui ) {
246 if ( ! ui.content.length ) {
247 const noResult = {
248 value: '',
249 label: frm_admin_js.no_items_found
250 };
251 ui.content.push( noResult );
252 }
253 },
254 create: function() {
255 let $container = jQuery( this ).parent();
256
257 if ( $container.length === 0 ) {
258 $container = 'body';
259 }
260
261 jQuery( this ).autocomplete( 'option', 'appendTo', $container );
262 }
263 })
264 .on( 'focus', function() {
265 // Show options on click to make it work more like a dropdown.
266 if ( this.value === '' || this.nextElementSibling.value < 1 ) {
267 jQuery( this ).autocomplete( 'search', this.value );
268 }
269 })
270 .data( 'ui-autocomplete' )._renderItem = function( ul, item ) {
271 return jQuery( '<li>' )
272 .attr( 'aria-label', item.label )
273 .append( jQuery( '<div>' ).text( item.label ) )
274 .appendTo( ul );
275 };
276 }
277 },
278
279 selectBlank: function( e, ui ) {
280 if ( ui.item === null ) {
281 this.nextElementSibling.value = '';
282 }
283 },
284
285 completeSelectFromResults: function( e, ui ) {
286 e.preventDefault();
287 this.value = ui.item.value === '' ? '' : ui.item.label;
288 this.nextElementSibling.value = ui.item.value;
289 }
290 };
291
292 const search = {
293 wrapInput: ( searchInput, labelText ) => {
294 const label = tag(
295 'label',
296 {
297 className: 'screen-reader-text',
298 text: labelText
299 }
300 );
301 label.setAttribute( 'for', searchInput.id );
302 return tag(
303 'p',
304 {
305 className: 'frm-search',
306 children: [
307 label,
308 span({ className: 'frmfont frm_search_icon' }),
309 searchInput
310 ]
311 }
312 );
313 },
314 newSearchInput: ( id, placeholder, targetClassName, args = {}) => {
315 const input = getAutoSearchInput( id, placeholder );
316 const wrappedSearch = search.wrapInput( input, placeholder );
317 search.init( input, targetClassName, args );
318
319 function getAutoSearchInput( id, placeholder ) {
320 const className = 'frm-search-input frm-auto-search';
321 const inputArgs = { id, className };
322 const input = tag( 'input', inputArgs );
323 input.setAttribute( 'placeholder', placeholder );
324 return input;
325 }
326
327 return wrappedSearch;
328 },
329 init: ( input, targetClassName, { handleSearchResult } = {}) => {
330 input.setAttribute( 'type', 'search' );
331 input.setAttribute( 'autocomplete', 'off' );
332
333 input.addEventListener( 'input', handleSearch );
334 input.addEventListener( 'search', handleSearch );
335 input.addEventListener( 'change', handleSearch );
336
337 function handleSearch() {
338 const searchText = input.value.toLowerCase();
339 const notEmptySearchText = searchText !== '';
340 const items = Array.from( document.getElementsByClassName( targetClassName ) );
341
342 let foundSomething = false;
343 items.forEach( toggleSearchClassesForItem );
344 if ( 'function' === typeof handleSearchResult ) {
345 handleSearchResult({ foundSomething, notEmptySearchText });
346 }
347
348 function toggleSearchClassesForItem( item ) {
349 let itemText;
350
351 if ( item.hasAttribute( 'frm-search-text' ) ) {
352 itemText = item.getAttribute( 'frm-search-text' );
353 } else {
354 itemText = item.innerText.toLowerCase();
355 item.setAttribute( 'frm-search-text', itemText );
356 }
357
358 const hide = notEmptySearchText && -1 === itemText.indexOf( searchText );
359 item.classList.toggle( 'frm_hidden', hide );
360
361 const isSearchResult = ! hide && notEmptySearchText;
362 if ( isSearchResult ) {
363 foundSomething = true;
364 }
365 item.classList.toggle( 'frm-search-result', isSearchResult );
366 }
367 }
368 }
369 };
370
371 const util = {
372 debounce: ( func, wait = 100 ) => {
373 let timeout;
374 return function( ...args ) {
375 clearTimeout( timeout );
376 timeout = setTimeout(
377 () => func.apply( this, args ),
378 wait
379 );
380 };
381 },
382 onClickPreventDefault: ( element, callback ) => {
383 const listener = event => {
384 event.preventDefault();
385 callback( event );
386 };
387 element.addEventListener( 'click', listener );
388 }
389 };
390
391 const wysiwyg = {
392 init( editor, { setupCallback } = {}) {
393 if ( isTinyMceActive() ) {
394 setTimeout( resetTinyMce, 0 );
395 } else {
396 initQuickTagsButtons();
397 }
398
399 setUpTinyMceVisualButtonListener();
400 setUpTinyMceHtmlButtonListener();
401
402 function initQuickTagsButtons() {
403 if ( 'function' !== typeof window.quicktags || typeof window.QTags.instances[ editor.id ] !== 'undefined' ) {
404 return;
405 }
406
407 const id = editor.id;
408 window.quicktags({
409 name: 'qt_' + id,
410 id: id,
411 canvas: editor,
412 settings: { id },
413 toolbar: document.getElementById( 'qt_' + id + '_toolbar' ),
414 theButtons: {}
415 });
416 }
417
418 function initRichText() {
419 const key = Object.keys( tinyMCEPreInit.mceInit )[0];
420 const orgSettings = tinyMCEPreInit.mceInit[ key ];
421
422 const settings = Object.assign(
423 {},
424 orgSettings,
425 {
426 selector: '#' + editor.id,
427 body_class: orgSettings.body_class.replace( key, editor.id )
428 }
429 );
430
431 if ( setupCallback ) {
432 settings.setup = setupCallback;
433 }
434
435 tinymce.init( settings );
436 }
437
438 function removeRichText() {
439 tinymce.EditorManager.execCommand( 'mceRemoveEditor', true, editor.id );
440 }
441
442 function resetTinyMce() {
443 removeRichText();
444 initRichText();
445 }
446
447 function isTinyMceActive() {
448 const id = editor.id;
449 const wrapper = document.getElementById( 'wp-' + id + '-wrap' );
450 return null !== wrapper && wrapper.classList.contains( 'tmce-active' );
451 }
452
453 function setUpTinyMceVisualButtonListener() {
454 jQuery( document ).on(
455 'click', '#' + editor.id + '-html',
456 function() {
457 editor.style.visibility = 'visible';
458 initQuickTagsButtons( editor );
459 }
460 );
461 }
462
463 function setUpTinyMceHtmlButtonListener() {
464 jQuery( '#' + editor.id + '-tmce' ).on( 'click', handleTinyMceHtmlButtonClick );
465 }
466
467 function handleTinyMceHtmlButtonClick() {
468 if ( isTinyMceActive() ) {
469 resetTinyMce();
470 } else {
471 initRichText();
472 }
473
474 const wrap = document.getElementById( 'wp-' + editor.id + '-wrap' );
475 wrap.classList.add( 'tmce-active' );
476 wrap.classList.remove( 'html-active' );
477 }
478 }
479 };
480
481 function getModalHelper( modal, appendTo ) {
482 return function( child, uniqueClassName ) {
483 let element = modal.querySelector( '.' + uniqueClassName );
484 if ( null === element ) {
485 element = div({
486 child: child,
487 className: uniqueClassName
488 });
489 appendTo.appendChild( element );
490 } else {
491 redraw( element, child );
492 }
493 };
494 }
495
496 function createEmptyModal( id ) {
497 const modal = div({ id, className: 'frm-modal' });
498 const postbox = div({ className: 'postbox' });
499 const metaboxHolder = div({ className: 'metabox-holder', child: postbox });
500 modal.appendChild( metaboxHolder );
501 document.body.appendChild( modal );
502 return modal;
503 }
504
505 function makeModalIntoADialogAndOpen( modal, { width } = {}) {
506 const $modal = jQuery( modal );
507 if ( ! $modal.hasClass( 'frm-dialog' ) ) {
508 $modal.dialog({
509 dialogClass: 'frm-dialog',
510 modal: true,
511 autoOpen: false,
512 closeOnEscape: true,
513 width: width || '550px',
514 resizable: false,
515 draggable: false,
516 open: function() {
517 jQuery( '.ui-dialog-titlebar' ).addClass( 'frm_hidden' ).removeClass( 'ui-helper-clearfix' );
518 jQuery( '#wpwrap' ).addClass( 'frm_overlay' );
519 jQuery( '.frm-dialog' ).removeClass( 'ui-widget ui-widget-content ui-corner-all' );
520
521 modal.classList.remove( 'ui-dialog-content', 'ui-widget-content' );
522
523 $modal.on( 'click', 'a.dismiss', function( event ) {
524 event.preventDefault();
525 $modal.dialog( 'close' );
526 });
527
528 const overlay = document.querySelector( '.ui-widget-overlay' );
529 if ( overlay ) {
530 overlay.addEventListener(
531 'click',
532 function( event ) {
533 event.preventDefault();
534 $modal.dialog( 'close' );
535 }
536 );
537 }
538 },
539 close: function() {
540 document.body.style.overflowY = 'initial';
541 jQuery( '#wpwrap' ).removeClass( 'frm_overlay' );
542 jQuery( '.spinner' ).css( 'visibility', 'hidden' );
543 }
544 });
545 }
546
547 document.body.style.overflowY = 'hidden';
548 $modal.dialog( 'open' );
549 return $modal;
550 }
551
552 function div( args ) {
553 return tag( 'div', args );
554 }
555
556 function span( args ) {
557 return tag( 'span', args );
558 }
559
560 function a( args = {}) {
561 const anchor = tag( 'a', args );
562 anchor.setAttribute( 'href', 'string' === typeof args.href ? args.href : '#' );
563 if ( 'string' === typeof args.target ) {
564 anchor.target = args.target;
565 }
566 return anchor;
567 }
568
569 function img( args = {}) {
570 const output = tag( 'img', args );
571 if ( 'string' === typeof args.src ) {
572 output.setAttribute( 'src', args.src );
573 }
574 return output;
575 }
576
577 function tag( type, args = {}) {
578 const output = document.createElement( type );
579
580 if ( 'string' === typeof args ) {
581 // Support passing just a string to a tag for simple text elements.
582 output.textContent = args;
583 return output;
584 }
585
586 const { id, className, children, child, text } = args;
587
588 if ( id ) {
589 output.id = id;
590 }
591 if ( className ) {
592 output.className = className;
593 }
594 if ( children ) {
595 children.forEach( child => output.appendChild( child ) );
596 } else if ( child ) {
597 output.appendChild( child );
598 } else if ( text ) {
599 output.textContent = text;
600 }
601 return output;
602 }
603
604 function svg({ href, classList } = {}) {
605 const namespace = 'http://www.w3.org/2000/svg';
606 const output = document.createElementNS( namespace, 'svg' );
607 if ( classList ) {
608 output.classList.add( ...classList );
609 }
610
611 if ( href ) {
612 const use = document.createElementNS( namespace, 'use' );
613 use.setAttribute( 'href', href );
614 output.appendChild( use );
615 output.classList.add( 'frmsvg' );
616 }
617 return output;
618 }
619
620 function setAttributes( element, attrs ) {
621 Object.entries( attrs ).forEach(
622 ([ key, value ]) => element.setAttribute( key, value )
623 );
624 }
625
626 function redraw( element, child ) {
627 element.innerHTML = '';
628 element.appendChild( child );
629 }
630
631 window.frmDom = { tag, div, span, a, img, svg, setAttributes, modal, ajax, bootstrap, autocomplete, search, util, wysiwyg };
632 }() );
633