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

658 lines 17.5 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 bodyWithModalClassName = 'frm-body-with-open-modal';
529
530 const $modal = jQuery( modal );
531 if ( ! $modal.hasClass( 'frm-dialog' ) ) {
532 $modal.dialog({
533 dialogClass: 'frm-dialog',
534 modal: true,
535 autoOpen: false,
536 closeOnEscape: true,
537 width: width || '550px',
538 resizable: false,
539 draggable: false,
540 open: function() {
541 jQuery( '.ui-dialog-titlebar' ).addClass( 'frm_hidden' ).removeClass( 'ui-helper-clearfix' );
542 jQuery( '#wpwrap' ).addClass( 'frm_overlay' );
543 jQuery( '.frm-dialog' ).removeClass( 'ui-widget ui-widget-content ui-corner-all' );
544
545 modal.classList.remove( 'ui-dialog-content', 'ui-widget-content' );
546
547 $modal.on( 'click', 'a.dismiss', function( event ) {
548 event.preventDefault();
549 $modal.dialog( 'close' );
550 });
551
552 const overlay = document.querySelector( '.ui-widget-overlay' );
553 if ( overlay ) {
554 overlay.addEventListener(
555 'click',
556 function( event ) {
557 event.preventDefault();
558 $modal.dialog( 'close' );
559 }
560 );
561 }
562 },
563 close: function() {
564 document.body.classList.remove( bodyWithModalClassName );
565 jQuery( '#wpwrap' ).removeClass( 'frm_overlay' );
566 jQuery( '.spinner' ).css( 'visibility', 'hidden' );
567 }
568 });
569 }
570
571 document.body.classList.add( bodyWithModalClassName );
572
573 $modal.dialog( 'open' );
574 return $modal;
575 }
576
577 function div( args ) {
578 return tag( 'div', args );
579 }
580
581 function span( args ) {
582 return tag( 'span', args );
583 }
584
585 function a( args = {}) {
586 const anchor = tag( 'a', args );
587 anchor.setAttribute( 'href', 'string' === typeof args.href ? args.href : '#' );
588 if ( 'string' === typeof args.target ) {
589 anchor.target = args.target;
590 }
591 return anchor;
592 }
593
594 function img( args = {}) {
595 const output = tag( 'img', args );
596 if ( 'string' === typeof args.src ) {
597 output.setAttribute( 'src', args.src );
598 }
599 return output;
600 }
601
602 function tag( type, args = {}) {
603 const output = document.createElement( type );
604
605 if ( 'string' === typeof args ) {
606 // Support passing just a string to a tag for simple text elements.
607 output.textContent = args;
608 return output;
609 }
610
611 const { id, className, children, child, text } = args;
612
613 if ( id ) {
614 output.id = id;
615 }
616 if ( className ) {
617 output.className = className;
618 }
619 if ( children ) {
620 children.forEach( child => output.appendChild( child ) );
621 } else if ( child ) {
622 output.appendChild( child );
623 } else if ( text ) {
624 output.textContent = text;
625 }
626 return output;
627 }
628
629 function svg({ href, classList } = {}) {
630 const namespace = 'http://www.w3.org/2000/svg';
631 const output = document.createElementNS( namespace, 'svg' );
632 if ( classList ) {
633 output.classList.add( ...classList );
634 }
635
636 if ( href ) {
637 const use = document.createElementNS( namespace, 'use' );
638 use.setAttribute( 'href', href );
639 output.appendChild( use );
640 output.classList.add( 'frmsvg' );
641 }
642 return output;
643 }
644
645 function setAttributes( element, attrs ) {
646 Object.entries( attrs ).forEach(
647 ([ key, value ]) => element.setAttribute( key, value )
648 );
649 }
650
651 function redraw( element, child ) {
652 element.innerHTML = '';
653 element.appendChild( child );
654 }
655
656 window.frmDom = { tag, div, span, a, img, svg, setAttributes, modal, ajax, bootstrap, autocomplete, search, util, wysiwyg };
657 }() );
658