-
Notifications
You must be signed in to change notification settings - Fork 152
enhance: row population option for repeat field #1696
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
enhance: row population option for repeat field #1696
Conversation
Removed redundant dynamic :name bindings from radio field templates in both Vue and PHP templates for consistency. Enhanced admin form builder table styles with new classes for hover, zebra striping, sticky headers/footers, and improved RTL support. Simplified repeat field migration logic in wpuf_get_form_fields to ensure inner_fields is always a simple array, improving compatibility and maintainability.
Refactored the repeat field controls to dynamically create add/remove buttons, ensuring correct visibility and preventing rapid updates. Added MutationObserver to monitor button attribute changes and re-apply visibility logic after field initialization, improving reliability and consistency of repeat field UI in the admin posting interface.
WalkthroughThis PR changes radio input name bindings in templates, adds new table and bottom navigation CSS utilities, overhauls admin repeat-field rendering with MutationObserver-driven controls and reindexing, and simplifies repeat-field normalization in wpuf_get_form_fields by flattening inner_fields and removing multi-column conversion logic. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Poem
✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (9)
assets/js-templates/form-components.php (1)
509-513
: Radio group name change: verify uniqueness to avoid cross-group interference.Switching to
:name="option_field.name"
is fine if eachoption_field.name
is unique within the open options panel. If two radio option groups share the samename
, selecting one will deselect the other at the browser level.
- Action: Verify that no two radio options rendered simultaneously share the same
option_field.name
.- Optional hardening: suffix the editing field id to guarantee uniqueness.
Apply if desired:
-:name="option_field.name" +:name="option_field.name + '_' + editing_form_field.id"Also applies to: 524-529
includes/Admin/Posting.php (3)
688-729
: Hide “Add” button when max repeats reached.Right now, at max count the “+” button still shows but does nothing. Read
data-max-repeats
here and hide the add button to avoid UX confusion.Apply this diff:
- updateRepeatButtons: function($container) { + updateRepeatButtons: function($container) { var $instances = $container.find('.wpuf-repeat-instance'); var count = $instances.length; + var maxRepeats = parseInt($container.data('max-repeats')) || -1; @@ - // Add button: show only on last instance - if (isLast) { + // Add button: show only on last instance AND when under maxRepeats (if set) + var canAddMore = (maxRepeats === -1) || (count < maxRepeats); + if (isLast && canAddMore) { $controls.append(addButtonHtml); }
607-626
: IDs/labels may collide across instances when ids don’t use bracketed indexes.You only rewrite
id
/for
when they contain[...]
. If an element’s id uses another scheme (e.g., hyphen-suffixed), clones can end up with duplicate ids and mismatched labels.
- Option: store a
data-base-id
/data-base-for
on first render and derive unique ids from it during cloning/reindexing.
734-765
: MutationObserver scope/targets could be simplified.Observing attribute changes on individual buttons is brittle (buttons are rebuilt) and may miss updates. Either:
- Observe the container with
{childList: true, subtree: true}
and call a debouncedupdateRepeatButtons
, or- Drop the observer and rely on explicit
updateRepeatButtons
calls (you already call it after add/remove/init).assets/css/admin/form-builder.css (5)
1969-1976
: Intersection z-index for pinned header cell.The cell at the intersection of pinned rows and pinned columns should sit above both. Without it, edges can tear during scroll.
Apply this diff right after the sticky header rule:
background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); } +/* Ensure header+pinned intersection stays above */ +.wpuf-table :where(.wpuf-table-pin-rows):where(.wpuf-table-pin-cols) thead tr :is(th, td):first-child { + z-index: 2; +}
927-937
: Hover state doesn’t repaint pinned cells.Row hover updates the row background but sticky pinned cells keep their base bg, producing a visual seam. Mirror the hover color onto pinned cells too.
.wpuf-table tr.wpuf-hover:hover, .wpuf-table tr.wpuf-hover:nth-child(even):hover { --tw-bg-opacity: 1; background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); } + + .wpuf-table tr.wpuf-hover:hover :where(.wpuf-table-pin-cols) :is(th, td) { + --tw-bg-opacity: 1; + background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); + } .wpuf-table-zebra tr.wpuf-hover:hover, .wpuf-table-zebra tr.wpuf-hover:nth-child(even):hover { --tw-bg-opacity: 1; background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity))); } + .wpuf-table-zebra tr.wpuf-hover:hover :where(.wpuf-table-pin-cols) :is(th, td) { + --tw-bg-opacity: 1; + background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity))); + }
1960-1967
: Use logical alignment to simplify RTL.Switch
text-align: left
totext-align: start
so it auto-flips in RTL without extra overrides..wpuf-table { position: relative; width: 100%; border-radius: var(--rounded-box, 1rem); - text-align: left; + text-align: start; font-size: 0.875rem; line-height: 1.25rem; }
2828-2831
: Redundant RTL text-align override.If you adopt
text-align: start
above, this RTL-specific override becomes unnecessary. Remove to reduce cascade complexity.-.wpuf-table:where([dir="rtl"], [dir="rtl"] *) { - text-align: right; -}
4081-4084
: Confirm.wpuf-table { display: table; }
won’t wrap a native<table>
.If
.wpuf-table
is applied to a wrapper div around a real<table>
, forcing the wrapper todisplay: table
can cause unexpected box metrics and interfere with layout/overflow. If the class is meant for the<table>
element itself, all good; otherwise consider scoping to.wpuf-table table { display: table; }
.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (5)
admin/form-builder/assets/js/components/field-radio/template.php
(0 hunks)assets/css/admin/form-builder.css
(6 hunks)assets/js-templates/form-components.php
(2 hunks)includes/Admin/Posting.php
(4 hunks)wpuf-functions.php
(1 hunks)
💤 Files with no reviewable changes (1)
- admin/form-builder/assets/js/components/field-radio/template.php
🔇 Additional comments (3)
assets/js-templates/form-components.php (2)
24-27
: No-op cleanup looks good.Whitespace-only change; no behavioral impact.
123-126
: No-op cleanup looks good.Whitespace-only change; no behavioral impact.
assets/css/admin/form-builder.css (1)
2980-3030
: Bottom-nav size utilities look good.Consistent height and label typography scale across xs/sm/md/lg; naming matches existing utility patterns.
.wpuf-table :where(.wpuf-table-pin-cols tr th) { | ||
position: sticky; | ||
left: 0px; | ||
right: 0px; | ||
--tw-bg-opacity: 1; | ||
background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); | ||
} | ||
|
||
.wpuf-table-zebra tbody tr:nth-child(even) :where(.wpuf-table-pin-cols tr th) { | ||
--tw-bg-opacity: 1; | ||
background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pinned column rule likely broken (left and right set simultaneously) and excludes td
.
Setting both left: 0
and right: 0
on sticky cells will stretch them and cause overlap; also only th
is targeted (not td
). Prefer logical properties and include both header/body cells. Add a stacking context for pinned cells.
Apply this diff:
-.wpuf-table :where(.wpuf-table-pin-cols tr th) {
- position: sticky;
- left: 0px;
- right: 0px;
- --tw-bg-opacity: 1;
- background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
-}
+.wpuf-table :where(.wpuf-table-pin-cols tr :is(th, td)) {
+ position: sticky;
+ inset-inline-start: 0;
+ z-index: 1;
+ --tw-bg-opacity: 1;
+ background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
+}
-.wpuf-table-zebra tbody tr:nth-child(even) :where(.wpuf-table-pin-cols tr th) {
+.wpuf-table-zebra :where(.wpuf-table-pin-cols) tbody tr:nth-child(even) :is(th, td) {
--tw-bg-opacity: 1;
background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
.wpuf-table :where(.wpuf-table-pin-cols tr th) { | |
position: sticky; | |
left: 0px; | |
right: 0px; | |
--tw-bg-opacity: 1; | |
background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); | |
} | |
.wpuf-table-zebra tbody tr:nth-child(even) :where(.wpuf-table-pin-cols tr th) { | |
--tw-bg-opacity: 1; | |
background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); | |
} | |
.wpuf-table :where(.wpuf-table-pin-cols tr :is(th, td)) { | |
position: sticky; | |
inset-inline-start: 0; | |
z-index: 1; | |
--tw-bg-opacity: 1; | |
background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); | |
} | |
.wpuf-table-zebra :where(.wpuf-table-pin-cols) tbody tr:nth-child(even) :is(th, td) { | |
--tw-bg-opacity: 1; | |
background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); | |
} |
🤖 Prompt for AI Agents
In assets/css/admin/form-builder.css around lines 1985 to 1996, the
pinned-column rule sets both left:0 and right:0 (which stretches/stacks cells)
and only targets th, excluding td; change the rule to use logical
inset-inline-start (or left) only (remove right/right:auto), expand the selector
to include both th and td (e.g. :where(.wpuf-table-pin-cols tr th,
.wpuf-table-pin-cols tr td)), and add a stacking context (z-index and/or
transform: translateZ(0) or will-change) so pinned cells sit above other content
while preserving background color and opacity variables.
wpuf-functions.php
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Flattening repeat inner_fields drops columns > column-1 (data loss/regression risk).
Current logic keeps only column-1
and discards column-2
, column-3
, etc. Existing multi-column repeat fields would silently lose inner fields.
- Fix: flatten all column buckets in order instead of only
column-1
.
Apply this diff:
- // Ensure inner_fields is a simple array (not column structure)
- if ( empty( $field['inner_fields'] ) ) {
- $field['inner_fields'] = [];
- } elseif ( isset( $field['inner_fields']['column-1'] ) ) {
- // Convert column structure to simple array
- $field['inner_fields'] = $field['inner_fields']['column-1'];
- }
+ // Ensure inner_fields is a simple array (not column structure)
+ if ( empty( $field['inner_fields'] ) ) {
+ $field['inner_fields'] = [];
+ } elseif ( is_array( $field['inner_fields'] ) && isset( $field['inner_fields']['column-1'] ) ) {
+ // Convert column structure to simple array by concatenating all columns
+ $columns = $field['inner_fields'];
+ // keep natural order: column-1, column-2, ...
+ ksort( $columns, SORT_NATURAL );
+ $flat = [];
+ foreach ( $columns as $col => $items ) {
+ if ( is_array( $items ) ) {
+ foreach ( $items as $it ) {
+ $flat[] = $it;
+ }
+ }
+ }
+ $field['inner_fields'] = $flat;
+ }
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
// Ensure inner_fields is a simple array (not column structure) | |
if ( empty( $field['inner_fields'] ) ) { | |
$old_id = $field['id']; | |
$old_meta = $field['name']; | |
$old_label = $field['label']; | |
$new_id = wpuf_form_field_id_generator(); | |
$field['template'] = 'text_field'; | |
// set the new compatible values | |
$field['id'] = $new_id; | |
$field['name'] = $old_meta . '_' . $new_id; | |
$field['label'] = ''; | |
$field['inner_fields']['column-1'] = [ $field ]; | |
$field['inner_fields']['column-2'] = []; | |
$field['inner_fields']['column-3'] = []; | |
$field['template'] = 'repeat_field'; | |
$field['columns'] = 1; | |
$field['min_column'] = 1; | |
$field['max_column'] = 3; | |
$field['column_space'] = 5; | |
$field['id'] = $old_id; | |
$field['label'] = $old_label; | |
$field['name'] = $old_meta; | |
} | |
// if old repeat field format | |
if ( empty( $field['inner_columns_size'] ) ) { | |
$field['inner_columns_size']['column-1'] = '100%'; | |
$field['inner_columns_size']['column-2'] = '100%'; | |
$field['inner_columns_size']['column-3'] = '100%'; | |
$field['inner_fields'] = []; | |
} elseif ( isset( $field['inner_fields']['column-1'] ) ) { | |
// Convert column structure to simple array | |
$field['inner_fields'] = $field['inner_fields']['column-1']; | |
} | |
// Ensure inner_fields is a simple array (not column structure) | |
if ( empty( $field['inner_fields'] ) ) { | |
$field['inner_fields'] = []; | |
} elseif ( is_array( $field['inner_fields'] ) && isset( $field['inner_fields']['column-1'] ) ) { | |
// Convert column structure to simple array by concatenating all columns | |
$columns = $field['inner_fields']; | |
// keep natural order: column-1, column-2, ... | |
ksort( $columns, SORT_NATURAL ); | |
$flat = []; | |
foreach ( $columns as $col => $items ) { | |
if ( is_array( $items ) ) { | |
foreach ( $items as $it ) { | |
$flat[] = $it; | |
} | |
} | |
} | |
$field['inner_fields'] = $flat; | |
} |
🤖 Prompt for AI Agents
In wpuf-functions.php around lines 1857 to 1863 the current logic flattens
repeat inner_fields by keeping only 'column-1', which drops 'column-2',
'column-3', etc.; change the code to detect a column-bucketed structure and
merge all column-* buckets in numeric order into a single simple array
(preserving field order), e.g. collect keys matching /^column-(\d+)$/, sort by
the captured index, then append each bucket's entries into
$field['inner_fields']; also ensure the existing empty/already-simple-array
branches remain unchanged so non-column structures are not altered.
part of #1038
related PR
Currently, the repeat field is supporting/repeating the whole field set, we are now supporting a single row-wise repeat of fields.
Summary by CodeRabbit
New Features
Bug Fixes
Refactor