Skip to content

Rock: Redesigned forms list#2932

Open
truongwp wants to merge 37 commits intomasterfrom
redesigned-forms-table
Open

Rock: Redesigned forms list#2932
truongwp wants to merge 37 commits intomasterfrom
redesigned-forms-table

Conversation

@truongwp
Copy link
Copy Markdown
Contributor

@truongwp truongwp commented Feb 3, 2026

Handoff: https://linear.app/strategy11/document/feature-handoff-aa4fc7646f11

This adds a new gear icon to change the screen options more quickly.

Summary by CodeRabbit

  • New Features

    • Settings column and dropdown added to forms list for quick access
    • Option to show/hide form descriptions in the list, persisted per user
    • New screen options and templates to customize visible columns and per-page pagination
  • Style

    • New admin styles for forms list UI, settings panel, collapsible sections, and controls
  • Chores

    • Client-side behavior for settings panel, apply/save actions, and collapsible interactions

@deepsource-io
Copy link
Copy Markdown

deepsource-io bot commented Feb 3, 2026

DeepSource Code Review

We reviewed changes in f434ee9...ae3f4dc on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

PR Report Card

Overall Grade   Security  

Reliability  

Complexity  

Hygiene  

Code Review Summary

Analyzer Status Updated (UTC) Details
PHP Mar 25, 2026 3:27p.m. Review ↗
JavaScript Mar 25, 2026 3:27p.m. Review ↗

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 3, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds an admin forms-list feature: new JS and SCSS assets, screen options UI and templates, a Settings column and optional form descriptions in the list, and persistence of the description visibility preference.

Changes

Cohort / File(s) Summary
Admin Asset Registration
classes/controllers/FrmAppController.php
Registers new JS asset formidable_forms_list (js/admin/forms-list.js) with dependency formidable_dom and enqueues it on the forms listing page (ensures jquery-ui-autocomplete first).
Screen Options & Admin Hooks
classes/controllers/FrmFormsController.php, classes/controllers/FrmHooksController.php
Adds head(), print_forms_list_templates(), and add_screen_options() in FrmFormsController; registers screen_settings filter and admin_footer action in FrmHooksController to inject screen options and render templates.
Forms List Columns & Persistence
classes/controllers/FrmFormsController.php, classes/helpers/FrmFormsListHelper.php
Adds a settings column when not trash; save_per_page() now persists frm_forms_show_desc; single_row() sanitizes data-colname for settings, renders the new settings column, and appends form descriptions when the user option is enabled.
Admin JS Interaction
js/admin/forms-list.js
Adds an IIFE that toggles/moves the settings dropdown, applies settings by copying values into the advanced screen-options form and submitting it, and toggles collapsible sections with icon swaps.
Styling
resources/scss/admin/components/_forms-list.scss, resources/scss/admin/frm_admin.scss
Adds SCSS for forms-list UI (settings button, dropdown, per-page control, collapsible boxes, narrow settings column) and imports it into frm_admin.scss.

Sequence Diagram(s)

sequenceDiagram
    participant Browser as Admin Browser
    participant JS as forms-list.js
    participant PHP as WP Admin (FrmFormsController)
    participant DB as WP User Options

    Browser->>JS: click settings / toggle / Apply
    JS->>JS: build settings payload
    JS->>PHP: submit adv-settings form (POST)
    PHP->>DB: update options (per-page, frm_forms_show_desc, columns)
    PHP->>Browser: respond / reload listing
    Browser->>JS: reflect updated UI
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

"I’m a rabbit in the admin glade,
I nudged the columns, toggled the shade.
Per-page hops and descriptions unfurled,
A tiny dropdown brightens the world.
🥕✨ — cheers from the forms-list burrow."

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 73.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ⚠️ Warning The title 'Rock: Redesigned forms list' does not accurately reflect the changeset. The 'Rock:' prefix appears to be a branch or context label unrelated to the PR changes, and 'forms list' differs from the actual scope (forms table/listing page redesign). Revise the title to remove the 'Rock:' prefix and use a clearer descriptor such as 'Redesign forms table UI with screen options and settings column' to accurately reflect the scope of changes.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch redesigned-forms-table

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
classes/controllers/FrmFormsController.php (1)

