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

535 lines 14.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 let frmDom;
2
3 ( function() {
4 /** globals frmGlobal */
5
6 let __;
7
8 if ( 'undefined' === typeof wp || 'undefined' === typeof wp.i18n || 'function' !== typeof wp.i18n.__ ) {
9 __ = text => text;
10 } else {
11 __ = wp.i18n.__;
12 }
13
14 const modal = {
15 maybeCreateModal: ( id, { title, content, footer, width } = {}) => {
16 let modal = document.getElementById( id );
17
18 if ( ! modal ) {
19 modal = createEmptyModal( id );
20
21 const titleElement = div({
22 className: 'frm-modal-title'
23 });
24
25 if ( 'string' === typeof title ) {
26 titleElement.textContent = title;
27 }
28
29 const a = tag(
30 'a',
31 {
32 child: svg({ href: '#frm_close_icon' }),
33 className: 'dismiss'
34 }
35 );
36 const postbox = modal.querySelector( '.postbox' );
37
38 postbox.appendChild(
39 div({
40 className: 'frm_modal_top',
41 children: [
42 titleElement,
43 div({ child: a })
44 ]
45 })
46 );
47 postbox.appendChild(
48 div({ className: 'frm_modal_content' })
49 );
50 postbox.appendChild(
51 div({ className: 'frm_modal_footer' })
52 );
53 } else if ( 'string' === typeof title ) {
54 const titleElement = modal.querySelector( '.frm-modal-title' );
55 titleElement.textContent = title;
56 }
57
58 if ( ! content && ! footer ) {
59 makeModalIntoADialogAndOpen( modal, { width });
60 return modal;
61 }
62
63 const postbox = modal.querySelector( '.postbox' );
64 const modalHelper = getModalHelper( modal, postbox );
65
66 if ( content ) {
67 modalHelper( content, 'frm_modal_content' );
68 }
69
70 if ( footer ) {
71 modalHelper( footer, 'frm_modal_footer' );
72 }
73
74 makeModalIntoADialogAndOpen( modal );
75 return modal;
76 },
77 footerButton: args => {
78 const output = a( args );
79 output.setAttribute( 'role', 'button' );
80 output.setAttribute( 'tabindex', 0 );
81 if ( args.buttonType ) {
82 output.classList.add( 'button' );
83 switch ( args.buttonType ) {
84 case 'primary':
85 output.classList.add( 'button-primary', 'frm-button-primary' );
86 if ( ! args.noDismiss ) {
87 output.classList.add( 'dismiss' );
88 }
89 break;
90 case 'secondary':
91 output.classList.add( 'button-secondary', 'frm-button-secondary' );
92 output.style.marginRight = '10px';
93 break;
94 case 'cancel':
95 output.classList.add( 'button-secondary', 'frm-modal-cancel' );
96 break;
97 }
98 }
99 return output;
100 }
101 };
102
103 const ajax = {
104 doJsonFetch: async function( action ) {
105 const response = await fetch( ajaxurl + '?action=frm_' + action );
106 const json = await response.json();
107 if ( ! json.success ) {
108 return Promise.reject( json.data || 'JSON result is not successful' );
109 }
110 return Promise.resolve( json.data );
111 },
112 doJsonPost: async function( action, formData ) {
113 formData.append( 'nonce', frmGlobal.nonce );
114 const init = {
115 method: 'POST',
116 body: formData
117 };
118 const response = await fetch( ajaxurl + '?action=frm_' + action, init );
119 const json = await response.json();
120 if ( ! json.success ) {
121 return Promise.reject( json.data || 'JSON result is not successful' );
122 }
123 return Promise.resolve( 'undefined' !== typeof json.data ? json.data : json );
124 }
125 };
126
127 const multiselect = {
128 init: function() {
129 let $select, id, labelledBy;
130
131 $select = jQuery( this );
132 id = $select.is( '[id]' ) ? $select.attr( 'id' ).replace( '[]', '' ) : false;
133 labelledBy = id ? jQuery( '#for_' + id ) : false;
134 labelledBy = id && labelledBy.length ? 'aria-labelledby="' + labelledBy.attr( 'id' ) + '"' : '';
135
136 $select.multiselect({
137 templates: {
138 popupContainer: '<div class="multiselect-container frm-dropdown-menu"></div>',
139 option: '<button type="button" class="multiselect-option dropdown-item frm_no_style_button"></button>',
140 button: '<button type="button" class="multiselect dropdown-toggle btn" data-toggle="dropdown" ' + labelledBy + '><span class="multiselect-selected-text"></span> <b class="caret"></b></button>'
141 },
142 buttonContainer: '<div class="btn-group frm-btn-group dropdown" />',
143 nonSelectedText: '',
144 onDropdownShown: function( event ) {
145 const action = jQuery( event.currentTarget.closest( '.frm_form_action_settings, #frm-show-fields' ) );
146 if ( action.length ) {
147 jQuery( '#wpcontent' ).on( 'click', function() {
148 if ( jQuery( '.multiselect-container.frm-dropdown-menu' ).is( ':visible' ) ) {
149 jQuery( event.currentTarget ).removeClass( 'open' );
150 }
151 });
152 }
153 },
154 onChange: function( element, option ) {
155 $select.trigger( 'frm-multiselect-changed', element, option );
156 }
157 });
158 }
159 };
160
161 const bootstrap = {
162 setupBootstrapDropdowns( callback ) {
163 if ( ! window.bootstrap || ! window.bootstrap.Dropdown ) {
164 return;
165 }
166
167 window.bootstrap.Dropdown._getParentFromElement = getParentFromElement;
168 window.bootstrap.Dropdown.prototype._getParentFromElement = getParentFromElement;
169
170 function getParentFromElement( element ) {
171 let parent;
172 const selector = window.bootstrap.Util.getSelectorFromElement( element );
173
174 if ( selector ) {
175 parent = document.querySelector( selector );
176 }
177
178 const result = parent || element.parentNode;
179 const frmDropdownMenu = result.querySelector( '.frm-dropdown-menu' );
180
181 if ( ! frmDropdownMenu ) {
182 // Not a formidable dropdown, treat like Bootstrap does normally.
183 return result;
184 }
185
186 // Temporarily add dropdown-menu class so bootstrap can initialize.
187 frmDropdownMenu.classList.add( 'dropdown-menu' );
188 setTimeout(
189 function() {
190 frmDropdownMenu.classList.remove( 'dropdown-menu' );
191 },
192 0
193 );
194
195 if ( 'function' === typeof callback ) {
196 callback( frmDropdownMenu );
197 }
198
199 return result;
200 }
201 },
202 multiselect
203 };
204
205 const autocomplete = {
206 initSelectionAutocomplete: function() {
207 if ( jQuery.fn.autocomplete ) {
208 autocomplete.initAutocomplete( 'page' );
209 autocomplete.initAutocomplete( 'user' );
210 }
211 },
212 /**
213 * Init autocomplete.
214 *
215 * @since 4.10.01 Add container param to init autocomplete elements inside an element.
216 *
217 * @param {String} type Type of data. Accepts `page` or `user`.
218 * @param {String|Object} container Container class or element. Default is null.
219 */
220 initAutocomplete: function( type, container ) {
221 const basedUrlParams = '?action=frm_' + type + '_search&nonce=' + frmGlobal.nonce;
222 const elements = ! container ? jQuery( '.frm-' + type + '-search' ) : jQuery( container ).find( '.frm-' + type + '-search' );
223
224 elements.each( initAutocompleteForElement );
225
226 function initAutocompleteForElement() {
227 let urlParams = basedUrlParams;
228 const element = jQuery( this );
229
230 // Check if a custom post type is specific.
231 if ( element.attr( 'data-post-type' ) ) {
232 urlParams += '&post_type=' + element.attr( 'data-post-type' );
233 }
234
235 element.autocomplete({
236 delay: 100,
237 minLength: 0,
238 source: ajaxurl + urlParams,
239 change: autocomplete.selectBlank,
240 select: autocomplete.completeSelectFromResults,
241 focus: () => false,
242 position: {
243 my: 'left top',
244 at: 'left bottom',
245 collision: 'flip'
246 },
247 response: function( event, ui ) {
248 if ( ! ui.content.length ) {
249 const noResult = {
250 value: '',
251 label: frm_admin_js.no_items_found
252 };
253 ui.content.push( noResult );
254 }
255 },
256 create: function() {
257 let $container = jQuery( this ).parent();
258
259 if ( $container.length === 0 ) {
260 $container = 'body';
261 }
262
263 jQuery( this ).autocomplete( 'option', 'appendTo', $container );
264 }
265 })
266 .on( 'focus', function() {
267 // Show options on click to make it work more like a dropdown.
268 if ( this.value === '' || this.nextElementSibling.value < 1 ) {
269 jQuery( this ).autocomplete( 'search', this.value );
270 }
271 });
272 }
273 },
274
275 selectBlank: function( e, ui ) {
276 if ( ui.item === null ) {
277 this.nextElementSibling.value = '';
278 }
279 },
280
281 completeSelectFromResults: function( e, ui ) {
282 e.preventDefault();
283 this.value = ui.item.value === '' ? '' : ui.item.label;
284 this.nextElementSibling.value = ui.item.value;
285 }
286 };
287
288 const search = {
289 wrapInput: ( searchInput, labelText ) => {
290 const label = tag(
291 'label',
292 {
293 className: 'screen-reader-text',
294 text: labelText
295 }
296 );
297 label.setAttribute( 'for', searchInput.id );
298 return tag(
299 'p',
300 {
301 className: 'frm-search',
302 children: [
303 label,
304 span({ className: 'frmfont frm_search_icon' }),
305 searchInput
306 ]
307 }
308 );
309 },
310 newSearchInput: ( id, placeholder, targetClassName, args = {}) => {
311 const input = getAutoSearchInput( id, placeholder );
312 const wrappedSearch = search.wrapInput( input, placeholder );
313 search.init( input, targetClassName, args );
314
315 function getAutoSearchInput( id, placeholder ) {
316 const className = 'frm-search-input frm-auto-search';
317 const inputArgs = { id, className };
318 const input = tag( 'input', inputArgs );
319 input.setAttribute( 'placeholder', placeholder );
320 return input;
321 }
322
323 return wrappedSearch;
324 },
325 init: ( input, targetClassName, { handleSearchResult } = {}) => {
326 input.setAttribute( 'type', 'search' );
327 input.setAttribute( 'autocomplete', 'off' );
328
329 input.addEventListener( 'input', handleSearch );
330 input.addEventListener( 'search', handleSearch );
331 input.addEventListener( 'change', handleSearch );
332
333 function handleSearch() {
334 const searchText = input.value.toLowerCase();
335 const notEmptySearchText = searchText !== '';
336 const items = Array.from( document.getElementsByClassName( targetClassName ) );
337
338 let foundSomething = false;
339 items.forEach( toggleSearchClassesForItem );
340 if ( 'function' === typeof handleSearchResult ) {
341 handleSearchResult({ foundSomething, notEmptySearchText });
342 }
343
344 function toggleSearchClassesForItem( item ) {
345 let itemText;
346
347 if ( item.hasAttribute( 'frm-search-text' ) ) {
348 itemText = item.getAttribute( 'frm-search-text' );
349 } else {
350 itemText = item.innerText.toLowerCase();
351 item.setAttribute( 'frm-search-text', itemText );
352 }
353
354 const hide = notEmptySearchText && -1 === itemText.indexOf( searchText );
355 item.classList.toggle( 'frm_hidden', hide );
356
357 const isSearchResult = ! hide && notEmptySearchText;
358 if ( isSearchResult ) {
359 foundSomething = true;
360 }
361 item.classList.toggle( 'frm-search-result', isSearchResult );
362 }
363 }
364 }
365 };
366
367 const util = {
368 debounce: ( func, wait = 100 ) => {
369 let timeout;
370 return function( ...args ) {
371 clearTimeout( timeout );
372 timeout = setTimeout(
373 () => func.apply( this, args ),
374 wait
375 );
376 };
377 },
378 onClickPreventDefault: ( element, callback ) => {
379 const listener = event => {
380 event.preventDefault();
381 callback( event );
382 };
383 element.addEventListener( 'click', listener );
384 }
385 };
386
387 function getModalHelper( modal, appendTo ) {
388 return function( child, uniqueClassName ) {
389 let element = modal.querySelector( '.' + uniqueClassName );
390 if ( null === element ) {
391 element = div({
392 child: child,
393 className: uniqueClassName
394 });
395 appendTo.appendChild( element );
396 } else {
397 redraw( element, child );
398 }
399 };
400 }
401
402 function createEmptyModal( id ) {
403 const modal = div({ id, className: 'frm-modal' });
404 const postbox = div({ className: 'postbox' });
405 const metaboxHolder = div({ className: 'metabox-holder', child: postbox });
406 modal.appendChild( metaboxHolder );
407 document.body.appendChild( modal );
408 return modal;
409 }
410
411 function makeModalIntoADialogAndOpen( modal, { width } = {}) {
412 const $modal = jQuery( modal );
413 if ( ! $modal.hasClass( 'frm-dialog' ) ) {
414 $modal.dialog({
415 dialogClass: 'frm-dialog',
416 modal: true,
417 autoOpen: false,
418 closeOnEscape: true,
419 width: width || '550px',
420 resizable: false,
421 draggable: false,
422 open: function() {
423 jQuery( '.ui-dialog-titlebar' ).addClass( 'frm_hidden' ).removeClass( 'ui-helper-clearfix' );
424 jQuery( '#wpwrap' ).addClass( 'frm_overlay' );
425 jQuery( '.frm-dialog' ).removeClass( 'ui-widget ui-widget-content ui-corner-all' );
426
427 modal.classList.remove( 'ui-dialog-content', 'ui-widget-content' );
428
429 $modal.on( 'click', 'a.dismiss', function( event ) {
430 event.preventDefault();
431 $modal.dialog( 'close' );
432 });
433
434 const overlay = document.querySelector( '.ui-widget-overlay' );
435 if ( overlay ) {
436 overlay.addEventListener(
437 'click',
438 function( event ) {
439 event.preventDefault();
440 $modal.dialog( 'close' );
441 }
442 );
443 }
444 },
445 close: function() {
446 document.body.style.overflowY = 'initial';
447 jQuery( '#wpwrap' ).removeClass( 'frm_overlay' );
448 jQuery( '.spinner' ).css( 'visibility', 'hidden' );
449 }
450 });
451 }
452
453 document.body.style.overflowY = 'hidden';
454 $modal.dialog( 'open' );
455 return $modal;
456 }
457
458 function div( args ) {
459 return tag( 'div', args );
460 }
461
462 function span( args ) {
463 return tag( 'span', args );
464 }
465
466 function a( args = {}) {
467 const anchor = tag( 'a', args );
468 anchor.setAttribute( 'href', 'string' === typeof args.href ? args.href : '#' );
469 if ( 'string' === typeof args.target ) {
470 anchor.target = args.target;
471 }
472 return anchor;
473 }
474
475 function img( args = {}) {
476 const output = tag( 'img', args );
477 if ( 'string' === typeof args.src ) {
478 output.setAttribute( 'src', args.src );
479 }
480 return output;
481 }
482
483 function tag( type, args = {}) {
484 const output = document.createElement( type );
485
486 if ( 'string' === typeof args ) {
487 // Support passing just a string to a tag for simple text elements.
488 output.textContent = args;
489 return output;
490 }
491
492 const { id, className, children, child, text } = args;
493
494 if ( id ) {
495 output.id = id;
496 }
497 if ( className ) {
498 output.className = className;
499 }
500 if ( children ) {
501 children.forEach( child => output.appendChild( child ) );
502 } else if ( child ) {
503 output.appendChild( child );
504 } else if ( text ) {
505 output.textContent = text;
506 }
507 return output;
508 }
509
510 function svg({ href } = {}) {
511 const namespace = 'http://www.w3.org/2000/svg';
512 const output = document.createElementNS( namespace, 'svg' );
513 if ( href ) {
514 const use = document.createElementNS( namespace, 'use' );
515 use.setAttribute( 'href', href );
516 output.appendChild( use );
517 output.classList.add( 'frmsvg' );
518 }
519 return output;
520 }
521
522 function setAttributes( element, attrs ) {
523 Object.entries( attrs ).forEach(
524 ([ key, value ]) => element.setAttribute( key, value )
525 );
526 }
527
528 function redraw( element, child ) {
529 element.innerHTML = '';
530 element.appendChild( child );
531 }
532
533 frmDom = { tag, div, span, a, img, svg, setAttributes, modal, ajax, bootstrap, autocomplete, search, util };
534 }() );
535