: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
/* global wpforms_admin */
* WPForms admin. Extend list tables functionality.
* @param wpforms_admin.column_selector_title
* @param wpforms_admin.save_changes
* @param wpforms_admin.uh_oh
* @param wpforms_admin.unknown_error
* @param wpforms_admin.column_selector_no_fields
* @param wpforms_admin.column_selector_no_meta
const WPFormsAdminListTableExt = window.WPFormsAdminListTableExt || ( function( document, window, $ ) {
* Supported pages' CSS selectors.
* It is the ids of the `.wpforms-admin-wrap` container, which reflects `page` + `view` URL attributes.
* Element selectors shared between functions.
cogIcon: '#wpforms-list-table-ext-edit-columns-cog',
submitButton: '#wpforms-list-table-ext-edit-columns-select-submit',
* Public functions and properties.
el.$doc.on( 'wpformsReady', app.initMultiSelect );
app.prepareTableFootColumns();
app.initTableScrollColumns();
app.initTableSortableColumns();
.on( 'click', selectors.cogIcon, app.onClickCog )
.on( 'wpforms_multiselect_checkbox_list_toggle', app.onMenuToggle )
.on( 'click', selectors.submitButton, app.onSaveChanges );
el.$tableScroll?.on( 'scroll', app.tableScroll );
// noinspection TypeScriptUMDGlobal
$( window ).on( 'resize', _.debounce( app.windowResize, 100 ) );
el.$searchInput?.on( 'input', _.debounce( app.maybeShowNoResults, 310 ) ); // On 300 ms the multiselect lib is updating the list of items so we need to wait a bit more.
el.$header = $( '#wpforms-header' );
el.$page = $( supportedPages.join( ',' ) );
el.$table = el.$page.find( '.wp-list-table' );
el.$tableContainer = el.$table.parent();
el.$menu = $( '#wpforms-list-table-ext-edit-columns-select-container' );
el.$cog = app.initCogIcon();
el.$wpcontent = $( '#wpcontent' );
// The Forms Overview page has no table container, wrap the table.
if ( ! el.$tableContainer.hasClass( 'wpforms-table-container' ) ) {
el.$table.wrap( '<div class="wpforms-table-container"></div>' );
el.$tableContainer = el.$table.parent();
// Add specific classes to the page container.
el.$page.addClass( 'wpforms-list-table-ext-page' );
* Prepare table footer columns. Their IDs should match the IDs of the header columns.
prepareTableFootColumns() {
el.$table.find( 'thead tr .manage-column' ).each( function() {
const columnId = $( this ).attr( 'id' );
el.$table.find( 'tfoot tr .column-' + columnId ).attr( 'id', columnId + '-foot' );
// Disable sorting of the cog column.
el.$table.find( '.manage-column.column-cog' )
.addClass( 'wpforms-table-cell-sticky' );
* Initialize table columns sortable container.
initTableSortableColumns() { // eslint-disable-line max-lines-per-function
el.$table.find( 'thead tr, tfoot tr' ).each( function() { // eslint-disable-line max-lines-per-function
const $sortable = $( this );
items: '> th:not(:first-child):not(.wpforms-table-cell-sticky)',
cancel: '.wpforms-table-column-not-draggable',
placeholder: 'wpforms-table-column-drag-placeholder',
width = $el.outerWidth();
return $helper.css( 'width', width + 'px' );
ui.helper.addClass( 'wpforms-table-column-drag-helper' ); // Add a specific class to the helper container.
ui.item.addClass( 'wpforms-table-column-dragged-out' ).css( 'display', '' );
// Disable global scrolling.
el.$wpcontent.addClass( 'wpforms-no-scroll' );
columnId = ui.item.attr( 'id' ).replace( '-foot', '' );
// Remove specific classes from the helper.
.removeClass( 'wpforms-table-column-drag-helper' )
.removeClass( 'wpforms-table-column-dragged-out' );
// Remove previously added vertical placeholder class from all columns.
el.$table.find( 'thead tr > *, tfoot tr > *' ).removeClass( 'wpforms-table-column-drag-placeholder-prev' );
// Enable global scrolling.
el.$wpcontent.removeClass( 'wpforms-no-scroll' );
const prevColumnId = ui.item.prev().attr( 'id' ).replace( '-foot', '' ),
$rows = el.$table.find( 'tbody tr:not(.wpforms-hidden)' ),
prevSelector = prevColumnId !== 'cb' ? '.column-' + prevColumnId : '.check-column';
$columnCells = $rows.find( 'td.column-' + columnId ).detach();
for ( let i = 0; i < $columnCells.length; i++ ) {
$rows.eq( i ).find( prevSelector ).after( $columnCells.eq( i ) );
// Move opposite column header.
const oppositeColumnsSelector = ui.item.closest( 'thead' ).length > 0 ? 'tfoot' : 'thead',
$oppositeColumn = el.$table.find( oppositeColumnsSelector + ' tr .column-' + columnId ).detach();
el.$table.find( oppositeColumnsSelector + ' tr ' + prevSelector ).after( $oppositeColumn );
app.updateMenuColumnsOrder();
// Remove previously added vertical placeholder class from all columns.
el.$table.find( 'thead tr > *, tfoot tr > *' ).removeClass( 'wpforms-table-column-drag-placeholder-prev' );
// Add the vertical placeholder class to the previous column.
ui.placeholder.prev().addClass( 'wpforms-table-column-drag-placeholder-prev' );
* Initialize table scroll sticky columns.
initTableScrollColumns() {
// Init table horizontal scrolling only on the Entries page.
if ( ! el.$page.is( '#wpforms-entries-list' ) ) {
el.$tableScroll = el.$tableContainer;
// The Entries page has own table container, add the class.
el.$tableScroll.addClass( 'wpforms-table-scroll' );
// Detect the Windows OS platform.
el.$tableScroll.toggleClass( 'wpforms-scrollbar', app.isCustomScrollbarNeeded() );
// Add specific class to the sticky columns.
el.$table.find( '.check-column, .column-indicators' )
.addClass( 'wpforms-table-cell-sticky' )
el.$table.find( '.column-actions' )
.addClass( 'wpforms-table-cell-sticky' )
if ( ! el.$tableScroll?.length ) {
const width = el.$tableScroll.outerWidth(),
scrollLeft = Math.abs( el.$tableScroll.get( 0 ).scrollLeft ),
scrollWidth = el.$tableScroll.get( 0 ).scrollWidth;
// Conditionally Add shadow to the sticky columns.
.find( '.wpforms-table-cell-sticky.left' )
.toggleClass( 'shadow', scrollLeft > 1 ); // 1px is fix for the RTL mode.
.find( '.wpforms-table-cell-sticky.right' )
.toggleClass( 'shadow', scrollWidth - width >= scrollLeft );
// Disable dragging on mobiles.
el.$table.find( 'thead th, tfoot th' ).toggleClass( 'wpforms-table-column-not-draggable', window.innerWidth <= 782 );
app.windowResizeToggleColumns();
* Toggle columns visibility for certain window sizes.
windowResizeToggleColumns() {
// Proceed only on the Forms Overview page.
if ( ! el.$page.is( '#wpforms-overview' ) ) {
const $visibleColumns = el.$table.find( 'thead tr th:visible' );
const $columnTags = el.$table.find( '.column-tags' );
// For browser window with the width between 960px and 1280px.
if ( window.innerWidth > 960 && window.innerWidth <= 1280 ) {
$columnTags.toggleClass( 'wpforms-hidden', $visibleColumns.length > 4 );
$columnTags.removeClass( 'wpforms-hidden' );
// Synchronize menu items visibility.
el.$menu.find( 'label' ).removeClass( 'wpforms-hidden' );
el.$table.find( 'thead tr th:not(:visible)' ).each( function() {
const $column = $( this );
.find( `input[value="${ $column.attr( 'id' ) }"]` )
.addClass( 'wpforms-hidden' );
* Show or hide no results text.
[ 'fields', 'meta' ].forEach( ( section ) => {
const labels = el.$menu.find( '.wpforms-multiselect-checkbox-optgroup-' + section )
.nextUntil( '.wpforms-multiselect-checkbox-optgroup' )
const hiddenLabels = labels.filter( function() {
return $( this ).is( ':hidden' );
el.$menu.find( '.wpforms-multiselect-checkbox-no-results-' + section )
.toggleClass( 'wpforms-hidden', labels.length !== hiddenLabels.length );
* Close the columns' selector menu.
if ( ! el.$cog.hasClass( 'active' ) ) {
el.$cog.removeClass( 'active' );
el.$menu.find( '.wpforms-multiselect-checkbox-list' ).removeClass( 'open' );
// Flush the search input.
el.$searchInput.val( '' );
el.$searchInput[ 0 ]?.dispatchEvent( new Event( 'input' ) );
* @return {Array} Columns order.
const $row = el.$table.find( 'thead tr' );
$row.find( 'th' ).each( function() {
columns.push( $( this ).attr( 'id' ) );
* Get menu columns order.
* @return {Array} Columns order.
let columnsOrder = app.getColumnsOrder();
const columnsChecked = [];
el.$menu.find( `input:checked` ).each( function() {
columnsChecked.push( $( this ).val() );
// Convert DOM element IDs to column IDs.
columnsOrder = columnsOrder.map( function( column ) {
return app.convertColumnId( column );
// Add checked columns in the same order as in the table.
for ( let i = 0; i < columnsOrder.length; i++ ) {
const column = columnsOrder[ i ];
if ( columnsChecked.includes( column ) ) {
columnsChecked.splice( columnsChecked.indexOf( column ), 1 );
// Add the rest of the checked columns.
return columns.concat( columnsChecked );
nonce: wpforms_admin.nonce,
action: el.$menu.find( '[name="action"]' ).val(),
form_id: el.$menu.find( '[name="form_id"]' ).val(), // eslint-disable-line camelcase
columns: app.getColumnsOrder(),
// AJAX request to save the columns order.
$.post( wpforms_admin.ajax_url, data )
.done( function( response ) {
if ( ! response.success ) {
app.displayErrorModal( response.data || wpforms_admin.unknown_error );
app.displayErrorModal( wpforms_admin.server_error );
* Display modal window with an error message.
* @param {string} content Modal content.
displayErrorModal( content ) {
title : wpforms_admin.uh_oh,
icon : 'fa fa-exclamation-circle',
text : wpforms_admin.close,
* Update menu columns order.
updateMenuColumnsOrder() { // eslint-disable-line complexity
let columnsOrder = app.getColumnsOrder();
const $groups = el.$menu.find( '.wpforms-multiselect-checkbox-optgroup' );
const $itemsCont = el.$menu.find( '.wpforms-multiselect-checkbox-items' );
const $items = $itemsCont.find( 'label' );
const itemsByGroup = [ 0 ];
// If there are no groups, add the items to the first group.
itemsByGroup[ 0 ] = $items;
// If there are groups, split the items by groups.
$groups.each( function( i ) {
itemsByGroup[ i ] = $( this ).nextUntil( '.wpforms-multiselect-checkbox-optgroup' );
// Convert DOM element IDs to column IDs.
columnsOrder = columnsOrder.map( function( column ) {
return app.convertColumnId( column );
// Rebuild the menu items order.
for ( let g = 0; g < itemsByGroup.length; g++ ) {
itemsByGroup[ g ] = itemsByGroup[ g ].filter( function() {
return $( this ).find( 'input:checked' ).length > 0;