( function() { /** globals frmGlobal */ let __; if ( 'undefined' === typeof wp || 'undefined' === typeof wp.i18n || 'function' !== typeof wp.i18n.__ ) { __ = text => text; } else { __ = wp.i18n.__; } const modal = { maybeCreateModal: ( id, { title, content, footer, width } = {}) => { let modal = document.getElementById( id ); if ( ! modal ) { modal = createEmptyModal( id ); const titleElement = div({ className: 'frm-modal-title' }); if ( 'string' === typeof title ) { titleElement.textContent = title; } const a = tag( 'a', { child: svg({ href: '#frm_close_icon' }), className: 'dismiss' } ); const postbox = modal.querySelector( '.postbox' ); postbox.appendChild( div({ className: 'frm_modal_top', children: [ titleElement, div({ child: a }) ] }) ); postbox.appendChild( div({ className: 'frm_modal_content' }) ); if ( footer ) { postbox.appendChild( div({ className: 'frm_modal_footer' }) ); } } else if ( 'string' === typeof title ) { const titleElement = modal.querySelector( '.frm-modal-title' ); titleElement.textContent = title; } if ( ! content && ! footer ) { makeModalIntoADialogAndOpen( modal, { width }); return modal; } const postbox = modal.querySelector( '.postbox' ); const modalHelper = getModalHelper( modal, postbox ); if ( content ) { modalHelper( content, 'frm_modal_content' ); } if ( footer ) { modalHelper( footer, 'frm_modal_footer' ); } makeModalIntoADialogAndOpen( modal ); return modal; }, footerButton: args => { const output = a( args ); output.setAttribute( 'role', 'button' ); output.setAttribute( 'tabindex', 0 ); if ( args.buttonType ) { output.classList.add( 'button' ); if ( ! args.noDismiss && -1 !== [ 'red', 'primary' ].indexOf( args.buttonType ) ) { // Primary and red buttons close modals by default on click. // To disable this default behaviour you can use the noDismiss: 1 arg. output.classList.add( 'dismiss' ); } switch ( args.buttonType ) { case 'red': output.classList.add( 'frm-button-red', 'frm-button-primary' ); break; case 'primary': output.classList.add( 'button-primary', 'frm-button-primary' ); break; case 'secondary': output.classList.add( 'button-secondary', 'frm-button-secondary' ); output.style.marginRight = '10px'; break; case 'cancel': output.classList.add( 'button-secondary', 'frm-modal-cancel' ); break; } } return output; } }; const ajax = { doJsonFetch: async function( action ) { let targetUrl = ajaxurl + '?action=frm_' + action; if ( -1 === targetUrl.indexOf( 'nonce=' ) ) { targetUrl += '&nonce=' + frmGlobal.nonce; } const response = await fetch( targetUrl ); const json = await response.json(); if ( ! json.success ) { return Promise.reject( json.data || 'JSON result is not successful' ); } return Promise.resolve( json.data ); }, doJsonPost: async function( action, formData, { signal } = {}) { formData.append( 'nonce', frmGlobal.nonce ); const init = { method: 'POST', body: formData }; if ( signal ) { init.signal = signal; } const response = await fetch( ajaxurl + '?action=frm_' + action, init ); const json = await response.json(); if ( ! json.success ) { return Promise.reject( json.data || 'JSON result is not successful' ); } return Promise.resolve( 'undefined' !== typeof json.data ? json.data : json ); } }; const multiselect = { init: function() { const $select = jQuery( this ); const id = $select.is( '[id]' ) ? $select.attr( 'id' ).replace( '[]', '' ) : false; let labelledBy = id ? jQuery( '#for_' + id ) : false; labelledBy = id && labelledBy.length ? 'aria-labelledby="' + labelledBy.attr( 'id' ) + '"' : ''; // Set empty title attributes so that none of the dropdown options include title attributes. $select.find( 'option' ).attr( 'title', ' ' ); $select.multiselect({ templates: { popupContainer: '
', option: '', button: '' }, buttonContainer: '', nonSelectedText: __( '— Select —', 'formidable' ), // Prevent the dropdown from showing "All Selected" when every option is checked. allSelectedText: '', // This is 3 by default. We want to show more options before it starts showing a count. numberDisplayed: 8, onInitialized: function( _, $container ) { $container.find( '.multiselect.dropdown-toggle' ).removeAttr( 'title' ); }, onDropdownShown: function( event ) { const action = jQuery( event.currentTarget.closest( '.frm_form_action_settings, #frm-show-fields' ) ); if ( action.length ) { jQuery( '#wpcontent' ).on( 'click', function() { if ( jQuery( '.multiselect-container.frm-dropdown-menu' ).is( ':visible' ) ) { jQuery( event.currentTarget ).removeClass( 'open' ); } }); } const $dropdown = $select.next( '.frm-btn-group.dropdown' ); $dropdown.find( '.dropdown-item' ).each( function() { const option = this; const dropdownInput = option.querySelector( 'input[type="checkbox"], input[type="radio"]' ); if ( dropdownInput ) { option.setAttribute( 'role', 'checkbox' ); option.setAttribute( 'aria-checked', dropdownInput.checked ? 'true' : 'false' ); } } ); }, onChange: function( $option, checked ) { $select.trigger( 'frm-multiselect-changed', $option, checked ); const $dropdown = $select.next( '.frm-btn-group.dropdown' ); const optionValue = $option.val(); const $dropdownItem = $dropdown.find( 'input[value="' + optionValue + '"]' ).closest( 'button.dropdown-item' ); if ( $dropdownItem.length ) { $dropdownItem.attr( 'aria-checked', checked ? 'true' : 'false' ); // Delay a focus event so the screen reader reads the option value again. // Without this, and without the setTimeout, it only reads "checked" or "unchecked". setTimeout( () => $dropdownItem.get( 0 ).focus(), 0 ); } } }); } }; const bootstrap = { setupBootstrapDropdowns( callback ) { if ( ! window.bootstrap || ! window.bootstrap.Dropdown ) { return; } window.bootstrap.Dropdown._getParentFromElement = getParentFromElement; window.bootstrap.Dropdown.prototype._getParentFromElement = getParentFromElement; function getParentFromElement( element ) { let parent; const selector = window.bootstrap.Util.getSelectorFromElement( element ); if ( selector ) { parent = document.querySelector( selector ); } const result = parent || element.parentNode; const frmDropdownMenu = result.querySelector( '.frm-dropdown-menu' ); if ( ! frmDropdownMenu ) { // Not a formidable dropdown, treat like Bootstrap does normally. return result; } // Temporarily add dropdown-menu class so bootstrap can initialize. frmDropdownMenu.classList.add( 'dropdown-menu' ); setTimeout( function() { frmDropdownMenu.classList.remove( 'dropdown-menu' ); }, 0 ); if ( 'function' === typeof callback ) { callback( frmDropdownMenu ); } return result; } }, multiselect }; const autocomplete = { initSelectionAutocomplete: function() { if ( jQuery.fn.autocomplete ) { autocomplete.initAutocomplete( 'page' ); autocomplete.initAutocomplete( 'user' ); } }, /** * Init autocomplete. * * @since 4.10.01 Add container param to init autocomplete elements inside an element. * * @param {String} type Type of data. Accepts `page` or `user`. * @param {String|Object} container Container class or element. Default is null. */ initAutocomplete: function( type, container ) { const basedUrlParams = '?action=frm_' + type + '_search&nonce=' + frmGlobal.nonce; const elements = ! container ? jQuery( '.frm-' + type + '-search' ) : jQuery( container ).find( '.frm-' + type + '-search' ); elements.each( initAutocompleteForElement ); function initAutocompleteForElement() { let urlParams = basedUrlParams; const element = jQuery( this ); // Check if a custom post type is specific. if ( element.attr( 'data-post-type' ) ) { urlParams += '&post_type=' + element.attr( 'data-post-type' ); } element.autocomplete({ delay: 100, minLength: 0, source: ajaxurl + urlParams, change: autocomplete.selectBlank, select: autocomplete.completeSelectFromResults, focus: () => false, position: { my: 'left top', at: 'left bottom', collision: 'flip' }, response: function( event, ui ) { if ( ! ui.content.length ) { const noResult = { value: '', label: frm_admin_js.no_items_found }; ui.content.push( noResult ); } }, create: function() { let $container = jQuery( this ).parent(); if ( $container.length === 0 ) { $container = 'body'; } jQuery( this ).autocomplete( 'option', 'appendTo', $container ); } }) .on( 'focus', function() { // Show options on click to make it work more like a dropdown. if ( this.value === '' || this.nextElementSibling.value < 1 ) { jQuery( this ).autocomplete( 'search', this.value ); } }) .data( 'ui-autocomplete' )._renderItem = function( ul, item ) { return jQuery( '