Skip to content

fix: [DHIS2-21286] fix scrolling to first validation error in tracker/event forms#4592

Draft
karolinelien wants to merge 2 commits into
masterfrom
fix/DHIS2-21286-validation-error-scroll-positioning-v2
Draft

fix: [DHIS2-21286] fix scrolling to first validation error in tracker/event forms#4592
karolinelien wants to merge 2 commits into
masterfrom
fix/DHIS2-21286-validation-error-scroll-positioning-v2

Conversation

@karolinelien

@karolinelien karolinelien commented May 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes two bugs that caused the scroll-to-first-error on save to land in the wrong place. Affects every entry point that uses withSaveHandler (TE registration, enrollment registration, single-event registration, new event in enrollment, edit event) — i.e. both tracker and event programs.

  • B1 — D2Form section data elements never scrolled. withGotoInterface was wrapped in forwardRef during the React 18 migration (09ed283). That redirected the parent's ref from GotoFieldInterface to the inner field component, which doesn't expose a goto() method, so instance.goto && instance.goto() in D2Form.validateFormScrollToFirstFailedField silently no-op'd for every section field. Drop the forwardRef wrapper — no consumer reads the forwarded ref. This explains the pre-fix asymmetry where save would scroll to OccurredAt (which uses DataEntryField's own goto()) but not to a section mandatory DE.
  • B2 — top-of-form fields (e.g. OccurredAt) landed behind the sticky ScopeSelector bar. Both goto() implementations did scrollIntoView() and then post-corrected with window.scroll(0, scrolledY - 48). The if (scrolledY) guard skipped the correction when scrolledY === 0 (which is exactly what happens when the field is near the top of the document), leaving the field flush with the viewport top — under the sticky ScopeSelector. Replace with scrollIntoView({ block: 'start' }) + scrollMarginTop: '80px' on the wrapper so the browser lands the field 80px below the viewport top regardless of position. 48px covers the SelectorBar height, the remaining 32px is breathing room above the label.

Same fix pattern applied to both call sites (withGotoInterface for in-section data elements and DataEntryField for top-of-form fields).

Test plan

Save is always at the bottom of the form, so the only scroll direction validation needs to drive is up.

  • Tracker program stage, OccurredAt empty, click Save → page scrolls UP, OccurredAt label clear of ScopeSelector (tests B2 via DataEntryField)
  • Tracker program stage, mandatory section data element empty, click Save → page scrolls UP, that field's label clear of ScopeSelector (tests B1 — broken on master)
  • Event program (single-event registration), OccurredAt empty, Save → scrolls UP, label clear of ScopeSelector
  • Event program (single-event registration), mandatory section DE empty, Save → scrolls UP, label clear of ScopeSelector (tests B1 on the event-program path)

AI Assisted

…ector

Two bugs caused validation-failure scroll to land in the wrong place:

1. withGotoInterface wrapped its class in forwardRef during the React 18
   migration (09ed283). The outer ref then pointed at the inner field
   component, not GotoFieldInterface, so .goto() silently no-op'd on every
   D2Form section field. Drop the wrapper - no consumer reads the
   forwarded ref.

2. Both goto() implementations did scrollIntoView() then post-corrected
   with window.scroll(0, scrolledY - 48). The guard skipped the correction
   whenever scrolledY === 0 (e.g. OccurredAt near top of doc), leaving the
   field flush with the viewport top - and behind the sticky ScopeSelector
   bar. Use scrollIntoView({block:'start'}) + scrollMarginTop:80px on the
   wrapper so the browser lands the field 80px below the viewport top
   regardless of position.

AI Assisted

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

@karolinelien karolinelien changed the title fix: [DHIS2-21286] scroll first failed field clear of sticky ScopeSel… fix: [DHIS2-21286] fix scrolling to first validation error in tracker/event forms May 28, 2026
@JohanGHole JohanGHole force-pushed the fix/DHIS2-21286-validation-error-scroll-positioning-v2 branch from e8991b3 to 85c9e42 Compare June 3, 2026 08:10
@Philip-Larsen-Donnelly Philip-Larsen-Donnelly force-pushed the fix/DHIS2-21286-validation-error-scroll-positioning-v2 branch from 85c9e42 to e8991b3 Compare June 3, 2026 14:41
@sonarqubecloud

sonarqubecloud Bot commented Jun 3, 2026

Copy link
Copy Markdown

PR #4592 fixed the single-section case, but multi-section forms (common in
event programs) still failed to scroll when the empty mandatory field was
not in the first section.

Two issues in D2Form:

1. validateFormIncludeSectionFailedFields short-circuited with .every(). A
   section's isValid() inspects the form-wide field UI state (keyed by
   formId), so a valid earlier section reports invalid when a later section
   has the failing field. The short-circuit stopped there and collected that
   section's getInvalidFields() (empty for it), leaving failedFields empty
   and no field to scroll to. Evaluate every section with
   .map(...).every(Boolean) so the section that owns the failing field
   always contributes it.

2. validateFormScrollToFirstFailedField only tried failedFields[0]. Fields
   that register no instance (e.g. plugins) at the head of the list would
   no-op the scroll. Find the first failed field that exposes goto().

Export the D2Form class and add a regression test covering the multi-section
case, no-goto fields, all-valid, and hidden sections.

AI Assisted

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sonarqubecloud

sonarqubecloud Bot commented Jul 1, 2026

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
2 New issues
2 New Code Smells (required ≤ 0)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant