From 4a8a9c826c7d3291e39666accbd4ffcfd296c07e Mon Sep 17 00:00:00 2001 From: Sherv Date: Mon, 18 Aug 2025 15:50:29 +0300 Subject: [PATCH 01/14] Refactor admin JavaScript to use webpack build system --- .eslintignore | 1 + js/formidable_admin.js | 11287 +-------------------------------------- js/src/admin/admin.js | 11286 ++++++++++++++++++++++++++++++++++++++ webpack.config.js | 3 +- 4 files changed, 11290 insertions(+), 11287 deletions(-) create mode 100644 js/src/admin/admin.js diff --git a/.eslintignore b/.eslintignore index e919e75fd9..81814aca91 100644 --- a/.eslintignore +++ b/.eslintignore @@ -6,6 +6,7 @@ **/formidable_dashboard.js **/formidable-templates.js **/formidable_styles.js +js/formidable_admin.js **/node_modules/** **/vendor/** diff --git a/js/formidable_admin.js b/js/formidable_admin.js index c24709330c..3272fe5217 100644 --- a/js/formidable_admin.js +++ b/js/formidable_admin.js @@ -1,11286 +1 @@ -/* exported frm_add_logic_row, frm_remove_tag, frm_show_div, frmCheckAll, frmCheckAllLevel */ -/* eslint-disable jsdoc/require-param, prefer-const, no-redeclare, @wordpress/no-unused-vars-before-return, jsdoc/check-types, jsdoc/check-tag-names, @wordpress/i18n-translator-comments, @wordpress/valid-sprintf, jsdoc/require-returns-description, jsdoc/require-param-type, no-unused-expressions, compat/compat */ - -window.FrmFormsConnect = window.FrmFormsConnect || ( function( document, window, $ ) { - - /*global jQuery:false, frm_admin_js, frmGlobal, ajaxurl */ - - const el = { - messageBox: null, - reset: null, - - setElements: function() { - el.messageBox = document.querySelector( '.frm_pro_license_msg' ); - el.reset = document.getElementById( 'frm_reconnect_link' ); - } - }; - - /** - * Public functions and properties. - * - * @since 4.03 - * - * @type {Object} - */ - const app = { - - /** - * Register connect button event. - * - * @since 4.03 - */ - init: function() { - el.setElements(); - - $( document.getElementById( 'frm_deauthorize_link' ) ).on( 'click', app.deauthorize ); - $( '.frm_authorize_link' ).on( 'click', app.authorize ); - // Handles FF dashboard Authorize & Reauthorize events. - // Attach click event to parent as #frm_deauthorize_link & #frm_reconnect_link dynamically recreated by bootstrap.setupBootstrapDropdowns in dom.js - $( '.frm-dashboard-license-options' ).on( 'click', '#frm_deauthorize_link', app.deauthorize ); - $( '.frm-dashboard-license-options' ).on( 'click', '#frm_reconnect_link', app.reauthorize ); - - if ( el.reset !== null ) { - $( el.reset ).on( 'click', app.reauthorize ); - } - }, - - /* Manual license authorization */ - authorize: function() { - /*jshint validthis:true */ - const button = this; - const pluginSlug = this.getAttribute( 'data-plugin' ); - const input = document.getElementById( 'edd_' + pluginSlug + '_license_key' ); - const license = input.value; - let wpmu = document.getElementById( 'proplug-wpmu' ); - this.classList.add( 'frm_loading_button' ); - if ( wpmu === null ) { - wpmu = 0; - } else if ( wpmu.checked ) { - wpmu = 1; - } else { - wpmu = 0; - } - - $.ajax({ - type: 'POST', url: ajaxurl, dataType: 'json', - data: { - action: 'frm_addon_activate', - license: license, - plugin: pluginSlug, - wpmu: wpmu, - nonce: frmGlobal.nonce - }, - success: function( msg ) { - app.afterAuthorize( msg, input ); - button.classList.remove( 'frm_loading_button' ); - } - }); - }, - - afterAuthorize: function( msg, input ) { - if ( msg.success === true ) { - input.value = '•••••••••••••••••••'; - } - - wp.hooks.doAction( 'frm_after_authorize', msg ); - app.showMessage( msg ); - }, - - showProgress: function( msg ) { - if ( el.messageBox === null ) { - // In case the message box was added after page load. - el.setElements(); - } - - const messageBox = el.messageBox; - if ( messageBox === null ) { - return; - } - - if ( msg.success === true ) { - messageBox.classList.remove( 'frm_error_style' ); - messageBox.classList.add( 'frm_message', 'frm_updated_message' ); - } else { - messageBox.classList.add( 'frm_error_style' ); - messageBox.classList.remove( 'frm_message', 'frm_updated_message' ); - } - messageBox.classList.remove( 'frm_hidden' ); - messageBox.innerHTML = msg.message; - }, - - showMessage: function( msg ) { - if ( el.messageBox === null ) { - // In case the message box was added after page load. - el.setElements(); - } - const messageBox = el.messageBox; - - if ( msg.success === true ) { - app.showAuthorized( true ); - app.showInlineSuccess(); - - /** - * Triggers the after license is authorized action for a confirmation/success modal. - * @param {Object} msg An object containing message data received from Authorize request. - */ - wp.hooks.doAction( 'frmAdmin.afterLicenseAuthorizeSuccess', { msg }); - } - app.showProgress( msg ); - - if ( msg.message !== '' ) { - setTimeout( function() { - messageBox.innerHTML = ''; - messageBox.classList.add( 'frm_hidden' ); - messageBox.classList.remove( 'frm_error_style', 'frm_message', 'frm_updated_message' ); - }, 10000 ); - const refreshPage = document.querySelector( '.frm-admin-page-dashboard' ); - if ( refreshPage ) { - setTimeout( function() { - window.location.reload(); - }, 1000 ); - } - } - }, - - showAuthorized: function( show ) { - const from = show ? 'unauthorized' : 'authorized'; - const to = show ? 'authorized' : 'unauthorized'; - const container = document.querySelectorAll( '.frm_' + from + '_box' ); - if ( container.length ) { - // Replace all authorized boxes with unauthorized boxes. - container.forEach( function( box ) { - box.className = box.className.replace( 'frm_' + from + '_box', 'frm_' + to + '_box' ); - }); - } - }, - - /** - * Use the data-success element to replace the element content. - */ - showInlineSuccess: function() { - const successElement = document.querySelectorAll( '.frm-confirm-msg [data-success]' ); - if ( successElement.length ) { - successElement.forEach( function( element ) { - element.innerHTML = frmAdminBuild.purifyHtml( element.getAttribute( 'data-success' ) ); - }); - } - }, - - /* Clear the site license cache */ - reauthorize: function() { - /*jshint validthis:true */ - this.innerHTML = ''; - - $.ajax({ - type: 'POST', - url: ajaxurl, - dataType: 'json', - data: { - action: 'frm_reset_cache', - plugin: 'formidable_pro', - nonce: frmGlobal.nonce - }, - success: function( msg ) { - el.reset.textContent = msg.message; - if ( el.reset.getAttribute( 'data-refresh' ) === '1' ) { - window.location.reload(); - } - } - }); - return false; - }, - - deauthorize: function() { - /*jshint validthis:true */ - if ( ! confirm( frmGlobal.deauthorize ) ) { - return false; - } - const pluginSlug = this.getAttribute( 'data-plugin' ), - input = document.getElementById( 'edd_' + pluginSlug + '_license_key' ), - license = input.value, - link = this; - - this.innerHTML = ''; - - $.ajax({ - type: 'POST', - url: ajaxurl, - data: { - action: 'frm_addon_deactivate', - license: license, - plugin: pluginSlug, - nonce: frmGlobal.nonce - }, - success: function() { - app.showAuthorized( false ); - input.value = ''; - link.replaceWith( 'Disconnected' ); - - /** - * Triggers the after license is deauthorized sruccess action. - */ - wp.hooks.doAction( 'frmAdmin.afterLicenseDeauthorizeSuccess', {}); - - } - }); - return false; - } - }; - - // Provide access to public functions/properties. - return app; - -}( document, window, jQuery ) ); - -function frmAdminBuildJS() { - //'use strict'; - - /*global jQuery:false, frm_admin_js, frmGlobal, ajaxurl, fromDom */ - - const frmAdminJs = frm_admin_js; // eslint-disable-line camelcase - const { tag, div, span, a, svg, img } = frmDom; - const { onClickPreventDefault } = frmDom.util; - const { doJsonFetch, doJsonPost } = frmDom.ajax; - frmAdminJs.contextualShortcodes = getContextualShortcodes(); - const icons = { - save: svg({ href: '#frm_save_icon' }), - drag: svg({ href: '#frm_drag_icon', classList: [ 'frm_drag_icon', 'frm-drag' ] }) - }; - - let $newFields = jQuery( document.getElementById( 'frm-show-fields' ) ), - builderForm = document.getElementById( 'new_fields' ), - thisForm = document.getElementById( 'form_id' ), - copyHelper = false, - fieldsUpdated = 0, - thisFormId = 0, - autoId = 0, - optionMap = {}, - lastNewActionIdReturned = 0; - - const { __, sprintf } = wp.i18n; - let debouncedSyncAfterDragAndDrop, postBodyContent, $postBodyContent; - - const dragState = { - dragging: false - }; - - if ( thisForm !== null ) { - thisFormId = thisForm.value; - } - - const currentURL = new URL( window.location.href ); - const urlParams = currentURL.searchParams; - const builderPage = document.getElementById( 'frm_builder_page' ); - - // Global settings - let s; - - function showElement( element ) { - if ( ! element[0]) { - return; - } - element[0].style.display = ''; - } - - function empty( $obj ) { - if ( $obj !== null ) { - while ( $obj.firstChild ) { - $obj.removeChild( $obj.firstChild ); - } - } - } - - function addClass( $obj, className ) { - if ( $obj.classList ) { - $obj.classList.add( className ); - } else { - $obj.className += ' ' + className; - } - } - - function confirmClick( e ) { - /*jshint validthis:true */ - e.stopPropagation(); - e.preventDefault(); - confirmLinkClick( this ); - } - - function confirmLinkClick( link ) { - const message = link.getAttribute( 'data-frmverify' ), - loadedFrom = link.getAttribute( 'data-loaded-from' ) ; - - if ( message === null || link.id === 'frm-confirmed-click' ) { - return true; - } - - if ( 'entries-list' === loadedFrom ) { - return wp.hooks.applyFilters( 'frm_on_multiple_entries_delete', { link, initModal }); - } - - return confirmModal( link ); - } - - function confirmModal( link ) { - let verify, $confirmMessage, i, dataAtts, btnClass, - $info = initModal( '#frm_confirm_modal', '400px' ), - continueButton = document.getElementById( 'frm-confirmed-click' ); - - if ( $info === false ) { - return false; - } - - verify = link.getAttribute( 'data-frmverify' ); - btnClass = verify ? link.getAttribute( 'data-frmverify-btn' ) : ''; - $confirmMessage = jQuery( '.frm-confirm-msg' ); - $confirmMessage.empty(); - - if ( verify ) { - $confirmMessage.append( document.createTextNode( verify ) ); - if ( btnClass ) { - continueButton.classList.add( btnClass ); - } - } - - removeAtts = continueButton.dataset; - for ( i in dataAtts ) { - continueButton.removeAttribute( 'data-' + i ); - } - - dataAtts = link.dataset; - for ( i in dataAtts ) { - if ( i !== 'frmverify' ) { - continueButton.setAttribute( 'data-' + i, dataAtts[i]); - } - } - - /** - * Triggers the pre-open action for a confirmation modal. This action passes - * relevant modal information and associated link to any listening hooks. - * - * @param {Object} options An object containing modal elements and data. - * @param {HTMLElement} options.$info The HTML element containing modal information. - * @param {string} options.link The link associated with the modal action. - */ - wp.hooks.doAction( 'frmAdmin.beforeOpenConfirmModal', { $info, link }); - - $info.dialog( 'open' ); - continueButton.setAttribute( 'href', link.getAttribute( 'href' ) || link.getAttribute( 'data-href' ) ); - return false; - } - - function infoModal( msg ) { - const $info = initModal( '#frm_info_modal', '400px' ); - - if ( $info === false ) { - return false; - } - - jQuery( '.frm-info-msg' ).html( msg ); - - $info.dialog( 'open' ); - return false; - } - - function toggleItem( e ) { - /*jshint validthis:true */ - const toggle = this.getAttribute( 'data-frmtoggle' ); - const text = this.getAttribute( 'data-toggletext' ); - const $items = jQuery( toggle ); - - e.preventDefault(); - - $items.toggle(); - - if ( text !== null && text !== '' ) { - this.setAttribute( 'data-toggletext', this.innerHTML ); - this.textContent = text; - } - - return false; - } - - /** - * Toggle a class on target elements when an anchor is clicked, or when a radio or checkbox has been selected. - * - * @param {Event} e Event with either the change or click type. - * @returns {false} - */ - function hideShowItem( e ) { - /*jshint validthis:true */ - let hide = this.getAttribute( 'data-frmhide' ); - let show = this.getAttribute( 'data-frmshow' ); - let uncheckList = this.getAttribute( 'data-frmuncheck' ); - let uncheckListArray = uncheckList ? uncheckList.split( ',' ) : []; - - // Flip unchecked checkboxes so an off value undoes the on value. - if ( isUncheckedCheckbox( this ) ) { - if ( hide !== null ) { - show = hide; - hide = null; - } else if ( show !== null ) { - hide = show; - show = null; - } - } - - e.preventDefault(); - - const toggleClass = this.getAttribute( 'data-toggleclass' ) || 'frm_hidden'; - - if ( hide !== null ) { - jQuery( hide ).addClass( toggleClass ); - } - - if ( show !== null ) { - jQuery( show ).removeClass( toggleClass ); - } - - const current = this.parentNode.querySelectorAll( 'a.current' ); - if ( current !== null ) { - for ( let i = 0; i < current.length; i++ ) { - current[ i ].classList.remove( 'current' ); - } - this.classList.add( 'current' ); - } - - if ( uncheckListArray.length ) { - uncheckListArray.forEach( function( uncheckItem ) { - const uncheckItemElement = document.querySelector( uncheckItem ); - if ( uncheckItemElement ) { - uncheckItemElement.checked = false; - } - }); - } - - return false; - } - - function isUncheckedCheckbox( element ) { - return 'INPUT' === element.nodeName && 'checkbox' === element.type && ! element.checked; - } - - function loadTooltips() { - let wrapClass = jQuery( '.wrap, .frm_wrap' ), - confirmModal = document.getElementById( 'frm_confirm_modal' ), - doAction = false, - confirmedBulkDelete = false; - - jQuery( confirmModal ).on( 'click', '[data-deletefield]', deleteFieldConfirmed ); - jQuery( confirmModal ).on( 'click', '[data-removeid]', removeThisTag ); - jQuery( confirmModal ).on( 'click', '[data-trashtemplate]', trashTemplate ); - - wrapClass.on( 'click', '.frm_remove_tag, .frm_remove_form_action', removeThisTag ); - wrapClass.on( 'click', 'a[data-frmverify]', confirmClick ); - wrapClass.on( 'click', 'a[data-frmtoggle]', toggleItem ); - wrapClass.on( 'click', 'a[data-frmhide], a[data-frmshow]', hideShowItem ); - wrapClass.on( 'change', 'input[data-frmhide], input[data-frmshow]', hideShowItem ); - wrapClass.on( 'click', '.widget-top,a.widget-action', clickWidget ); - - wrapClass.on( 'mouseenter.frm', '.frm_bstooltip, .frm_help', function() { - jQuery( this ).off( 'mouseenter.frm' ); - - jQuery( '.frm_bstooltip, .frm_help' ).tooltip(); - jQuery( this ).tooltip( 'show' ); - }); - - jQuery( '.frm_bstooltip, .frm_help' ).tooltip( ); - - jQuery( document ).on( 'click', '#doaction, #doaction2', function( event ) { - const isTop = this.id === 'doaction', - suffix = isTop ? 'top' : 'bottom', - bulkActionSelector = document.getElementById( 'bulk-action-selector-' + suffix ), - confirmBulkDelete = document.getElementById( 'confirm-bulk-delete-' + suffix ); - - if ( bulkActionSelector !== null && confirmBulkDelete !== null ) { - doAction = this; - - if ( ! confirmedBulkDelete && bulkActionSelector.value === 'bulk_delete' ) { - event.preventDefault(); - confirmLinkClick( confirmBulkDelete ); - return false; - } - } else { - doAction = false; - } - }); - - jQuery( document ).on( 'click', '#frm-confirmed-click', function( event ) { - if ( doAction === false || event.target.classList.contains( 'frm-btn-inactive' ) ) { - return; - } - - if ( this.getAttribute( 'href' ) === 'confirm-bulk-delete' ) { - event.preventDefault(); - confirmedBulkDelete = true; - doAction.click(); - return false; - } - }); - } - - function deleteTooltips() { - document.querySelectorAll( '.tooltip' ).forEach( - function( tooltip ) { - tooltip.remove(); - } - ); - } - - function removeThisTag() { - /*jshint validthis:true */ - let show, hide, removeMore; - - if ( parseInt( this.getAttribute( 'data-skip-frm-js' ) ) || confirmLinkClick( this ) === false ) { - return; - } - - const deleteButton = jQuery( this ); - const id = deleteButton.attr( 'data-removeid' ); - - show = deleteButton.attr( 'data-showlast' ); - if ( typeof show === 'undefined' ) { - show = ''; - } - - hide = deleteButton.attr( 'data-hidelast' ); - if ( typeof hide === 'undefined' ) { - hide = ''; - } - - removeMore = deleteButton.attr( 'data-removemore' ); - - if ( show !== '' ) { - if ( deleteButton.closest( '.frm_add_remove' ).find( '.frm_remove_tag:visible' ).length > 1 ) { - show = ''; - hide = ''; - } - } else if ( id.indexOf( 'frm_postmeta_' ) === 0 ) { - if ( jQuery( '#frm_postmeta_rows .frm_postmeta_row' ).length < 2 ) { - show = '.frm_add_postmeta_row.button'; - } - if ( jQuery( '.frm_toggle_cf_opts' ).length && jQuery( '#frm_postmeta_rows .frm_postmeta_row:not(#' + id + ')' ).last().length ) { - if ( show !== '' ) { - show += ','; - } - show += '#' + jQuery( '#frm_postmeta_rows .frm_postmeta_row:not(#' + id + ')' ).last().attr( 'id' ) + ' .frm_toggle_cf_opts'; - } - } - - const $fadeEle = jQuery( document.getElementById( id ) ); - $fadeEle.fadeOut( 400, function() { - $fadeEle.remove(); - fieldUpdated(); - - if ( hide !== '' ) { - jQuery( hide ).hide(); - } - - if ( show !== '' ) { - jQuery( show + ' a,' + show ).removeClass( 'frm_hidden' ).fadeIn( 'slow' ); - } - - if ( this.closest( '.frm_form_action_settings' ) ) { - const type = this.closest( '.frm_form_action_settings' ).querySelector( '.frm_action_name' ).value; - afterActionRemoved( type ); - } - document.querySelector( '.tooltip' )?.remove(); - }); - - if ( typeof removeMore !== 'undefined' ) { - removeMore = jQuery( removeMore ); - removeMore.fadeOut( 400, function() { - removeMore.remove(); - }); - } - - if ( show !== '' ) { - jQuery( this ).closest( '.frm_logic_rows' ).fadeOut( 'slow' ); - } - - return false; - } - - function afterActionRemoved( type ) { - checkActiveAction( type ); - - const hookName = 'frm_after_action_removed'; - const hookArgs = { type }; - wp.hooks.doAction( hookName, hookArgs ); - } - - function clickWidget( event, b ) { - /*jshint validthis:true */ - if ( typeof b === 'undefined' ) { - b = this; - } - - popCalcFields( b, false ); - - const cont = jQuery( b ).closest( '.frm_form_action_settings' ); - const target = event.target; - - if ( cont.length && typeof target !== 'undefined' ) { - const className = target.parentElement.className; - if ( 'string' === typeof className ) { - if ( className.indexOf( 'frm_email_icons' ) > -1 || className.indexOf( 'frm_toggle' ) > -1 ) { - // clicking on delete icon shouldn't open it - event.stopPropagation(); - return; - } - } - } - - let inside = cont.children( '.widget-inside' ); - - if ( cont.length && inside.find( 'p, div, table' ).length < 1 ) { - const actionId = cont.find( 'input[name$="[ID]"]' ).val(); - const actionType = cont.find( 'input[name$="[post_excerpt]"]' ).val(); - if ( actionType ) { - inside.html( '' ); - cont.find( '.spinner' ).fadeIn( 'slow' ); - jQuery.ajax({ - type: 'POST', - url: ajaxurl, - data: { - action: 'frm_form_action_fill', - action_id: actionId, - action_type: actionType, - nonce: frmGlobal.nonce - }, - success: function( html ) { - inside.html( html ); - initiateMultiselect(); - showInputIcon( '#' + cont.attr( 'id' ) ); - initAutocomplete( inside ); - jQuery( b ).trigger( 'frm-action-loaded' ); - - /** - * Fires after filling form action content when opening. - * - * @since 5.5.4 - * - * @param {Object} insideElement JQuery object of form action inside element. - */ - wp.hooks.doAction( 'frm_filled_form_action', inside ); - } - }); - } - } - - jQuery( b ).closest( '.frm_field_box' ).siblings().find( '.widget-inside' ).slideUp( 'fast' ); - if ( ( typeof b.className !== 'undefined' && b.className.indexOf( 'widget-action' ) !== -1 ) || jQuery( b ).closest( '.start_divider' ).length < 1 ) { - return; - } - - inside = jQuery( b ).closest( 'div.widget' ).children( '.widget-inside' ); - if ( inside.is( ':hidden' ) ) { - inside.slideDown( 'fast' ); - } else { - inside.slideUp( 'fast' ); - } - } - - function clickNewTab() { - /*jshint validthis:true */ - const t = this.getAttribute( 'href' ); - if ( typeof t === 'undefined' ) { - return false; - } - - const c = t.replace( '#', '.' ); - const $link = jQuery( this ); - - $link.closest( 'li' ).addClass( 'frm-tabs active' ).siblings( 'li' ).removeClass( 'frm-tabs active starttab' ); - $link.closest( 'div' ).children( '.tabs-panel' ).not( t ).not( c ).hide(); - - const tabContent = document.getElementById( t.replace( '#', '' ) ); - if ( tabContent ) { - tabContent.style.display = 'block'; - } - - // clearSettingsBox would hide field settings when opening the fields modal and we want to skip it there. - if ( this.id === 'frm_insert_fields_tab' && ! this.closest( '#frm_adv_info' ) ) { - clearSettingsBox(); - } - return false; - } - - function clickTab( link, auto ) { - link = jQuery( link ); - const t = link.attr( 'href' ); - if ( typeof t === 'undefined' ) { - return; - } - - const c = t.replace( '#', '.' ); - - link.closest( 'li' ).addClass( 'frm-tabs active' ).siblings( 'li' ).removeClass( 'frm-tabs active starttab' ); - if ( link.closest( 'div' ).find( '.tabs-panel' ).length ) { - link.closest( 'div' ).children( '.tabs-panel' ).not( t ).not( c ).hide(); - } else if ( document.getElementById( 'form_global_settings' ) !== null ) { - /* global settings */ - const ajax = link.data( 'frmajax' ); - link.closest( '.frm_wrap' ).find( '.tabs-panel, .hide_with_tabs' ).hide(); - if ( typeof ajax !== 'undefined' && ajax == '1' ) { - loadSettingsTab( t ); - } - } else { - /* form settings page */ - jQuery( '#frm-categorydiv .tabs-panel, .hide_with_tabs' ).hide(); - } - jQuery( t ).show(); - jQuery( c ).show(); - - hideShortcodes(); - - if ( auto !== 'auto' ) { - // Hide success message on tab change. - jQuery( '.frm_updated_message' ).hide(); - jQuery( '.frm_warning_style' ).hide(); - } - - if ( jQuery( link ).closest( '#frm_adv_info' ).length ) { - return; - } - - if ( jQuery( '.frm_form_settings' ).length ) { - jQuery( '.frm_form_settings' ).attr( 'action', '?page=formidable&frm_action=settings&id=' + jQuery( '.frm_form_settings input[name="id"]' ).val() + '&t=' + t.replace( '#', '' ) ); - } else { - jQuery( '.frm_settings_form' ).attr( 'action', '?page=formidable-settings&t=' + t.replace( '#', '' ) ); - } - } - - function setupSortable( sortableSelector ) { - document.querySelectorAll( sortableSelector ).forEach( - list => { - makeDroppable( list ); - Array.from( list.children ).forEach( child => makeDraggable( child, '.frm-move' ) ); - - const $sectionTitle = jQuery( list ).children( '[data-type="divider"]' ).children( '.divider_section_only' ); - if ( $sectionTitle.length ) { - makeDroppable( $sectionTitle ); - } - } - ); - setupFieldOptionSorting( jQuery( '#frm_builder_page' ) ); - } - - function makeDroppable( list ) { - jQuery( list ).droppable({ - accept: '.frmbutton, li.frm_field_box', - deactivate: handleFieldDrop, - over: onDragOverDroppable, - out: onDraggableLeavesDroppable, - tolerance: 'pointer' - }); - } - - function onDragOverDroppable( event, ui ) { - const droppable = getDroppableForOnDragOver( event.target ); - const draggable = ui.draggable[0]; - - if ( ! allowDrop( draggable, droppable, event ) ) { - droppable.classList.remove( 'frm-over-droppable' ); - jQuery( droppable ).parents( 'ul.frm_sorting' ).addClass( 'frm-over-droppable' ); - return; - } - - document.querySelectorAll( '.frm-over-droppable' ).forEach( droppable => droppable.classList.remove( 'frm-over-droppable' ) ); - droppable.classList.add( 'frm-over-droppable' ); - jQuery( droppable ).parents( 'ul.frm_sorting' ).addClass( 'frm-over-droppable' ); - } - - /** - * Maybe change the droppable. - * Section titles are made droppable, but are not a list, so we need to change the droppable to the section's list instead. - * - * @param {Element} droppable - * @returns {Element} - */ - function getDroppableForOnDragOver( droppable ) { - if ( droppable.classList.contains( 'divider_section_only' ) ) { - droppable = jQuery( droppable ).nextAll( '.start_divider.frm_sorting' ).get( 0 ); - } - return droppable; - } - - function onDraggableLeavesDroppable( event ) { - const droppable = event.target; - droppable.classList.remove( 'frm-over-droppable' ); - } - - function makeDraggable( draggable, handle ) { - const settings = { - helper: getDraggableHelper, - revert: 'invalid', - delay: 10, - start: handleDragStart, - stop: handleDragStop, - drag: handleDrag, - cursor: 'grabbing', - refreshPositions: true, - cursorAt: { - top: 0, - left: 90 // The width of draggable button is 180. 90 should center the draggable on the cursor. - } - }; - if ( 'string' === typeof handle ) { - settings.handle = handle; - } - jQuery( draggable ).draggable( settings ); - } - - function getDraggableHelper( event ) { - const draggable = event.delegateTarget; - - if ( isFieldGroup( draggable ) ) { - const newTextFieldClone = document.getElementById( 'frm-insert-fields' ).querySelector( '.frm_ttext' ).cloneNode( true ); - newTextFieldClone.querySelector( 'use' ).setAttributeNS( 'http://www.w3.org/1999/xlink', 'href', '#frm_field_group_layout_icon' ); - newTextFieldClone.querySelector( 'span' ).textContent = __( 'Field Group', 'formidable' ); - newTextFieldClone.classList.add( 'frm_field_box' ); - newTextFieldClone.classList.add( 'ui-sortable-helper' ); - return newTextFieldClone; - } - - let copyTarget; - const isNewField = draggable.classList.contains( 'frmbutton' ); - if ( isNewField ) { - copyTarget = draggable.cloneNode( true ); - copyTarget.classList.add( 'ui-sortable-helper' ); - draggable.classList.add( 'frm-new-field' ); - return copyTarget; - } - - if ( draggable.hasAttribute( 'data-ftype' ) ) { - const fieldType = draggable.getAttribute( 'data-ftype' ); - copyTarget = document.getElementById( 'frm-insert-fields' ).querySelector( '.frm_t' + fieldType ); - copyTarget = copyTarget.cloneNode( true ); - copyTarget.classList.add( 'form-field' ); - - copyTarget.classList.add( 'ui-sortable-helper' ); - - if ( copyTarget ) { - return copyTarget.cloneNode( true ); - } - } - - return div({ className: 'frmbutton' }); - } - - function handleDragStart( event, ui ) { - dragState.dragging = true; - - const container = postBodyContent; - container.classList.add( 'frm-dragging-field' ); - - document.body.classList.add( 'frm-dragging' ); - ui.helper.addClass( 'frm-sortable-helper' ); - ui.helper.initialOffset = container.scrollTop; - - event.target.classList.add( 'frm-drag-fade' ); - - unselectFieldGroups(); - deleteEmptyDividerWrappers(); - maybeRemoveGroupHoverTarget(); - closeOpenFieldDropdowns(); - deleteTooltips(); - } - - function handleDragStop() { - const container = postBodyContent; - container.classList.remove( 'frm-dragging-field' ); - document.body.classList.remove( 'frm-dragging' ); - - const fade = document.querySelector( '.frm-drag-fade' ); - if ( fade ) { - fade.classList.remove( 'frm-drag-fade' ); - } - } - - function handleDrag( event, ui ) { - maybeScrollBuilder( event ); - const draggable = event.target; - const droppable = getDroppableTarget(); - - let placeholder = document.getElementById( 'frm_drag_placeholder' ); - if ( ! allowDrop( draggable, droppable, event ) ) { - if ( placeholder ) { - placeholder.remove(); - } - return; - } - - if ( ! placeholder ) { - placeholder = tag( 'li', { - id: 'frm_drag_placeholder', - className: 'sortable-placeholder' - }); - } - const frmSortableHelper = ui.helper.get( 0 ); - if ( frmSortableHelper.classList.contains( 'form-field' ) || frmSortableHelper.classList.contains( 'frm_field_box' ) ) { - // Sync the y position of the draggable so it still follows the cursor after scrolling up and down the field list. - frmSortableHelper.style.transform = 'translateY(' + getDragOffset( ui.helper ) + 'px)'; - } - - if ( 'frm-show-fields' === droppable.id || droppable.classList.contains( 'start_divider' ) ) { - placeholder.style.left = 0; - handleDragOverYAxis({ droppable, y: event.clientY, placeholder }); - return; - } - - placeholder.style.top = ''; - handleDragOverFieldGroup({ droppable, x: event.clientX, placeholder }); - } - - function maybeScrollBuilder( event ) { - $postBodyContent.scrollTop( - ( _, v ) => { - const moved = event.clientY; - const h = postBodyContent.offsetHeight; - const relativePos = event.clientY - postBodyContent.offsetTop; - const y = relativePos - h / 2; - - if ( relativePos > ( h - 50 ) && moved > 5 ) { - // Scrolling down. - return v + y * 0.1; - } - - if ( relativePos < 70 && moved < 130 ) { - // Scrolling up. - return v - Math.abs( y * 0.1 ); - } - - return v; - } - ); - } - - function getDragOffset( $helper ) { - return postBodyContent.scrollTop - $helper.initialOffset; - } - - function getDroppableTarget() { - let droppable = document.getElementById( 'frm-show-fields' ); - while ( droppable.querySelector( '.frm-over-droppable' ) ) { - droppable = droppable.querySelector( '.frm-over-droppable' ); - } - if ( 'frm-show-fields' === droppable.id && ! droppable.classList.contains( 'frm-over-droppable' ) ) { - droppable = false; - } - return droppable; - } - - function handleFieldDrop( _, ui ) { - if ( ! dragState.dragging ) { - // dragState.dragging is set to true on drag start. - // The deactivate event gets called for every droppable. This check to make sure it happens once. - return; - } - - dragState.dragging = false; - - const draggable = ui.draggable[0]; - const placeholder = document.getElementById( 'frm_drag_placeholder' ); - - if ( ! placeholder ) { - ui.helper.remove(); - debouncedSyncAfterDragAndDrop(); - return; - } - - maybeOpenCollapsedPage( placeholder ); - - const $previousFieldContainer = ui.helper.parent(); - const previousSection = ui.helper.get( 0 ).closest( 'ul.start_divider' ); - const newSection = placeholder.closest( 'ul.frm_sorting' ); - - if ( draggable.classList.contains( 'frm-new-field' ) ) { - insertNewFieldByDragging( draggable.id ); - } else { - moveFieldThatAlreadyExists( draggable, placeholder ); - } - - const previousSectionId = previousSection ? parseInt( previousSection.closest( '.edit_field_type_divider' ).getAttribute( 'data-fid' ) ) : 0; - const newSectionId = newSection.classList.contains( 'start_divider' ) ? parseInt( newSection.closest( '.edit_field_type_divider' ).getAttribute( 'data-fid' ) ) : 0; - - placeholder.remove(); - ui.helper.remove(); - - const $previousContainerFields = $previousFieldContainer.length ? getFieldsInRow( $previousFieldContainer ) : []; - maybeUpdatePreviousFieldContainerAfterDrop( $previousFieldContainer, $previousContainerFields ); - maybeUpdateDraggableClassAfterDrop( draggable, $previousContainerFields ); - - if ( previousSectionId !== newSectionId ) { - updateFieldAfterMovingBetweenSections( jQuery( draggable ), previousSection ); - } - - debouncedSyncAfterDragAndDrop(); - } - - /** - * If a page if collapsed, expand it before dragging since only the page break will move. - * - * @param {Element} placeholder - * @returns {void} - */ - function maybeOpenCollapsedPage( placeholder ) { - if ( ! placeholder.previousElementSibling || ! placeholder.previousElementSibling.classList.contains( 'frm-is-collapsed' ) ) { - return; - } - - const $pageBreakField = jQuery( placeholder ).prevUntil( '[data-type="break"]' ); - if ( ! $pageBreakField.length ) { - return; - } - - const collapseButton = $pageBreakField.find( '.frm-collapse-page' ).get( 0 ); - if ( collapseButton ) { - collapseButton.click(); - } - } - - function maybeUpdatePreviousFieldContainerAfterDrop( $previousFieldContainer, $previousContainerFields ) { - if ( ! $previousFieldContainer.length ) { - return; - } - - if ( $previousContainerFields.length ) { - syncLayoutClasses( $previousContainerFields.first() ); - } else { - maybeDeleteAnEmptyFieldGroup( $previousFieldContainer.get( 0 ) ); - } - } - - function maybeUpdateDraggableClassAfterDrop( draggable, $previousContainerFields ) { - if ( 0 !== $previousContainerFields.length || 1 !== getFieldsInRow( jQuery( draggable.parentNode ) ).length ) { - syncLayoutClasses( jQuery( draggable ) ); - } - } - - /** - * Remove an empty field group, but don't remove an empty section. - * - * @param {Element} previousFieldContainer - * @returns {void} - */ - function maybeDeleteAnEmptyFieldGroup( previousFieldContainer ) { - const closestFieldBox = previousFieldContainer.closest( 'li.frm_field_box' ); - if ( closestFieldBox && ! closestFieldBox.classList.contains( 'edit_field_type_divider' ) ) { - closestFieldBox.remove(); - } - } - - function handleDragOverYAxis({ droppable, y, placeholder }) { - const $list = jQuery( droppable ); - - let top; - - $children = $list.children().not( '.edit_field_type_end_divider' ); - if ( 0 === $children.length ) { - $list.prepend( placeholder ); - top = 0; - } else { - const insertAtIndex = determineIndexBasedOffOfMousePositionInList( $list, y ); - - if ( insertAtIndex === $children.length ) { - const $lastChild = jQuery( $children.get( insertAtIndex - 1 ) ); - top = $lastChild.offset().top + $lastChild.outerHeight(); - $list.append( placeholder ); - - // Make sure nothing gets inserted after the end divider. - const $endDivider = $list.children( '.edit_field_type_end_divider' ); - if ( $endDivider.length ) { - $list.append( $endDivider ); - } - } else { - top = jQuery( $children.get( insertAtIndex ) ).offset().top; - jQuery( $children.get( insertAtIndex ) ).before( placeholder ); - } - } - - top -= $list.offset().top; - placeholder.style.top = top + 'px'; - } - - function determineIndexBasedOffOfMousePositionInList( $list, y ) { - const $items = $list.children().not( '.edit_field_type_end_divider' ); - const length = $items.length; - - let index, item, itemTop, returnIndex; - - if ( ! document.querySelector( '.frm-has-fields .frm_no_fields' ) ) { - // Always return 0 when there are no fields. - return 0; - } - - returnIndex = 0; - for ( index = length - 1; index >= 0; --index ) { - item = $items.get( index ); - itemTop = jQuery( item ).offset().top; - if ( y > itemTop ) { - returnIndex = index; - if ( y > itemTop + ( jQuery( item ).outerHeight() / 2 ) ) { - returnIndex = index + 1; - } - break; - } - } - - return returnIndex; - } - - function handleDragOverFieldGroup({ droppable, x, placeholder }) { - const $row = jQuery( droppable ); - const $children = getFieldsInRow( $row ); - - if ( ! $children.length ) { - return; - } - - let left; - const insertAtIndex = determineIndexBasedOffOfMousePositionInRow( $row, x ); - - if ( insertAtIndex === $children.length ) { - const $lastChild = jQuery( $children.get( insertAtIndex - 1 ) ); - left = $lastChild.offset().left + $lastChild.outerWidth(); - $row.append( placeholder ); - } else { - left = jQuery( $children.get( insertAtIndex ) ).offset().left; - jQuery( $children.get( insertAtIndex ) ).before( placeholder ); - - const amountToOffsetLeftBy = 0 === insertAtIndex ? 4 : 8; // Offset by 8 in between rows, but only 4 for the first item in a group. - left -= amountToOffsetLeftBy; // Offset the placeholder slightly so it appears between two fields. - } - - left -= $row.offset().left; - - placeholder.style.left = left + 'px'; - } - - function syncAfterDragAndDrop() { - fixUnwrappedListItems(); - toggleSectionHolder(); - maybeFixEndDividers(); - maybeDeleteEmptyFieldGroups(); - updateFieldOrder(); - - const event = new Event( 'frm_sync_after_drag_and_drop', { bubbles: false }); - document.dispatchEvent( event ); - } - - function maybeFixEndDividers() { - document.querySelectorAll( '.edit_field_type_end_divider' ).forEach( - endDivider => endDivider.parentNode.appendChild( endDivider ) - ); - } - - function maybeDeleteEmptyFieldGroups() { - document.querySelectorAll( 'li.form_field_box:not(.form-field)' ).forEach( - fieldGroup => ! fieldGroup.children.length && fieldGroup.remove() - ); - } - - function fixUnwrappedListItems() { - const lists = document.querySelectorAll( 'ul#frm-show-fields, ul.start_divider' ); - lists.forEach( - list => { - list.childNodes.forEach( - child => { - if ( 'undefined' === typeof child.classList ) { - return; - } - - if ( child.classList.contains( 'edit_field_type_end_divider' ) ) { - // Never wrap end divider in place. - return; - } - - if ( 'undefined' !== typeof child.classList && child.classList.contains( 'form-field' ) ) { - wrapFieldLiInPlace( child ); - } - } - ); - } - ); - } - - function deleteEmptyDividerWrappers() { - const dividers = document.querySelectorAll( 'ul.start_divider' ); - if ( ! dividers.length ) { - return; - } - dividers.forEach( - function( divider ) { - const children = [].slice.call( divider.children ); - children.forEach( - function( child ) { - if ( 0 === child.children.length ) { - child.remove(); - } else if ( 1 === child.children.length && 'ul' === child.firstElementChild.nodeName.toLowerCase() && 0 === child.firstElementChild.children.length ) { - child.remove(); - } - } - ); - } - ); - } - - function getFieldsInRow( $row ) { - let $fields = jQuery(); - - const row = $row.get( 0 ); - if ( ! row.children ) { - return $fields; - } - - Array.from( row.children ).forEach( - child => { - if ( 'none' === child.style.display ) { - return; - } - - const classes = child.classList; - if ( ! classes.contains( 'form-field' ) || classes.contains( 'edit_field_type_end_divider' ) || classes.contains( 'frm-sortable-helper' ) ) { - return; - } - - $fields = $fields.add( child ); - } - ); - return $fields; - } - - function determineIndexBasedOffOfMousePositionInRow( $row, x ) { - let $inputs = getFieldsInRow( $row ), - length = $inputs.length, - index, input, inputLeft, returnIndex; - - returnIndex = 0; - for ( index = length - 1; index >= 0; --index ) { - input = $inputs.get( index ); - inputLeft = jQuery( input ).offset().left; - if ( x > inputLeft ) { - returnIndex = index; - if ( x > inputLeft + ( jQuery( input ).outerWidth() / 2 ) ) { - returnIndex = index + 1; - } - break; - } - } - - return returnIndex; - } - - function syncLayoutClasses( $item, type ) { - let $fields, size, layoutClasses, classToAddFunction; - - if ( 'undefined' === typeof type ) { - type = 'even'; - } - - $fields = $item.parent().children( 'li.form-field, li.frmbutton_loadingnow' ).not( '.edit_field_type_end_divider' ); - size = $fields.length; - layoutClasses = getLayoutClasses(); - - if ( 'even' === type && 5 !== size ) { - $fields.each( getSyncLayoutClass( layoutClasses, getEvenClassForSize( size ) ) ); - } else if ( 'clear' === type ) { - $fields.each( getSyncLayoutClass( layoutClasses, '' ) ); - } else { - if ( -1 !== [ 'left', 'right', 'middle', 'even' ].indexOf( type ) ) { - classToAddFunction = function( index ) { - return getClassForBlock( size, type, index ); - }; - } else { - classToAddFunction = function( index ) { - const size = type[ index ]; - return getLayoutClassForSize( size ); - }; - } - - $fields.each( getSyncLayoutClass( layoutClasses, classToAddFunction ) ); - } - - updateFieldGroupControls( $item.parent(), $fields.length ); - } - - function updateFieldGroupControls( $row, count ) { - let rowOffset, shouldShowControls, controls; - - rowOffset = $row.offset(); - - if ( 'undefined' === typeof rowOffset ) { - return; - } - - shouldShowControls = count >= 2; - - controls = document.getElementById( 'frm_field_group_controls' ); - if ( null === controls ) { - if ( ! shouldShowControls ) { - // exit early. if we do not need controls and they do not exist, do nothing. - return; - } - - controls = div(); - controls.id = 'frm_field_group_controls'; - controls.setAttribute( 'role', 'group' ); - controls.setAttribute( 'tabindex', 0 ); - setFieldControlsHtml( controls ); - builderPage.appendChild( controls ); - } - - $row.append( controls ); - controls.style.display = shouldShowControls ? 'block' : 'none'; - } - - function setFieldControlsHtml( controls ) { - let layoutOption, moveOption; - - layoutOption = document.createElement( 'span' ); - layoutOption.innerHTML = ''; - const layoutOptionLabel = __( 'Set Row Layout', 'formidable' ); - addTooltip( layoutOption, layoutOptionLabel ); - makeTabbable( layoutOption, layoutOptionLabel ); - - moveOption = document.createElement( 'span' ); - moveOption.innerHTML = ''; - moveOption.classList.add( 'frm-move' ); - const moveOptionLabel = __( 'Move Field Group', 'formidable' ); - addTooltip( moveOption, moveOptionLabel ); - makeTabbable( moveOption, moveOptionLabel ); - - controls.innerHTML = ''; - controls.appendChild( layoutOption ); - controls.appendChild( moveOption ); - controls.appendChild( getFieldControlsDropdown() ); - } - - function addTooltip( element, title ) { - element.setAttribute( 'data-toggle', 'tooltip' ); - element.setAttribute( 'data-container', 'body' ); - element.setAttribute( 'title', title ); - element.addEventListener( - 'mouseover', - function() { - if ( null === element.getAttribute( 'data-original-title' ) ) { - jQuery( element ).tooltip(); - } - } - ); - } - - function getFieldControlsDropdown() { - const dropdown = span({ className: 'dropdown' }); - const trigger = a({ - className: 'frm_bstooltip frm-hover-icon frm-dropdown-toggle dropdown-toggle', - children: [ - span({ - child: svg({ href: '#frm_thick_more_vert_icon' }) - }), - span({ - className: 'screen-reader-text', - text: __( 'Toggle More Options Dropdown', 'formidable' ) - }) - ] - }); - - frmDom.setAttributes( - trigger, - { - 'title': __( 'More Options', 'formidable' ), - 'data-toggle': 'dropdown', - 'data-container': 'body' - } - ); - makeTabbable( trigger, __( 'More Options', 'formidable' ) ); - dropdown.appendChild( trigger ); - - const ul = div({ - className: 'frm-dropdown-menu dropdown-menu dropdown-menu-right' - }); - ul.setAttribute( 'role', 'menu' ); - dropdown.appendChild( ul ); - - return dropdown; - } - - function getSyncLayoutClass( layoutClasses, classToAdd ) { - return function( itemIndex ) { - let currentClassToAdd, length, layoutClassIndex, currentClass, activeLayoutClass, fieldId, layoutClassesInput; - - currentClassToAdd = 'function' === typeof classToAdd ? classToAdd( itemIndex ) : classToAdd; - length = layoutClasses.length; - activeLayoutClass = false; - for ( layoutClassIndex = 0; layoutClassIndex < length; ++layoutClassIndex ) { - currentClass = layoutClasses[ layoutClassIndex ]; - if ( this.classList.contains( currentClass ) ) { - activeLayoutClass = currentClass; - break; - } - } - - fieldId = this.dataset.fid; - - if ( 'undefined' === typeof fieldId ) { - // we are syncing the drag/drop placeholder before the actual field has loaded. - // this will get called again afterward and the input will exist then. - this.classList.add( currentClassToAdd ); - return; - } - - moveFieldSettings( document.getElementById( 'frm-single-settings-' + fieldId ) ); - layoutClassesInput = document.getElementById( 'frm_classes_' + fieldId ); - - if ( null === layoutClassesInput ) { - // not every field type has a layout class input. - return; - } - - if ( false === activeLayoutClass ) { - if ( '' !== currentClassToAdd ) { - layoutClassesInput.value = layoutClassesInput.value.concat( ' ' + currentClassToAdd ); - } - } else { - this.classList.remove( activeLayoutClass ); - layoutClassesInput.value = layoutClassesInput.value.replace( activeLayoutClass, currentClassToAdd ); - } - - if ( this.classList.contains( 'frm_first' ) ) { - this.classList.remove( 'frm_first' ); - layoutClassesInput.value = layoutClassesInput.value.replace( 'frm_first', '' ).trim(); - } - - if ( 0 === itemIndex ) { - this.classList.add( 'frm_first' ); - layoutClassesInput.value = layoutClassesInput.value.concat( ' frm_first' ); - } - - jQuery( layoutClassesInput ).trigger( 'change' ); - }; - } - - function getLayoutClasses() { - return [ 'frm_full', 'frm_half', 'frm_third', 'frm_fourth', 'frm_sixth', 'frm_two_thirds', 'frm_three_fourths', 'frm1', 'frm2', 'frm3', 'frm4', 'frm5', 'frm6', 'frm7', 'frm8', 'frm9', 'frm10', 'frm11', 'frm12' ]; - } - - function setupFieldOptionSorting( sort ) { - const opts = { - items: '.frm_sortable_field_opts li', - axis: 'y', - opacity: 0.65, - forcePlaceholderSize: false, - handle: '.frm-drag', - helper: function( e, li ) { - copyHelper = li.clone().insertAfter( li ); - return li.clone(); - }, - stop: function( e, ui ) { - copyHelper && copyHelper.remove(); - const fieldId = ui.item.attr( 'id' ).replace( 'frm_delete_field_', '' ).replace( '-' + ui.item.data( 'optkey' ) + '_container', '' ); - resetDisplayedOpts( fieldId ); - fieldUpdated(); - } - }; - jQuery( sort ).sortable( opts ); - } - - // Get the section where a field is dropped - function getSectionForFieldPlacement( currentItem ) { - let section = ''; - if ( typeof currentItem !== 'undefined' && ! currentItem.hasClass( 'edit_field_type_divider' ) ) { - section = currentItem.closest( '.edit_field_type_divider' ); - } - return section; - } - - // Get the form ID where a field is dropped - function getFormIdForFieldPlacement( section ) { - let formId = ''; - - if ( typeof section[0] !== 'undefined' ) { - const sDivide = section.children( '.start_divider' ); - sDivide.children( '.edit_field_type_end_divider' ).appendTo( sDivide ); - if ( typeof section.attr( 'data-formid' ) !== 'undefined' ) { - const fieldId = section.attr( 'data-fid' ); - formId = jQuery( 'input[name="field_options[form_select_' + fieldId + ']"]' ).val(); - } - } - - if ( typeof formId === 'undefined' || formId === '' ) { - formId = thisFormId; - } - - return formId; - } - - // Get the section ID where a field is dropped - function getSectionIdForFieldPlacement( section ) { - let sectionId = 0; - if ( typeof section[0] !== 'undefined' ) { - sectionId = section.attr( 'id' ).replace( 'frm_field_id_', '' ); - } - - return sectionId; - } - - /** - * Update a field after it is dragged and dropped into, out of, or between sections - * - * @param {Object} currentItem - * @param {Object} previousSection - * @returns {void} - */ - function updateFieldAfterMovingBetweenSections( currentItem, previousSection ) { - if ( ! currentItem.hasClass( 'form-field' ) ) { - // currentItem is a field group. Call for children recursively. - getFieldsInRow( jQuery( currentItem.get( 0 ).firstChild ) ).each( - function() { - updateFieldAfterMovingBetweenSections( jQuery( this ), previousSection ); - } - ); - return; - } - - const fieldId = currentItem.attr( 'id' ).replace( 'frm_field_id_', '' ); - const section = getSectionForFieldPlacement( currentItem ); - const formId = getFormIdForFieldPlacement( section ); - const sectionId = getSectionIdForFieldPlacement( section ); - const previousFormId = previousSection ? getFormIdForFieldPlacement( jQuery( previousSection.parentNode ) ) : 0; - - jQuery.ajax({ - type: 'POST', - url: ajaxurl, - data: { - action: 'frm_update_field_after_move', - form_id: formId, - field: fieldId, - section_id: sectionId, - previous_form_id: previousFormId, - nonce: frmGlobal.nonce - }, - success: function() { - toggleSectionHolder(); - updateInSectionValue( fieldId, sectionId ); - } - }); - } - - // Update the in_section field value - function updateInSectionValue( fieldId, sectionId ) { - document.getElementById( 'frm_in_section_' + fieldId ).value = sectionId; - } - - /** - * Get the arguments for inserting a new field. - * - * @since 6.23 - * - * @param {string} fieldType - * @param {string} sectionId - * @param {string} formId - * @param {Number} hasBreak - * - * @returns {Object} - */ - function getInsertNewFieldArgs( fieldType, sectionId, formId, hasBreak ) { - return { - action: 'frm_insert_field', - form_id: formId, - field_type: fieldType, - section_id: sectionId, - nonce: frmGlobal.nonce, - has_break: hasBreak, - last_row_field_ids: getFieldIdsInSubmitRow() - }; - } - - /** - * Returns true if it's a range field type and slider type is not selected. - * - * @since 6.23 - * - * @param {string} fieldType - * @returns {boolean} - */ - function shouldStopInsertingField( fieldType ) { - return wp.hooks.applyFilters( 'frm_should_stop_inserting_field', false, fieldType ); - } - - /** - * Add a new field by dragging and dropping it from the Fields sidebar - * - * @param {string} fieldType - */ - function insertNewFieldByDragging( fieldType ) { - if ( shouldStopInsertingField( fieldType ) ) { - wp.hooks.doAction( 'frm_stopped_inserting_by_dragging', fieldType ); - return; - } - - const placeholder = document.getElementById( 'frm_drag_placeholder' ); - const loadingID = fieldType.replace( '|', '-' ) + '_' + getAutoId(); - const loading = tag( - 'li', - { - id: loadingID, - className: 'frm-wait frmbutton_loadingnow' - } - ); - const $placeholder = jQuery( loading ); - const currentItem = jQuery( placeholder ); - const section = getSectionForFieldPlacement( currentItem ); - const formId = getFormIdForFieldPlacement( section ); - const sectionId = getSectionIdForFieldPlacement( section ); - - placeholder.parentNode.insertBefore( loading, placeholder ); - placeholder.remove(); - syncLayoutClasses( $placeholder ); - - let hasBreak = 0; - if ( 'summary' === fieldType ) { - // see if we need to insert a page break before this newly-added summary field. Check for at least 1 page break - hasBreak = jQuery( '.frmbutton_loadingnow#' + loadingID ).prevAll( 'li[data-type="break"]' ).length ? 1 : 0; - } - - jQuery.ajax({ - type: 'POST', - url: ajaxurl, - data: getInsertNewFieldArgs( fieldType, sectionId, formId, hasBreak ), - success: function( msg ) { - handleInsertFieldByDraggingResponse( msg, $placeholder ); - - const fieldId = checkMsgForFieldId( msg ); - if ( fieldId ) { - /** - * Fires after a field is added. - * - * @since 6.23 - * - * @param {Object} fieldData The field data. - * @param {String} fieldData.field The field HTML. - * @param {String} fieldData.field_type The field type. - * @param {String} fieldData.form_id The form ID. - */ - wp.hooks.doAction( 'frm_after_field_added_in_form_builder', { - field: msg, - fieldId, - fieldType, - form_id: formId, - }); - } - }, - error: handleInsertFieldError - }); - } - - /** - * @param {String} msg - * @param {Object} $placeholder jQuery object. - */ - function handleInsertFieldByDraggingResponse( msg, $placeholder ) { - let replaceWith; - document.getElementById( 'frm_form_editor_container' ).classList.add( 'frm-has-fields' ); - const $siblings = $placeholder.siblings( 'li.form-field' ).not( '.edit_field_type_end_divider' ); - - if ( ! $siblings.length ) { - // if dragging into a new row, we need to wrap the li first. - replaceWith = wrapFieldLi( msg ); - } else { - replaceWith = msgAsjQueryObject( msg ); - if ( ! $placeholder.get( 0 ).parentNode.parentNode.classList.contains( 'ui-draggable' ) ) { - // If a field group wasn't draggable because it only had a single field, make it draggable. - makeDraggable( $placeholder.get( 0 ).parentNode.parentNode, '.frm-move' ); - } - } - $placeholder.replaceWith( replaceWith ); - updateFieldOrder(); - afterAddField( msg, false ); - if ( $siblings.length ) { - syncLayoutClasses( $siblings.first() ); - } - toggleSectionHolder(); - - if ( ! $siblings.length ) { - makeDroppable( replaceWith.get( 0 ).querySelector( 'ul.frm_sorting' ) ); - makeDraggable( replaceWith.get( 0 ).querySelector( 'li.form-field' ), '.frm-move' ); - } else { - makeDraggable( replaceWith.get( 0 ), '.frm-move' ); - } - } - - /** - * Get the field ID from the response message. - * - * @since 6.23 - * - * @param {String} msg - * @return {Number} - */ - function checkMsgForFieldId( msg ) { - const result = msg.match( /data-fid="(\d+)"/ ); - return result ? parseInt( result[1] ) : 0; - } - - function getFieldIdsInSubmitRow() { - const submitField = document.querySelector( '.edit_field_type_submit' ); - if ( ! submitField ) { - return []; - } - - const lastRowFields = submitField.parentNode.children; - const ids = []; - for ( let i = 0; i < lastRowFields.length; i++ ) { - ids.push( lastRowFields[ i ].dataset.fid ); - } - - return ids; - } - - function moveFieldThatAlreadyExists( draggable, placeholder ) { - placeholder.parentNode.insertBefore( draggable, placeholder ); - } - - function msgAsjQueryObject( msg ) { - const element = div(); - element.innerHTML = msg; - return jQuery( element.firstChild ); - } - - function handleInsertFieldError( jqXHR, _, errorThrown ) { - maybeShowInsertFieldError( errorThrown, jqXHR ); - } - - function maybeShowInsertFieldError( errorThrown, jqXHR ) { - if ( ! jqXHRAborted( jqXHR ) ) { - infoModal( errorThrown + '. Please try again.' ); - } - } - - function jqXHRAborted( jqXHR ) { - return jqXHR.status === 0 || jqXHR.readyState === 0; - } - - /** - * Get a unique id that automatically increments with every function call. - * Can be used for any UI that requires a unique id. - * Not to be used in data. - * - * @returns {integer} - */ - function getAutoId() { - return ++autoId; - } - - /** - * Determine if a draggable element can be droppable into a droppable element. - * - * Don't allow page break, embed form, or section inside section field - * Don't allow page breaks inside of field groups. - * Don't allow field groups with sections inside of sections. - * Don't allow field groups in field groups. - * Don't allow hidden fields inside of field groups but allow them in sections. - * Don't allow any fields below the submit button field. - * Don't allow submit button field above any fields. - * Don't allow GDPR fields in repeaters. - * - * @param {HTMLElement} draggable - * @param {HTMLElement} droppable - * @param {Event} event - * @returns {Boolean} - */ - function allowDrop( draggable, droppable, event ) { - if ( false === droppable ) { - // Don't show drop placeholder if dragging somewhere off of the droppable area. - return false; - } - - if ( droppable.closest( '.frm-sortable-helper' ) ) { - // Do not allow drop into draggable. - return false; - } - - const isSubmitBtn = draggable.classList.contains( 'edit_field_type_submit' ); - const containSubmitBtn = ! draggable.classList.contains( 'form_field' ) && !! draggable.querySelector( '.edit_field_type_submit' ); - - if ( 'frm-show-fields' === droppable.id ) { - const draggableIndex = determineIndexBasedOffOfMousePositionInList( jQuery( droppable ), event.clientY ); - - if ( isSubmitBtn || containSubmitBtn ) { - // Do not allow dropping submit button to above position. - const lastRowIndex = droppable.childElementCount - 1; - return draggableIndex > lastRowIndex; - } - - // Do not allow dropping other fields to below submit button. - const submitButtonIndex = jQuery( droppable.querySelector( '.edit_field_type_submit' ).closest( '#frm-show-fields > li' ) ).index(); - return draggableIndex <= submitButtonIndex; - } - - if ( isSubmitBtn ) { - if ( droppable.classList.contains( 'start_divider' ) ) { - // Don't allow dropping submit button into a repeater. - return false; - } - - if ( isLastRow( droppable.parentElement ) ) { - // Allow dropping submit button into the last row. - return true; - } - - if ( ! isLastRow( droppable.parentElement.nextElementSibling ) ) { - // Don't a dropping submit button into the row that isn't the second one from bottom. - return false; - } - - // Allow dropping submit button into the second row from bottom if there is only submit button in the last row. - return ! draggable.parentElement.querySelector( 'li.frm_field_box:not(.edit_field_type_submit)' ); - } - - if ( droppable.classList.contains( 'start_divider' ) && ( draggable.classList.contains( 'edit_field_type_gdpr' ) || draggable.id === 'gdpr' ) && droppable.closest( '.repeat_section' ) ) { - // Don't allow GDPR fields in repeaters. - return false; - } - - if ( ! droppable.classList.contains( 'start_divider' ) ) { - const $fieldsInRow = getFieldsInRow( jQuery( droppable ) ); - if ( ! groupCanFitAnotherField( $fieldsInRow, jQuery( draggable ) ) ) { - // Field group is full and cannot accept another field. - return false; - } - - if ( draggable.id === 'divider' && droppable.closest( '.start_divider' ) ) { - return false; - } - } - - const isNewField = draggable.classList.contains( 'frm-new-field' ); - if ( isNewField ) { - return allowNewFieldDrop( draggable, droppable ); - } - - return allowMoveField( draggable, droppable ); - } - - /** - * Checks if given element is the last row in form builder. - * - * @param {HTMLElement} element Element. - * @return {Boolean} - */ - function isLastRow( element ) { - return element && element.matches( '#frm-show-fields > li:last-child' ); - } - - // Don't allow a new page break or hidden field in a field group. - // Don't allow a new field into a field group that includes a page break or hidden field. - // Don't allow a new section inside of a section. - // Don't allow an embedded form in a section. - function allowNewFieldDrop( draggable, droppable ) { - const classes = draggable.classList; - const newPageBreakField = classes.contains( 'frm_tbreak' ); - const newHiddenField = classes.contains( 'frm_thidden' ); - const newSectionField = classes.contains( 'frm_tdivider' ); - const newEmbedField = classes.contains( 'frm_tform' ); - const newUserIdField = classes.contains( 'frm_tuser_id' ); - - const newFieldWillBeAddedToAGroup = ! ( 'frm-show-fields' === droppable.id || droppable.classList.contains( 'start_divider' ) ); - if ( newFieldWillBeAddedToAGroup ) { - if ( groupIncludesBreakOrHiddenOrUserId( droppable ) ) { - // Never allow any field beside a page break or a hidden field. - return false; - } - - return ! newHiddenField && ! newPageBreakField && ! newUserIdField; - } - - const fieldTypeIsAlwaysAllowed = ! newPageBreakField && ! newHiddenField && ! newSectionField && ! newEmbedField; - if ( fieldTypeIsAlwaysAllowed ) { - return true; - } - - const newFieldWillBeAddedToASection = droppable.classList.contains( 'start_divider' ) || null !== droppable.closest( '.start_divider' ); - if ( newFieldWillBeAddedToASection ) { - // Don't allow a section or an embedded form in a section. - return ! newEmbedField && ! newSectionField; - } - - return true; - } - - function allowMoveField( draggable, droppable ) { - if ( isFieldGroup( draggable ) ) { - return allowMoveFieldGroup( draggable, droppable ); - } - - const isPageBreak = draggable.classList.contains( 'edit_field_type_break' ); - if ( isPageBreak ) { - // Page breaks are only allowed in the main list of fields, not in sections or in field groups. - return false; - } - - if ( droppable.classList.contains( 'start_divider' ) ) { - return allowMoveFieldToSection( draggable ); - } - - const isHiddenField = draggable.classList.contains( 'edit_field_type_hidden' ); - const isUserIdField = draggable.classList.contains( 'edit_field_type_user_id' ); - if ( isHiddenField || isUserIdField ) { - // Hidden fields and user id fields should not be added to field groups since they're not shown - // and don't make sense with the grid distribution. - return false; - } - - return allowMoveFieldToGroup( draggable, droppable ); - } - - function isFieldGroup( draggable ) { - return draggable.classList.contains( 'frm_field_box' ) && ! draggable.classList.contains( 'form-field' ); - } - - function allowMoveFieldGroup( fieldGroup, droppable ) { - if ( droppable.classList.contains( 'start_divider' ) && null === fieldGroup.querySelector( '.start_divider' ) ) { - // Allow a field group with no section inside of a section. - return true; - } - return false; - } - - function allowMoveFieldToSection( draggable ) { - const draggableIncludeEmbedForm = draggable.classList.contains( 'edit_field_type_form' ) || draggable.querySelector( '.edit_field_type_form' ); - if ( draggableIncludeEmbedForm ) { - // Do not allow an embedded form inside of a section. - return false; - } - - const draggableIncludesSection = draggable.classList.contains( 'edit_field_type_divider' ) || draggable.querySelector( '.edit_field_type_divider' ); - if ( draggableIncludesSection ) { - // Do not allow a section inside of a section. - return false; - } - - return true; - } - - function allowMoveFieldToGroup( draggable, group ) { - if ( groupIncludesBreakOrHiddenOrUserId( group ) ) { - // Never allow any field beside a page break or a hidden field. - return false; - } - - const isFieldGroup = jQuery( draggable ).children( 'ul.frm_sorting' ).not( '.start_divider' ).length > 0; - if ( isFieldGroup ) { - // Do not allow a field group directly inside of a field group unless it's in a section. - return false; - } - - const draggableIncludesASection = draggable.classList.contains( 'edit_field_type_divider' ) || draggable.querySelector( '.edit_field_type_divider' ); - const draggableIsEmbedField = draggable.classList.contains( 'edit_field_type_form' ); - const groupIsInASection = null !== group.closest( '.start_divider' ); - if ( groupIsInASection && ( draggableIncludesASection || draggableIsEmbedField ) ) { - // Do not allow a section or an embed field inside of a section. - return false; - } - - return true; - } - - function groupIncludesBreakOrHiddenOrUserId( group ) { - return null !== group.querySelector( '.edit_field_type_break, .edit_field_type_hidden, .edit_field_type_user_id' ); - } - - function groupCanFitAnotherField( fieldsInRow, $field ) { - let fieldId; - if ( fieldsInRow.length < 6 ) { - return true; - } - if ( fieldsInRow.length > 6 ) { - return false; - } - fieldId = $field.attr( 'data-fid' ); - // allow 6 if we're not changing field groups. - return 1 === jQuery( fieldsInRow ).filter( '[data-fid="' + fieldId + '"]' ).length; - } - - function loadFields( fieldId ) { - const thisField = document.getElementById( fieldId ); - const $thisField = jQuery( thisField ); - const field = []; - const addHtmlToField = element => { - const frmHiddenFdata = element.querySelector( '.frm_hidden_fdata' ); - element.classList.add( 'frm_load_now' ); - if ( frmHiddenFdata !== null ) { - field.push( frmHiddenFdata.innerHTML ); - } - }; - - let nextElement = thisField; - addHtmlToField( nextElement ); - - let nextField = getNextField( nextElement ); - while ( nextField && field.length < 15 ) { - addHtmlToField( nextField ); - nextElement = nextField; - nextField = getNextField( nextField ); - } - - jQuery.ajax({ - type: 'POST', - url: ajaxurl, - data: { - action: 'frm_load_field', - field: field, - form_id: thisFormId, - nonce: frmGlobal.nonce - }, - success: html => handleAjaxLoadFieldSuccess( html, $thisField, field ) - }); - } - - function getNextField( field ) { - if ( field.nextElementSibling ) { - return field.nextElementSibling; - } - return field.parentNode?.closest( '.frm_field_box' )?.nextElementSibling?.querySelector( '.form-field' ); - } - - function handleAjaxLoadFieldSuccess( html, $thisField, field ) { - let key, $nextSet; - - html = html.replace( /^\s+|\s+$/g, '' ); - if ( html.indexOf( '{' ) !== 0 ) { - jQuery( '.frm_load_now' ).removeClass( '.frm_load_now' ).html( 'Error' ); - return; - } - - html = JSON.parse( html ); - for ( key in html ) { - jQuery( '#frm_field_id_' + key ).replaceWith( html[key]); - setupSortable( '#frm_field_id_' + key + '.edit_field_type_divider ul.frm_sorting' ); - makeDraggable( document.getElementById( 'frm_field_id_' + key ) ); - } - - $nextSet = $thisField.nextAll( '.frm_field_loading:not(.frm_load_now)' ); - if ( $nextSet.length ) { - loadFields( $nextSet.attr( 'id' ) ); - } else { - // go up a level - $nextSet = jQuery( document.getElementById( 'frm-show-fields' ) ).find( '.frm_field_loading:not(.frm_load_now)' ); - if ( $nextSet.length ) { - loadFields( $nextSet.attr( 'id' ) ); - } - } - - initiateMultiselect(); - renumberPageBreaks(); - maybeHideQuantityProductFieldOption(); - - const loadedEvent = new Event( 'frm_ajax_loaded_field', { bubbles: false }); - loadedEvent.frmFields = field.map( f => JSON.parse( f ) ); - document.dispatchEvent( loadedEvent ); - } - - function addFieldClick() { - /*jshint validthis:true */ - const $thisObj = jQuery( this ); - // there is no real way to disable a (with a valid href attribute) in HTML - https://css-tricks.com/how-to-disable-links/ - if ( $thisObj.hasClass( 'disabled' ) ) { - return false; - } - - const $button = $thisObj.closest( '.frmbutton' ); - const fieldType = $button.attr( 'id' ); - - if ( shouldStopInsertingField( fieldType ) ) { - return; - } - - let hasBreak = 0; - if ( 'summary' === fieldType ) { - hasBreak = $newFields.children( 'li[data-type="break"]' ).length > 0 ? 1 : 0; - } - - const formId = thisFormId; - jQuery.ajax({ - type: 'POST', - url: ajaxurl, - data: getInsertNewFieldArgs( fieldType, 0, formId, hasBreak ), - success: function( msg ) { - handleAddFieldClickResponse( msg ); - - const fieldId = checkMsgForFieldId( msg ); - if ( fieldId ) { - /** - * Fires after a field is added. - * - * @since 6.23 - * - * @param {Object} fieldData The field data. - * @param {String} fieldData.field The field HTML. - * @param {String} fieldData.field_type The field type. - * @param {String} fieldData.form_id The form ID. - */ - wp.hooks.doAction( 'frm_after_field_added_in_form_builder', { - field: msg, - fieldId, - fieldType, - form_id: formId, - }); - } - }, - error: handleInsertFieldError - }); - return false; - } - - function handleAddFieldClickResponse( msg ) { - document.getElementById( 'frm_form_editor_container' ).classList.add( 'frm-has-fields' ); - const replaceWith = wrapFieldLi( msg ); - const submitField = $newFields[0].querySelector( '.edit_field_type_submit' ); - - if ( ! submitField ) { - $newFields.append( replaceWith ); - } else { - jQuery( submitField.closest( '.frm_field_box:not(.form-field)' ) ).before( replaceWith ); - } - - afterAddField( msg, true ); - - replaceWith.each( - function() { - makeDroppable( this.querySelector( 'ul.frm_sorting' ) ); - makeDraggable( this.querySelector( '.form-field' ), '.frm-move' ); - } - ); - } - - function insertFormField( fieldType, fieldOptions = {} ) { - - return new Promise( ( resolve ) => { - const formId = thisFormId; - let hasBreak = 0; - - if ( 'summary' === fieldType ) { - hasBreak = $newFields.children( 'li[data-type="break"]' ).length > 0 ? 1 : 0; - } - - jQuery.ajax({ - type: 'POST', - url: ajaxurl, - data: Object.assign( getInsertNewFieldArgs( fieldType, 0, formId, hasBreak ), { field_options: fieldOptions } ), - success: function( msg ) { - resolve( msg ); - - setTimeout( () => { - updateFieldOrder(); - afterAddField( msg, true ); - - const fieldId = checkMsgForFieldId( msg ); - if ( fieldId ) { - /** - * Fires after a field is added. - * - * @since 6.23 - * - * @param {Object} fieldData The field data. - * @param {String} fieldData.field The field HTML. - * @param {String} fieldData.field_type The field type. - * @param {String} fieldData.form_id The form ID. - */ - wp.hooks.doAction( 'frm_after_field_added_in_form_builder', { - field: msg, - fieldId, - fieldType, - form_id: formId, - }); - } - }, 10 ); - }, - error: handleInsertFieldError - }); - } ); - } - - function maybeHideQuantityProductFieldOption() { - let hide = true, - opts = document.querySelectorAll( '.frmjs_prod_field_opt_cont' ); - - if ( $newFields.find( 'li.edit_field_type_product' ).length > 1 ) { - hide = false; - } - - for ( let i = 0; i < opts.length; i++ ) { - if ( hide ) { - opts[ i ].classList.add( 'frm_hidden' ); - } else { - opts[ i ].classList.remove( 'frm_hidden' ); - } - } - } - - /** - * Returns true if a field can be duplicated. - * - * @since 6.19 - * - * @param {HTMLElement} field - * @param {number} maxFieldsInGroup - * - * @returns {Boolean} - */ - function canDuplicateField( field, maxFieldsInGroup ) { - if ( field.classList.contains( 'frm-page-collapsed' ) ) { - return false; - } - const fieldGroup = field.closest( 'li.frm_field_box:not(.form-field)' ); - if ( ! fieldGroup ) { - return true; - } - const fieldsInGroup = getFieldsInRow( jQuery( fieldGroup.querySelector( 'ul' ) ) ).length; - return fieldsInGroup < maxFieldsInGroup; - } - - function duplicateField() { - let $field, fieldId, children, newRowId, fieldOrder; - const maxFieldsInGroup = 6; - - $field = jQuery( this ).closest( 'li.form-field' ); - newRowId = this.getAttribute( 'frm-target-row-id' ); - - if ( ! ( newRowId && newRowId.startsWith( 'frm_field_group_' ) ) && ! canDuplicateField( $field.get( 0 ), maxFieldsInGroup ) ) { - /* translators: %1$d: Maximum number of fields allowed in a field group. */ - infoModal( sprintf( __( 'You can only have a maximum of %1$d fields in a field group. Delete or move out a field from the group and try again.', 'formidable' ), maxFieldsInGroup ) ); - return; - } - - closeOpenFieldDropdowns(); - fieldId = $field.data( 'fid' ); - children = fieldsInSection( fieldId ); - - if ( null !== newRowId ) { - fieldOrder = this.getAttribute( 'frm-field-order' ); - } - - jQuery.ajax({ - type: 'POST', - url: ajaxurl, - data: { - action: 'frm_duplicate_field', - field_id: fieldId, - form_id: thisFormId, - children: children, - nonce: frmGlobal.nonce - }, - success: function( msg ) { - let newRow; - - let replaceWith; - - if ( null !== newRowId ) { - newRow = document.getElementById( newRowId ); - if ( null !== newRow ) { - replaceWith = msgAsjQueryObject( msg ); - jQuery( newRow ).append( replaceWith ); - makeDraggable( replaceWith.get( 0 ), '.frm-move' ); - if ( null !== fieldOrder ) { - newRow.lastElementChild.setAttribute( 'frm-field-order', fieldOrder ); - } - jQuery( newRow ).trigger( - 'frm_added_duplicated_field_to_row', - { - duplicatedFieldHtml: msg, - originalFieldId: fieldId - } - ); - afterAddField( msg, false ); - setLayoutClassesForDuplicatedFieldInGroup( $field.get( 0 ), replaceWith.get( 0 ) ); - return; - } - } - - if ( $field.siblings( 'li.form-field' ).length ) { - replaceWith = msgAsjQueryObject( msg ); - $field.after( replaceWith ); - syncLayoutClasses( $field ); - makeDraggable( replaceWith.get( 0 ), '.frm-move' ); - } else { - replaceWith = wrapFieldLi( msg ); - $field.parent().parent().after( replaceWith ); - makeDroppable( replaceWith.get( 0 ).querySelector( 'ul.frm_sorting' ) ); - makeDraggable( replaceWith.get( 0 ).querySelector( 'li.form-field' ), '.frm-move' ); - } - - updateFieldOrder(); - afterAddField( msg, false ); - maybeDuplicateUnsavedSettings( fieldId, msg ); - toggleOneSectionHolder( replaceWith.find( '.start_divider' ) ); - $field[0].querySelector( '.frm-dropdown-menu.dropdown-menu-right' )?.classList.remove( 'show' ); - setLayoutClassesForDuplicatedFieldInGroup( $field.get( 0 ), replaceWith.get( 0 ) ); - } - }); - return false; - } - - /** - * Sets the layout classes for a duplicated field in a field group from the layout classes of the original field. - * - * @param {HTMLElement} field The original field. - * @param {HTMLElement} newField The duplicated field. - * - * @returns {void} - */ - function setLayoutClassesForDuplicatedFieldInGroup( field, newField ) { - const hoverTarget = field.closest( '.frm-field-group-hover-target' ); - if ( ! hoverTarget || ! isFieldGroup( hoverTarget.parentElement ) ) { - return; - } - const fieldId = field.dataset.fid; - let fieldClasses = document.getElementById( 'frm_classes_' + fieldId )?.value; - if ( ! fieldClasses ) { - return; - } - fieldClasses = fieldClasses.replace( 'frm_first', '' ); - if ( ! newField.className.includes( fieldClasses ) ) { - newField.className += ' ' + fieldClasses; - - const classesInput = document.getElementById( 'frm_classes_' + newField.dataset.fid ); - if ( classesInput ) { - classesInput.value = fieldClasses; - } - } - } - - function maybeDuplicateUnsavedSettings( originalFieldId, newFieldHtml ) { - let originalSettings, newFieldId, copySettings, fieldOptionKeys, originalDefault, copyDefault; - - originalSettings = document.getElementById( 'frm-single-settings-' + originalFieldId ); - if ( null === originalSettings ) { - return; - } - - newFieldId = jQuery( newFieldHtml ).attr( 'data-fid' ); - if ( 'undefined' === typeof newFieldId ) { - return; - } - - copySettings = document.getElementById( 'frm-single-settings-' + newFieldId ); - if ( null === copySettings ) { - return; - } - - fieldOptionKeys = [ - 'name', 'required', 'unique', 'read_only', 'placeholder', 'description', 'size', 'max', 'format', 'prepend', 'append', 'separate_value' - ]; - - originalSettings.querySelectorAll( 'input[name^="field_options["], textarea[name^="field_options["]' ).forEach( - function( originalSetting ) { - let key, tagType, copySetting; - - key = getKeyFromSettingInput( originalSetting ); - - if ( 'options' === key ) { - copyOption( originalSetting, copySettings, originalFieldId, newFieldId ); - return; - } - - if ( -1 === fieldOptionKeys.indexOf( key ) ) { - return; - } - - tagType = originalSetting.matches( 'input' ) ? 'input' : 'textarea'; - copySetting = copySettings.querySelector( tagType + '[name="field_options[' + key + '_' + newFieldId + ']"]' ); - if ( null === copySetting ) { - return; - } - - if ( 'checkbox' === originalSetting.type ) { - if ( originalSetting.checked !== copySetting.checked ) { - jQuery( copySetting ).trigger( 'click' ); - } - } else if ( 'text' === originalSetting.type || 'textarea' === tagType ) { - if ( originalSetting.value !== copySetting.value ) { - copySetting.value = originalSetting.value; - jQuery( copySetting ).trigger( 'change' ); - } - } - } - ); - - originalDefault = originalSettings.querySelector( 'input[name="default_value_' + originalFieldId + '"]' ); - if ( null !== originalDefault ) { - copyDefault = copySettings.querySelector( 'input[name="default_value_' + newFieldId + '"]' ); - if ( null !== copyDefault && originalDefault.value !== copyDefault.value ) { - copyDefault.value = originalDefault.value; - jQuery( copyDefault ).trigger( 'change' ); - } - } - } - - function copyOption( originalSetting, copySettings, originalFieldId, newFieldId ) { - let remainingKeyDetails, copyKey, copySetting; - remainingKeyDetails = originalSetting.name.substr( 23 + ( '' + originalFieldId ).length ); - copyKey = 'field_options[options_' + newFieldId + ']' + remainingKeyDetails; - copySetting = copySettings.querySelector( 'input[name="' + copyKey + '"]' ); - if ( null !== copySetting && copySetting.value !== originalSetting.value ) { - copySetting.value = originalSetting.value; - jQuery( copySetting ).trigger( 'change' ); - } - } - - function getKeyFromSettingInput( input ) { - let nameWithoutPrefix, nameSplit; - nameWithoutPrefix = input.name.substr( 14 ); - nameSplit = nameWithoutPrefix.split( '_' ); - nameSplit.pop(); - return nameSplit.join( '_' ); - } - - function closeOpenFieldDropdowns() { - const openSettings = document.querySelector( '.frm-field-settings-open' ); - if ( null !== openSettings ) { - openSettings.classList.remove( 'frm-field-settings-open' ); - jQuery( document ).off( 'click', '#frm_builder_page', handleClickOutsideOfFieldSettings ); - jQuery( '.frm-field-action-icons .dropdown.open' ).removeClass( 'open' ); - } - } - - function handleClickOutsideOfFieldSettings( event ) { - if ( ! jQuery( event.originalEvent.target ).closest( '.frm-field-action-icons' ).length ) { - closeOpenFieldDropdowns(); - } - } - - function checkForMultiselectKeysOnMouseMove( event ) { - const keyIsDown = ! ! ( event.ctrlKey || event.metaKey || event.shiftKey ); - jQuery( builderPage ).toggleClass( 'frm-multiselect-key-is-down', keyIsDown ); - checkForActiveHoverTarget( event ); - } - - function checkForActiveHoverTarget( event ) { - let container, elementFromPoint, list, previousHoverTarget; - - container = postBodyContent; - if ( container.classList.contains( 'frm-dragging-field' ) ) { - return; - } - - if ( null !== document.querySelector( '.frm-field-group-hover-target .frm-field-settings-open' ) ) { - // do not set a hover target if a dropdown is open for the current hover target. - return; - } - - elementFromPoint = document.elementFromPoint( event.clientX, event.clientY ); - if ( null !== elementFromPoint && ! elementFromPoint.classList.contains( 'edit_field_type_divider' ) ) { - - list = elementFromPoint.closest( 'ul.frm_sorting' ); - - if ( null !== list && ! list.classList.contains( 'start_divider' ) && 'frm-show-fields' !== list.id ) { - previousHoverTarget = maybeRemoveGroupHoverTarget(); - if ( false !== previousHoverTarget && ! jQuery( previousHoverTarget ).is( list ) ) { - destroyFieldGroupPopup(); - } - updateFieldGroupControls( jQuery( list ), getFieldsInRow( jQuery( list ) ).length ); - list.classList.add( 'frm-field-group-hover-target' ); - jQuery( '#wpbody-content' ).on( 'mousemove', maybeRemoveHoverTargetOnMouseMove ); - } - } - } - - function maybeRemoveGroupHoverTarget() { - let controls, previousHoverTarget; - - controls = document.getElementById( 'frm_field_group_controls' ); - if ( null !== controls ) { - controls.style.display = 'none'; - } - - previousHoverTarget = document.querySelector( '.frm-field-group-hover-target' ); - if ( null === previousHoverTarget ) { - return false; - } - - jQuery( '#wpbody-content' ).off( 'mousemove', maybeRemoveHoverTargetOnMouseMove ); - previousHoverTarget.classList.remove( 'frm-field-group-hover-target' ); - return previousHoverTarget; - } - - function maybeRemoveHoverTargetOnMouseMove( event ) { - const elementFromPoint = document.elementFromPoint( event.clientX, event.clientY ); - if ( null !== elementFromPoint && null !== elementFromPoint.closest( '#frm-show-fields' ) ) { - return; - } - maybeRemoveGroupHoverTarget(); - } - - function onFieldActionDropdownShow( isFieldGroup ) { - unselectFieldGroups(); - // maybe offset the dropdown if it goes off of the right of the screen. - setTimeout( - function() { - let ul, $ul; - ul = document.querySelector( '.dropdown.show .frm-dropdown-menu' ); - if ( null === ul ) { - return; - } - if ( null === ul.getAttribute( 'aria-label' ) ) { - ul.setAttribute( 'aria-label', __( 'More Options', 'formidable' ) ); - } - if ( 0 === ul.children.length ) { - fillFieldActionDropdown( ul, true === isFieldGroup ); - } - $ul = jQuery( ul ); - if ( $ul.offset().left > jQuery( window ).width() - $ul.outerWidth() ) { - ul.style.left = ( -$ul.outerWidth() ) + 'px'; - } - const firstAnchor = ul.firstElementChild.querySelector( 'a' ); - if ( firstAnchor ) { - firstAnchor.focus(); - } - }, - 0 - ); - } - - function onFieldGroupActionDropdownShow() { - onFieldActionDropdownShow( true ); - } - - function changeSectionStyle( e ) { - const collapsedSection = e.target.closest( '.frm-section-collapsed' ); - if ( ! collapsedSection ) { - return; - } - - if ( e.type === 'show' ) { - collapsedSection.style.zIndex = 3; - } else { - collapsedSection.style.zIndex = 1; - } - } - - function fillFieldActionDropdown( ul, isFieldGroup ) { - let classSuffix, options; - classSuffix = isFieldGroup ? '_field_group' : '_field'; - options = [ getDeleteActionOption( isFieldGroup ), getDuplicateActionOption( isFieldGroup ) ]; - if ( ! isFieldGroup ) { - options.push( - { class: 'frm_select', icon: 'frm_settings_icon', label: __( 'Field Settings', 'formidable' ) } - ); - } - options.forEach( - function( option ) { - let li, anchor, span; - li = document.createElement( 'div' ); - li.classList.add( 'frm_more_options_li', 'dropdown-item' ); - - anchor = document.createElement( 'a' ); - anchor.classList.add( option.class + classSuffix ); - anchor.setAttribute( 'href', '#' ); - makeTabbable( anchor ); - - span = document.createElement( 'span' ); - span.textContent = option.label; - anchor.innerHTML = ''; - anchor.appendChild( document.createTextNode( ' ' ) ); - anchor.appendChild( span ); - - li.appendChild( anchor ); - ul.appendChild( li ); - } - ); - } - - function getDeleteActionOption( isFieldGroup ) { - const option = { class: 'frm_delete', icon: 'frm_delete_icon' }; - option.label = isFieldGroup ? __( 'Delete Group', 'formidable' ) : __( 'Delete', 'formidable' ); - return option; - } - - function getDuplicateActionOption( isFieldGroup ) { - const option = { class: 'frm_clone', icon: 'frm_clone_icon' }; - option.label = isFieldGroup ? __( 'Duplicate Group', 'formidable' ) : __( 'Duplicate', 'formidable' ); - return option; - } - - function wrapFieldLi( field ) { - const wrapper = div(); - - if ( 'string' === typeof field ) { - wrapper.innerHTML = field; - } else { - wrapper.appendChild( field ); - } - - let result = jQuery(); - Array.from( wrapper.children ).forEach( - li => { - result = result.add( - jQuery( '
  • ' ) - .addClass( 'frm_field_box' ) - .html( - jQuery( '