50-61: ⚠️ Potential issue | 🟡 Minor

Documentation placeholder needs version number.

The docblock uses @since x.x which should be updated to the actual version number before release.

🤖 Fix all issues with AI agents
In `@classes/controllers/FrmFormsController.php`:
- Around line 1248-1256: Fix the typo in the settings wrapper class by renaming
the HTML class "frm-forms-list-settings-btn-wrappper" to
"frm-forms-list-settings-btn-wrapper" in FrmFormsController (where
$columns['settings'] is set) and update the matching selector in the SCSS/CSS so
both markup and styles use the corrected "wrapper" spelling; ensure you update
any other occurrences of the misspelled class name across the codebase to keep
markup and styles consistent.
- Around line 1319-1320: The update_user_option call in FrmFormsController
directly reads $_POST['frm_forms_show_desc'] without nonce verification; before
using that POST value (the call around update_user_option(..., ! empty(
$_POST['frm_forms_show_desc'] ))), either perform a nonce check with
wp_verify_nonce() or check_admin_referer() using the nonce name used by the
form, or if this POST access is intentionally safe, add the same phpcs ignore
comment used elsewhere (// phpcs:ignore
WordPress.Security.NonceVerification.Missing) immediately above the line; update
the method in FrmFormsController accordingly so the POST read is protected or
explicitly whitelisted.

In `@classes/helpers/FrmFormsListHelper.php`:
- Around line 304-306: The form description output in FrmFormsListHelper (the
concat that builds $val using nl2br( $item->description )) is not escaped and
can lead to XSS; update the concatenation to escape the description before
converting newlines (e.g., call esc_html() or an appropriate WP escaping
function on $item->description and then pass that result to nl2br) so the output
is safely encoded while preserving line breaks, keeping the conditional around
get_user_option( 'frm_forms_show_desc' ) and the $item->description reference
intact.

In `@js/admin/forms-list.js`:
- Around line 6-24: In handleClickFormsListSettings, guard against a null btn
(computed from event.target or its closest 'a') before accessing
btn.nextElementSibling or calling btn.after: after computing const btn = ... add
a null check (if (!btn) return;) so the handler safely no-ops when the click
target isn't an <a> or inside one; this prevents the TypeError when btn is null
while leaving existing logic for toggling/moving the dropdown (refer to btn,
event.target, and frm-forms-list-settings).
- Around line 50-64: The handler handleClickCollapsibleBtn must guard against
nulls: verify that container (result of
event.target.closest('.frm-collapsible-box')) is non-null before using it, then
query for the '.frm-collapsible-box__content' element and ensure that content
exists before toggling its class, and finally keep the existing check for svgUse
(container.querySelector('.frm-collapsible-box__btn use')) but only access
svgUse.href.baseVal after confirming svgUse is not null; update the function to
return early when container or content is missing to avoid TypeError.

In `@resources/scss/admin/components/_forms-list.scss`:
- Around line 1-3: Fix the typo in the CSS class name by renaming
.frm-forms-list-settings-btn-wrappper to .frm-forms-list-settings-btn-wrapper in
the SCSS file, and update the corresponding HTML output in the
FrmFormsController::get_columns() method to use the corrected class name so both
stylesheet and generated markup match; search for all occurrences of
"frm-forms-list-settings-btn-wrappper" (including in get_columns()) and replace
them with "frm-forms-list-settings-btn-wrapper", then verify styling still
applies.
🧹 Nitpick comments (1)
classes/controllers/FrmFormsController.php (1)

3712-3798: Consider adding a maximum value for the per-page input.

The number input on Line 3789 has min="1" but no max attribute. While unlikely to cause issues, allowing extremely large values could impact performance when rendering the forms list.

Proposed enhancement
-<input id="frm-forms-list-per-page" type="number" value="<?php echo intval( $per_page ); ?>" min="1" data-screen-option-id="formidable_page_formidable_per_page" />
+<input id="frm-forms-list-per-page" type="number" value="<?php echo intval( $per_page ); ?>" min="1" max="999" data-screen-option-id="formidable_page_formidable_per_page" />

