PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.27
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.27
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 / embed.js

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

512 lines 13.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 ( function() {
2 /** globals ajaxurl, wp, frmDom */
3
4 if ( 'undefined' === typeof ajaxurl || 'undefined' === typeof wp || 'undefined' === typeof frmDom ) {
5 return;
6 }
7
8 const { __, sprintf } = wp.i18n;
9 const { div, tag, svg } = frmDom;
10 const { maybeCreateModal, footerButton } = frmDom.modal;
11
12 let autoId = 0;
13 let modal;
14
15 const state = {
16 type: 'form',
17 objectId: 0,
18 objectKey: ''
19 };
20
21 initEmbedModal();
22
23 function initEmbedModal() {
24 document.addEventListener( 'click', listenForEmbedClick );
25 }
26
27 function listenForEmbedClick( event ) {
28 let clicked = false;
29
30 const element = event.target;
31 const tag = element.tagName.toLowerCase();
32
33 switch ( tag ) {
34 case 'a':
35 clicked = 'frm-embed-action' === element.id || element.classList.contains( 'frm-embed-form' );
36
37 if ( clicked ) {
38 state.type = 'form';
39 } else {
40 clicked = element.classList.contains( 'frm-embed-view' );
41 if ( clicked ) {
42 state.type = 'view';
43 }
44 }
45 break;
46
47 case 'svg':
48 case 'use':
49 clicked = null !== element.closest( '.frm-embed-form' );
50 if ( clicked ) {
51 state.type = 'form';
52 }
53 break;
54 }
55
56 if ( clicked ) {
57 event.preventDefault();
58
59 const hookName = 'frm_before_embed_modal';
60 const initialIds = [ 0, 0 ];
61 const hookArgs = {
62 element,
63 type: state.type
64 };
65 const [ objectId, objectKey ] = wp.hooks.applyFilters( hookName, initialIds, hookArgs );
66
67 if ( objectId && objectKey ) {
68 state.objectId = objectId;
69 state.objectKey = objectKey;
70 openEmbedModal();
71 }
72 }
73 }
74
75 function openEmbedModal() {
76 /* translators: %s type: ie form, view. */
77 const title = sprintf( __( 'Embed %s', 'formidable' ), getTypeDescription() );
78
79 modal = maybeCreateModal(
80 'frm_embed_modal',
81 {
82 title,
83 content: getModalOptions(),
84 footer: getModalFooter()
85 }
86 );
87 modal.classList.add( 'frm_common_modal' );
88 modal.classList.remove( 'frm-on-page-2' );
89
90 const $modal = jQuery( modal );
91 offsetModalY( $modal, '50px' );
92 $modal.parent().addClass( 'frm-embed-modal-wrapper' );
93 }
94
95 function getModalFooter() {
96 const doneButton = footerButton( {
97 text: __( 'Done', 'formidable' ),
98 buttonType: 'primary'
99 } );
100
101 const cancelButton = footerButton( {
102 text: __( 'Back', 'formidable' ),
103 buttonType: 'cancel'
104 } );
105 cancelButton.addEventListener(
106 'click',
107 function( event ) {
108 event.preventDefault();
109 openEmbedModal();
110 }
111 );
112
113 return div( {
114 children: [ doneButton, cancelButton ]
115 } );
116 }
117
118 function offsetModalY( $modal, amount ) {
119 const position = {
120 my: 'top',
121 at: 'top+' + amount,
122 of: window
123 };
124 $modal.dialog( 'option', 'position', position );
125 }
126
127 function getModalOptions() {
128 const content = div( { className: 'frm-embed-modal-content frm_wrap' } );
129 const typeDescription = getTypeDescription();
130
131 /* translators: %s type: ie form, view. */
132 const existingPageDescription = sprintf( __( 'Embed your %s into an existing page.', 'formidable' ), typeDescription );
133
134 /* translators: %s type: ie form, view. */
135 const newPageDescription = sprintf( __( 'Put your %s on a newly created page.', 'formidable' ), typeDescription );
136
137 /* translators: %s type: ie form, view. */
138 const insertManuallyDescription = sprintf( __( 'Use shortcodes or PHP code to put the %s anywhere.', 'formidable' ), typeDescription );
139
140 const options = [
141 {
142 icon: '#frm_file_icon',
143 label: __( 'Select existing page', 'formidable' ),
144 description: existingPageDescription,
145 callback: () => {
146 content.innerHTML = '';
147
148 const spinner = tag( 'span' );
149 spinner.className = 'frm-wait frm_spinner';
150 spinner.style.visibility = 'visible';
151 content.append( spinner );
152
153 const gap = div();
154 gap.style.height = '20px';
155 content.append( gap );
156
157 content.classList.add( 'frm-loading-page-options' );
158
159 jQuery.ajax( {
160 type: 'POST',
161 url: ajaxurl,
162 data: {
163 action: 'get_page_dropdown',
164 nonce: frmGlobal.nonce
165 },
166 dataType: 'json',
167 success: addExistingPageDropdown
168 } );
169
170 function addExistingPageDropdown( response ) {
171 if ( 'object' !== typeof response || 'string' !== typeof response.html ) {
172 return;
173 }
174
175 content.classList.remove( 'frm-loading-page-options' );
176 content.innerHTML = '';
177
178 const typeDescription = getTypeDescription();
179
180 /* translators: %s: type (ie. view, form). */
181 const titleText = sprintf( __( 'Select the page you want to embed your %s into.', 'formidable' ), typeDescription );
182
183 const title = getLabel( titleText );
184 title.setAttribute( 'for', 'frm_page_dropdown' );
185 content.append( title );
186
187 let editPageUrl;
188
189 const doneButton = modal.querySelector( '.frm_modal_footer .button-primary' );
190 doneButton.classList.remove( 'dismiss' );
191 /* translators: %s: type (ie. view, form). */
192 doneButton.textContent = sprintf( __( 'Insert %s', 'formidable' ), typeDescription );
193 doneButton.addEventListener( 'click', redirectToExistingPageWithInjectedShortcode );
194
195 const dropdownWrapper = div();
196 dropdownWrapper.innerHTML = response.html;
197 content.append( dropdownWrapper );
198
199 if ( 'form' === state.type ) {
200 editPageUrl = response.edit_page_url + '&frmForm=' + state.objectId;
201 } else {
202 const hookName = 'frm_embed_edit_page_url';
203 const hookArgs = {
204 objectId: state.objectId,
205 type: state.type
206 };
207 editPageUrl = wp.hooks.applyFilters( hookName, response.edit_page_url, hookArgs );
208 }
209
210 frmDom.autocomplete.initAutocomplete( 'page', dropdownWrapper );
211
212 // Prevent cutoff issue with the autocomplete dropdown.
213 // This also adds visible overflow even if autocomplete is not activated, but that's fine for this page.
214 modal.querySelector( '.postbox' ).style.overflow = 'visible';
215
216 function redirectToExistingPageWithInjectedShortcode( event ) {
217 event.preventDefault();
218
219 const pageDropdown = modal.querySelector( '[name="frm_page_dropdown"]' );
220 modal.querySelectorAll( '.frm_error_style' ).forEach( error => error.remove() );
221
222 const pageId = pageDropdown.value;
223
224 if ( '0' === pageId || '' === pageId ) {
225 const error = div( { className: 'frm_error_style' } );
226 error.setAttribute( 'role', 'alert' );
227 error.textContent = __( 'Please select a page', 'formidable' );
228 content.insertBefore( error, title.nextElementSibling );
229 return;
230 }
231
232 window.location.href = editPageUrl.replace( 'post=0', 'post=' + pageId );
233 }
234 }
235 }
236 },
237 {
238 icon: '#frm_plus_icon',
239 label: __( 'Create new page', 'formidable' ),
240 description: newPageDescription,
241 callback: () => {
242 content.innerHTML = '';
243
244 const wrapper = div( { className: 'field-group' } );
245 const form = tag( 'form' );
246
247 const createPageWithShortcode = () => {
248 const hookName = 'frm_create_page_with_shortcode_data';
249 const data = wp.hooks.applyFilters(
250 hookName,
251 {
252 action: 'frm_create_page_with_shortcode',
253 object_id: state.objectId,
254 type: state.type,
255 name: input.value,
256 nonce: frmGlobal.nonce
257 }
258 );
259 jQuery.ajax( {
260 type: 'POST',
261 url: ajaxurl,
262 data,
263 dataType: 'json',
264 success: function( response ) {
265 if ( 'object' === typeof response && 'string' === typeof response.redirect ) {
266 window.location.href = response.redirect;
267 }
268 }
269 } );
270 };
271
272 form.addEventListener(
273 'submit',
274 function( event ) {
275 event.preventDefault();
276 createPageWithShortcode();
277 return false;
278 },
279 true
280 );
281
282 const title = getLabel( __( 'What will you call the new page?', 'formidable' ) );
283 title.setAttribute( 'for', 'frm_name_your_page' );
284 form.append( title );
285
286 const input = tag( 'input' );
287 input.id = 'frm_name_your_page';
288 input.placeholder = __( 'Name your page', 'formidable' );
289 form.append( input );
290
291 wrapper.append( form );
292 content.append( wrapper );
293
294 input.type = 'text';
295 input.focus();
296
297 const doneButton = modal.querySelector( '.frm_modal_footer .button-primary' );
298 doneButton.textContent = __( 'Create page', 'formidable' );
299 doneButton.addEventListener(
300 'click',
301 function( event ) {
302 event.preventDefault();
303 createPageWithShortcode();
304 }
305 );
306 }
307 },
308 {
309 icon: '#frm_code_icon',
310 label: __( 'Insert manually', 'formidable' ),
311 description: insertManuallyDescription,
312 callback: () => {
313 content.innerHTML = '';
314
315 if ( 'form' === state.type ) {
316 getEmbedFormManualExamples().forEach( example => content.append( getEmbedExample( example ) ) );
317 } else {
318 const hookName = 'frm_embed_examples';
319 const hookArgs = {
320 type: state.type,
321 objectId: state.objectId,
322 objectKey: state.objectKey
323 };
324 wp.hooks.applyFilters( hookName, [], hookArgs ).forEach(
325 example => content.append( getEmbedExample( example ) )
326 );
327 }
328 }
329 }
330 ];
331
332 options.forEach(
333 option => content.append( getModalOption( option ) )
334 );
335
336 return content;
337 }
338
339 function getTypeDescription() {
340 switch ( state.type ) {
341 case 'view':
342 return __( 'view', 'formidable' );
343 case 'form':
344 default:
345 return __( 'form', 'formidable' );
346 }
347 }
348
349 function getModalOption( { icon, label, description, callback } ) {
350 const output = div();
351 output.append( wrapModalOptionIcon( icon ) );
352 output.className = 'frm-embed-modal-option';
353 output.setAttribute( 'tabindex', 0 );
354 output.setAttribute( 'role', 'button' );
355
356 const textWrapper = div();
357 textWrapper.append( getLabel( label ) );
358 textWrapper.append( div( description ) );
359 output.append( textWrapper );
360
361 output.append( div( { className: 'caret' } ) );
362
363 output.addEventListener(
364 'click',
365 function() {
366 modal.classList.add( 'frm-on-page-2' );
367 callback();
368 }
369 );
370 return output;
371 }
372
373 function wrapModalOptionIcon( iconHref ) {
374 return div( {
375 className: 'frm-icon-wrapper',
376 child: svg( { href: iconHref } )
377 } );
378 }
379
380 function getEmbedFormManualExamples() {
381 const formId = state.objectId;
382 const formKey = state.objectKey;
383
384 let examples = [
385 {
386 label: __( 'WordPress shortcode', 'formidable' ),
387 example: '[formidable id=' + formId + ']',
388 link: 'https://formidableforms.com/knowledgebase/publish-a-form/#kb-insert-the-shortcode-manually',
389 linkLabel: __( 'How to use shortcodes in WordPress', 'formidable' )
390 },
391 {
392 label: __( 'Use PHP code', 'formidable' ),
393 example: '<?php echo FrmFormsController::get_form_shortcode( array( \'id\' => ' + formId + ' ) ); ?>'
394 }
395 ];
396
397 const filterArgs = { formId, formKey };
398 examples = wp.hooks.applyFilters( 'frmEmbedFormExamples', examples, filterArgs );
399
400 return examples;
401 }
402
403 function getEmbedExample( { label, example, link, linkLabel } ) {
404 const unique = getAutoId();
405
406 const labelElement = getLabel( label );
407 labelElement.id = 'frm_embed_example_label_' + unique;
408
409 const element = div();
410 element.append( labelElement );
411
412 let exampleElement;
413 if ( example.length > 80 ) {
414 exampleElement = tag( 'textarea' );
415 } else {
416 exampleElement = tag( 'input' );
417 exampleElement.type = 'text';
418 }
419
420 exampleElement.id = 'frm_embed_example_' + unique;
421 exampleElement.className = 'frm_embed_example';
422 exampleElement.value = example;
423 exampleElement.readOnly = true;
424 exampleElement.setAttribute( 'tabindex', -1 );
425
426 if ( 'undefined' !== typeof link && 'undefined' !== typeof linkLabel ) {
427 const linkElement = tag( 'a' );
428 linkElement.href = link;
429 linkElement.textContent = linkLabel;
430 linkElement.setAttribute( 'target', '_blank' );
431 element.append( linkElement );
432 }
433
434 element.append( exampleElement );
435 element.append( getCopyIcon( label ) );
436
437 return element;
438 }
439
440 function getLabel( text ) {
441 const args = { text };
442 return tag( 'label', args );
443 }
444
445 function getCopyIcon( label ) {
446 const icon = svg( { href: '#frm_clone_icon' } );
447 icon.id = 'frm_copy_embed_' + getAutoId();
448 icon.setAttribute( 'tabindex', 0 );
449 icon.setAttribute( 'role', 'button' );
450 /* translators: %s: Example type (ie. WordPress shortcode, API Form script) */
451 icon.setAttribute( 'aria-label', sprintf( __( 'Copy %s', 'formidable' ), label ) );
452 icon.addEventListener(
453 'click',
454 () => copyExampleToClipboard( icon.parentNode.querySelector( '.frm_embed_example' ) )
455 );
456 return icon;
457 }
458
459 function copyExampleToClipboard( example ) {
460 if ( navigator.clipboard ) {
461 navigator.clipboard.writeText( example.value ).then( handleCopySuccess );
462 return;
463 }
464
465 let copySuccess;
466
467 example.focus();
468 example.select();
469 example.setSelectionRange( 0, 99999 );
470
471 try {
472 copySuccess = document.execCommand( 'copy' );
473 } catch ( error ) {
474 copySuccess = false;
475 }
476
477 if ( copySuccess ) {
478 handleCopySuccess();
479 }
480
481 return copySuccess;
482 }
483
484 function handleCopySuccess() {
485 speak( __( 'Successfully copied embed example', 'formidable' ) );
486 }
487
488 function speak( message ) {
489 const element = document.createElement( 'div' );
490 const id = 'speak-' + getAutoId();
491
492 element.setAttribute( 'aria-live', 'assertive' );
493 element.setAttribute( 'id', id );
494 element.className = 'frm_screen_reader frm_hidden';
495 element.textContent = message;
496 document.body.append( element );
497
498 setTimeout( () => element.remove(), 1000 );
499 }
500
501 /**
502 * Get a unique id that automatically increments with every function call.
503 * Can be used for any UI that requires a unique id.
504 * Not to be used in data.
505 *
506 * @return {number} The unique id.
507 */
508 function getAutoId() {
509 return ++autoId;
510 }
511 }() );
512