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

539 lines 14.7 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 function getModalHelper( modal, appendTo ) {
392 return function( child, uniqueClassName ) {
393 let element = modal.querySelector( '.' + uniqueClassName );
394 if ( null === element ) {
395 element = div({
396 child: child,
397 className: uniqueClassName
398 });
399 appendTo.appendChild( element );
400 } else {
401 redraw( element, child );
402 }
403 };
404 }
405
406 function createEmptyModal( id ) {
407 const modal = div({ id, className: 'frm-modal' });
408 const postbox = div({ className: 'postbox' });
409 const metaboxHolder = div({ className: 'metabox-holder', child: postbox });
410 modal.appendChild( metaboxHolder );
411 document.body.appendChild( modal );
412 return modal;
413 }
414
415 function makeModalIntoADialogAndOpen( modal, { width } = {}) {
416 const $modal = jQuery( modal );
417 if ( ! $modal.hasClass( 'frm-dialog' ) ) {
418 $modal.dialog({
419 dialogClass: 'frm-dialog',
420 modal: true,
421 autoOpen: false,
422 closeOnEscape: true,
423 width: width || '550px',
424 resizable: false,
425 draggable: false,
426 open: function() {
427 jQuery( '.ui-dialog-titlebar' ).addClass( 'frm_hidden' ).removeClass( 'ui-helper-clearfix' );
428 jQuery( '#wpwrap' ).addClass( 'frm_overlay' );
429 jQuery( '.frm-dialog' ).removeClass( 'ui-widget ui-widget-content ui-corner-all' );
430
431 modal.classList.remove( 'ui-dialog-content', 'ui-widget-content' );
432
433 $modal.on( 'click', 'a.dismiss', function( event ) {
434 event.preventDefault();
435 $modal.dialog( 'close' );
436 });
437
438 const overlay = document.querySelector( '.ui-widget-overlay' );
439 if ( overlay ) {
440 overlay.addEventListener(
441 'click',
442 function( event ) {
443 event.preventDefault();
444 $modal.dialog( 'close' );
445 }
446 );
447 }
448 },
449 close: function() {
450 document.body.style.overflowY = 'initial';
451 jQuery( '#wpwrap' ).removeClass( 'frm_overlay' );
452 jQuery( '.spinner' ).css( 'visibility', 'hidden' );
453 }
454 });
455 }
456
457 document.body.style.overflowY = 'hidden';
458 $modal.dialog( 'open' );
459 return $modal;
460 }
461
462 function div( args ) {
463 return tag( 'div', args );
464 }
465
466 function span( args ) {
467 return tag( 'span', args );
468 }
469
470 function a( args = {}) {
471 const anchor = tag( 'a', args );
472 anchor.setAttribute( 'href', 'string' === typeof args.href ? args.href : '#' );
473 if ( 'string' === typeof args.target ) {
474 anchor.target = args.target;
475 }
476 return anchor;
477 }
478
479 function img( args = {}) {
480 const output = tag( 'img', args );
481 if ( 'string' === typeof args.src ) {
482 output.setAttribute( 'src', args.src );
483 }
484 return output;
485 }
486
487 function tag( type, args = {}) {
488 const output = document.createElement( type );
489
490 if ( 'string' === typeof args ) {
491 // Support passing just a string to a tag for simple text elements.
492 output.textContent = args;
493 return output;
494 }
495
496 const { id, className, children, child, text } = args;
497
498 if ( id ) {
499 output.id = id;
500 }
501 if ( className ) {
502 output.className = className;
503 }
504 if ( children ) {
505 children.forEach( child => output.appendChild( child ) );
506 } else if ( child ) {
507 output.appendChild( child );
508 } else if ( text ) {
509 output.textContent = text;
510 }
511 return output;
512 }
513
514 function svg({ href } = {}) {
515 const namespace = 'http://www.w3.org/2000/svg';
516 const output = document.createElementNS( namespace, 'svg' );
517 if ( href ) {
518 const use = document.createElementNS( namespace, 'use' );
519 use.setAttribute( 'href', href );
520 output.appendChild( use );
521 output.classList.add( 'frmsvg' );
522 }
523 return output;
524 }
525
526 function setAttributes( element, attrs ) {
527 Object.entries( attrs ).forEach(
528 ([ key, value ]) => element.setAttribute( key, value )
529 );
530 }
531
532 function redraw( element, child ) {
533 element.innerHTML = '';
534 element.appendChild( child );
535 }
536
537 window.frmDom = { tag, div, span, a, img, svg, setAttributes, modal, ajax, bootstrap, autocomplete, search, util };
538 }() );
539