@truongwp truongwp requested a review from lauramekaj1 February 3, 2026 19:56
@truongwp truongwp removed the run tests label Feb 6, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
classes/controllers/FrmFormsController.php (1)

1309-1318: ⚠️ Potential issue | 🔴 Critical

frm_forms_show_desc is updated on every screen option save across all admin pages.

The update_user_option call on Line 1315 executes whenever the set-screen-option filter fires for any option that isn't formidable_page_formidable_per_page. When a user saves screen options on the Posts page, Users page, etc., $_POST['frm_forms_show_desc'] will be absent, resetting the preference to false.

Gate this behind a page check:

Proposed fix
 public static function save_per_page( $save, $option, $value ) {
     if ( $option === 'formidable_page_formidable_per_page' ) {
+        // phpcs:ignore WordPress.Security.NonceVerification.Missing
+        update_user_option( get_current_user_id(), 'frm_forms_show_desc', ! empty( $_POST['frm_forms_show_desc'] ) );
+
         return (int) $value;
     }

-    // phpcs:ignore WordPress.Security.NonceVerification.Missing
-    update_user_option( get_current_user_id(), 'frm_forms_show_desc', ! empty( $_POST['frm_forms_show_desc'] ) );
-
     return $save;
 }
🧹 Nitpick comments (2)
js/admin/forms-list.js (1)

66-71: No mechanism to close the settings dropdown when clicking outside.

The dropdown is toggled by clicking the gear icon but there is no listener to close it when the user clicks elsewhere on the page. Consider adding a click listener on document (or a similar approach) that hides #frm-forms-list-settings when the click target is outside the dropdown and the trigger button.

classes/controllers/FrmFormsController.php (1)

1243-1251: Duplicate form_type check can be consolidated.

Lines 1237 and 1243 both check 'trash' !== FrmAppHelper::simple_get( 'form_type' ). Consider extracting the result into a local variable to avoid the repeated call.

Proposed refactor
+       $is_trash = 'trash' === FrmAppHelper::simple_get( 'form_type' );
 
