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

651 lines 17.3 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, height, addFocusEvents } = {}) {
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 settings.setup = editor => {
432 if ( addFocusEvents ) {
433 function focusInCallback() {
434 jQuery( editor.targetElm ).trigger( 'focusin' );
435 editor.off( 'focusin', '**' );
436 }
437
438 editor.on( 'focusin', focusInCallback );
439
440 editor.on( 'focusout', function() {
441 editor.on( 'focusin', focusInCallback );
442 });
443 }
444 if ( setupCallback ) {
445 setupCallback( editor );
446 }
447 };
448
449 if ( height ) {
450 settings.height = height;
451 }
452
453 tinymce.init( settings );
454 }
455
456 function removeRichText() {
457 tinymce.EditorManager.execCommand( 'mceRemoveEditor', true, editor.id );
458 }
459
460 function resetTinyMce() {
461 removeRichText();
462 initRichText();
463 }
464
465 function isTinyMceActive() {
466 const id = editor.id;
467 const wrapper = document.getElementById( 'wp-' + id + '-wrap' );
468 return null !== wrapper && wrapper.classList.contains( 'tmce-active' );
469 }
470
471 function setUpTinyMceVisualButtonListener() {
472 jQuery( document ).on(
473 'click', '#' + editor.id + '-html',
474 function() {
475 editor.style.visibility = 'visible';
476 initQuickTagsButtons( editor );
477 }
478 );
479 }
480
481 function setUpTinyMceHtmlButtonListener() {
482 jQuery( '#' + editor.id + '-tmce' ).on( 'click', handleTinyMceHtmlButtonClick );
483 }
484
485 function handleTinyMceHtmlButtonClick() {
486 if ( isTinyMceActive() ) {
487 resetTinyMce();
488 } else {
489 initRichText();
490 }
491
492 const wrap = document.getElementById( 'wp-' + editor.id + '-wrap' );
493 wrap.classList.add( 'tmce-active' );
494 wrap.classList.remove( 'html-active' );
495 }
496 }
497 };
498
499 function getModalHelper( modal, appendTo ) {
500 return function( child, uniqueClassName ) {
501 let element = modal.querySelector( '.' + uniqueClassName );
502 if ( null === element ) {
503 element = div({
504 child: child,
505 className: uniqueClassName
506 });
507 appendTo.appendChild( element );
508 } else {
509 redraw( element, child );
510 }
511 };
512 }
513
514 function createEmptyModal( id ) {
515 const modal = div({ id, className: 'frm-modal' });
516 const postbox = div({ className: 'postbox' });
517 const metaboxHolder = div({ className: 'metabox-holder', child: postbox });
518 modal.appendChild( metaboxHolder );
519 document.body.appendChild( modal );
520 return modal;
521 }
522
523 function makeModalIntoADialogAndOpen( modal, { width } = {}) {
524 const $modal = jQuery( modal );
525 if ( ! $modal.hasClass( 'frm-dialog' ) ) {
526 $modal.dialog({
527 dialogClass: 'frm-dialog',
528 modal: true,
529 autoOpen: false,
530 closeOnEscape: true,
531 width: width || '550px',
532 resizable: false,
533 draggable: false,
534 open: function() {
535 jQuery( '.ui-dialog-titlebar' ).addClass( 'frm_hidden' ).removeClass( 'ui-helper-clearfix' );
536 jQuery( '#wpwrap' ).addClass( 'frm_overlay' );
537 jQuery( '.frm-dialog' ).removeClass( 'ui-widget ui-widget-content ui-corner-all' );
538
539 modal.classList.remove( 'ui-dialog-content', 'ui-widget-content' );
540
541 $modal.on( 'click', 'a.dismiss', function( event ) {
542 event.preventDefault();
543 $modal.dialog( 'close' );
544 });
545
546 const overlay = document.querySelector( '.ui-widget-overlay' );
547 if ( overlay ) {
548 overlay.addEventListener(
549 'click',
550 function( event ) {
551 event.preventDefault();
552 $modal.dialog( 'close' );
553 }
554 );
555 }
556 },
557 close: function() {
558 document.body.style.overflowY = 'initial';
559 jQuery( '#wpwrap' ).removeClass( 'frm_overlay' );
560 jQuery( '.spinner' ).css( 'visibility', 'hidden' );
561 }
562 });
563 }
564
565 document.body.style.overflowY = 'hidden';
566 $modal.dialog( 'open' );
567 return $modal;
568 }
569
570 function div( args ) {
571 return tag( 'div', args );
572 }
573
574 function span( args ) {
575 return tag( 'span', args );
576 }
577
578 function a( args = {}) {
579 const anchor = tag( 'a', args );
580 anchor.setAttribute( 'href', 'string' === typeof args.href ? args.href : '#' );
581 if ( 'string' === typeof args.target ) {
582 anchor.target = args.target;
583 }
584 return anchor;
585 }
586
587 function img( args = {}) {
588 const output = tag( 'img', args );
589 if ( 'string' === typeof args.src ) {
590 output.setAttribute( 'src', args.src );
591 }
592 return output;
593 }
594
595 function tag( type, args = {}) {
596 const output = document.createElement( type );
597
598 if ( 'string' === typeof args ) {
599 // Support passing just a string to a tag for simple text elements.
600 output.textContent = args;
601 return output;
602 }
603
604 const { id, className, children, child, text } = args;
605
606 if ( id ) {
607 output.id = id;
608 }
609 if ( className ) {
610 output.className = className;
611 }
612 if ( children ) {
613 children.forEach( child => output.appendChild( child ) );
614 } else if ( child ) {
615 output.appendChild( child );
616 } else if ( text ) {
617 output.textContent = text;
618 }
619 return output;
620 }
621
622 function svg({ href, classList } = {}) {
623 const namespace = 'http://www.w3.org/2000/svg';
624 const output = document.createElementNS( namespace, 'svg' );
625 if ( classList ) {
626 output.classList.add( ...classList );
627 }
628
629 if ( href ) {
630 const use = document.createElementNS( namespace, 'use' );
631 use.setAttribute( 'href', href );
632 output.appendChild( use );
633 output.classList.add( 'frmsvg' );
634 }
635 return output;
636 }
637
638 function setAttributes( element, attrs ) {
639 Object.entries( attrs ).forEach(
640 ([ key, value ]) => element.setAttribute( key, value )
641 );
642 }
643
644 function redraw( element, child ) {
645 element.innerHTML = '';
646 element.appendChild( child );
647 }
648
649 window.frmDom = { tag, div, span, a, img, svg, setAttributes, modal, ajax, bootstrap, autocomplete, search, util, wysiwyg };
650 }() );
651