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

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