-       if ( 'trash' !== FrmAppHelper::simple_get( 'form_type' ) ) {
+       if ( ! $is_trash ) {
            $columns['shortcode'] = esc_html__( 'Actions', 'formidable' );
        }
 
        $columns['created_at'] = esc_html__( 'Date', 'formidable' );
 
-       if ( 'trash' !== FrmAppHelper::simple_get( 'form_type' ) ) {
+       if ( ! $is_trash ) {
            $columns['settings'] = '<div class="frm-forms-list-settings-btn-wrapper">

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
classes/controllers/FrmFormsController.php (1)

1309-1318: ⚠️ Potential issue | 🟠 Major

Bug: frm_forms_show_desc is reset on every screen-option save across all admin pages.

The update_user_option call on line 1315 runs for every invocation of the set-screen-option filter, not just the Formidable forms listing page. When a user saves screen options on any other admin page (e.g., Posts, Pages), $_POST['frm_forms_show_desc'] won't be set, so ! empty(...) evaluates to false, silently disabling the form description preference.

Guard this call so it only runs on the relevant page:

🐛 Proposed fix
 	public static function save_per_page( $save, $option, $value ) {
 		if ( $option === 'formidable_page_formidable_per_page' ) {
+			// phpcs:ignore WordPress.Security.NonceVerification.Missing
+			update_user_option( get_current_user_id(), 'frm_forms_show_desc', ! empty( $_POST['frm_forms_show_desc'] ) );
 			return (int) $value;
 		}
 
-		// phpcs:ignore WordPress.Security.NonceVerification.Missing
-		update_user_option( get_current_user_id(), 'frm_forms_show_desc', ! empty( $_POST['frm_forms_show_desc'] ) );
-
 		return $save;
 	}
🧹 Nitpick comments (2)
classes/controllers/FrmFormsController.php (2)

51-62: Docblock says "This adds screen options" but the method body doesn't.

The @since x.x note says "This adds screen options," yet head() only enqueues jquery-touch-punch. Screen options are wired via add_screen_options() and hooks elsewhere. Consider updating the annotation to reflect what actually changed (e.g., "Added docblock" or referencing the hook registration that ties into this method).


1237-1250: Consider caching the trash check to avoid duplicate simple_get calls.

FrmAppHelper::simple_get( 'form_type' ) is called twice with the same argument (lines 1237 and 1243). A local variable would reduce duplication.

♻️ Proposed refactor
+		$is_trash = 'trash' === FrmAppHelper::simple_get( 'form_type' );
 
-		if ( 'trash' !== FrmAppHelper::simple_get( 'form_type' ) ) {
+		if ( ! $is_trash ) {
 			$columns['shortcode'] = esc_html__( 'Actions', 'formidable' );
 		}
 
 		$columns['created_at'] = esc_html__( 'Date', 'formidable' );
 
-		if ( 'trash' !== FrmAppHelper::simple_get( 'form_type' ) ) {
+		if ( ! $is_trash ) {
 			$columns['settings'] = '<div class="frm-forms-list-settings-btn-wrapper">

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@classes/helpers/FrmFormsListHelper.php`:
- Around line 306-312: The code appends a description twice in excerpt mode
because get_form_name() already calls add_form_description() when $mode ===
'excerpt'; to fix, avoid appending the description again in FrmFormsListHelper
where $val is built: change the conditional that appends '<p
class="frm_form_desc">' . nl2br($item->description) . '</p>' to skip when $mode
=== 'excerpt' (or otherwise unify the logic so only add_form_description()
handles excerpts and this block only handles non-excerpt/full-description
cases), referencing get_form_name(), add_form_description(), $mode and
$item->description to locate the affected logic.

Comment on lines +306 to 312
$val = $this->get_form_name( $item, $actions, $edit_link, $mode );

if ( get_user_option( 'frm_forms_show_desc' ) && ! empty( $item->description ) ) {
$val .= '<p class="frm_form_desc">' . nl2br( $item->description ) . '</p>';
}

$val .= $action_links;
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Potential double description in excerpt mode.

get_form_name() already calls add_form_description() (line 494), which appends a truncated description when $mode === 'excerpt' (line 520). Then lines 308–310 append the full description again based on the user option, regardless of mode. When both conditions are true, the description appears twice.

Consider guarding lines 308–310 to skip when $mode === 'excerpt', or unify the two description paths.

Proposed fix
- if ( get_user_option( 'frm_forms_show_desc' ) && ! empty( $item->description ) ) {
+ if ( 'excerpt' !== $mode && get_user_option( 'frm_forms_show_desc' ) && ! empty( $item->description ) ) {
      $val .= '<p class="frm_form_desc">' . nl2br( esc_html( $item->description ) ) . '</p>';
  }
🤖 Prompt for AI Agents
In `@classes/helpers/FrmFormsListHelper.php` around lines 306 - 312, The code
appends a description twice in excerpt mode because get_form_name() already
calls add_form_description() when $mode === 'excerpt'; to fix, avoid appending
the description again in FrmFormsListHelper where $val is built: change the
conditional that appends '<p class="frm_form_desc">' . nl2br($item->description)
. '</p>' to skip when $mode === 'excerpt' (or otherwise unify the logic so only
add_form_description() handles excerpts and this block only handles
non-excerpt/full-description cases), referencing get_form_name(),
add_form_description(), $mode and $item->description to locate the affected
logic.

Copy link
Copy Markdown
Contributor Author

@truongwp truongwp Mar 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Crabcyborg If the mode is "excerpt" and our Show form description is enabled, it will show a truncated description and the full description below. Should we ignore the excerpt when our setting is enabled?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@truongwp, that's a good approach. If the "Show form description" setting is enabled, the new <p class="frm_form_desc"> block already handles displaying the description in full (for all modes), so the old truncated-excerpt path in add_form_description() becomes redundant. Suppressing the excerpt when the setting is on avoids the double-description without needing the 'excerpt' !== $mode guard on the new block.

Here's the suggested fix in add_form_description():

 private function add_form_description( $item, &$val ) {
     global $mode;

-    if ( 'excerpt' === $mode && ! is_null( $item->description ) ) {
+    if ( 'excerpt' === $mode && ! is_null( $item->description ) && ! get_user_option( 'frm_forms_show_desc' ) ) {
         $val .= FrmAppHelper::truncate( strip_tags( $item->description ), 50 );
     }
 }

This way:

  • Setting disabled + excerpt mode → truncated description shown as before (no regression).
  • Setting enabled (any mode) → full description shown via the new <p class="frm_form_desc"> block; the old excerpt path is skipped.

truongwp and others added 4 commits February 10, 2026 14:54
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@lauramekaj1
Copy link
Copy Markdown
Contributor

@truongwp I tested this feature and found a couple of issues as listed below:

  1. The selected column checkboxes and the “Form description” toggle are not saved after clicking Apply. When reopening the Settings panel, the options are reset. Only “Items per page” correctly saves and reflects the selected value (https://www.loom.com/share/c655d746c69c4573be2ad880853bc8dc).
  2. When “Items per page” is set to 1 or 2, the page height becomes short. Clicking the lower Settings gear opens the panel upward, causing it to overlap the top admin bar (https://imgur.com/eL4UXEK).
  3. When the page width is reduced, the Style column content overlaps into neighboring columns instead of wrapping correctly (https://imgur.com/91x3o8e).
  4. When entering a three digit number in the “Items per page” field, the value is not fully visible due to the limited input width (https://imgur.com/W8lIt2S).
  5. Question: The Settings columns panel remains open when you do an outside click. Would it be better if clicking outside the panel closed it?
  6. Question: When clicking “Edit Style” from the dropdown of that form, should it open the style assigned specifically to that form, or should it redirect to the general Styles page?

Let me know if you need any further details. Thank you!

@truongwp
Copy link
Copy Markdown
Contributor Author

@lauramekaj1 Thanks for testing this. I'm fixing them. For your questions:

  1. It should close when clicking outside.
  2. It should open the style that is assigned to that form.

@truongwp
Copy link
Copy Markdown
Contributor Author

truongwp commented Mar 4, 2026

@lauramekaj1 I fixed all the issues you reported and made the settings panel close when clicking outside. Please test this again.

Notice: The column checkboxes now affect immediately without clicking the "Apply" button. This is the same as the WordPress behavior.

Copy link
Copy Markdown
Contributor

@lauramekaj1 lauramekaj1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@truongwp I tested it and it's working as expected. Thank you!

$val = $this->get_form_name( $item, $actions, $edit_link, $mode );

if ( get_user_option( 'frm_forms_show_desc' ) && ! empty( $item->description ) ) {
$val .= '<p class="frm_form_desc">' . nl2br( $item->description ) . '</p>';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@truongwp Can we filter this $item->description variable at all? Like through kses?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added FrmAppHelper::kses().

Copy link
Copy Markdown
Contributor

@Crabcyborg Crabcyborg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@truongwp I believe with this update we're planning to also remove the screen options tab, since the options are now all covered with the new gear icon.

Image

@Crabcyborg Crabcyborg added this to the 6.30 milestone Mar 16, 2026
@truongwp
Copy link
Copy Markdown
Contributor Author

@truongwp I believe with this update we're planning to also remove the screen options tab, since the options are now all covered with the new gear icon.

Ok. Let me update the code for that change. Will the Screen Options tab be removed in this PR or in the future?

@truongwp
Copy link
Copy Markdown
Contributor Author

@Crabcyborg This button shows the form description. Will we remove it?

Screenshot 2026-03-17 at 00 32 17

@truongwp
Copy link
Copy Markdown
Contributor Author

@Crabcyborg This button shows the form description. Will we remove it?

Screenshot 2026-03-17 at 00 32 17

That button shows the excerpt, not the full description, so I think we will leave it there and use our own logic.

Now the forms list settings work with the screen options disabled. You can test with this hook add_filter( 'screen_options_show_screen', '__return_false' );

@truongwp truongwp changed the title Redesigned forms table Rock: Redesigned forms list Mar 18, 2026
@truongwp truongwp added the deploy Deploy website on push label Mar 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

deploy Deploy website on push run analysis

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants