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

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

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