Open Mottie opened 1 year ago
@Mottie , I know you are a regular DSC attendee. :) But want to check that you are available Friday, April 28 or next Friday, May 5? We might have a meeting this Friday.
I'm on PTO this Friday, but I plan on being at the meeting after.
Follow up actions from DSC discussion on 5/5.
Duplicate check
This update is for:
Pattern
What is the name?
List loop + summary pattern
What is the nature of this update?
What problem does this solve?
When our Supplemental Claim form was designed, we created a few new patterns, one of which is a variation on the add item list loop pattern in that the summary page is the main page and the add item page appears when adding a new entry. This pattern works from the add entry page and shows the summary after additions are made. This change required some additional challenging coding changes which should be documented
Additional Context
TOC:
VA Forms Library - How to add a custom array loop plus summary page
Before you get started, it’s helpful to know how to work with Array Data (aka-list-loops).
This array loop pattern allows you to add entries in a page loop followed by a summary page.
Notes:
?index=2
)CustomPage
with a custom back button to achieve this.Setting up the form config
With this pattern, we're going to set up the
config/form.js
file with two pages. One for filling in array entries and the second showing a summary of all entries. This pattern is reverse of the flow described in How to use "Add item" link in Array Data where you do all the work from the summary page.There are a few important things going on here:
CustomPage
in the page's config renders a custom component.CustomPageReview
in the page's config is for the review & submit page.schema
anduiSchema
are mostly ignored, butuiSchema
can include a'ui:reviewField'
that can take the place of theCustomPageReview
; you'll likely have to deal with focus management in either case.ui:validations
rules must be included on the summary page, so that form submission validation checks are done prior to making the submit API call.useCustomScrollAndFocus
will require that all form pages include a uniqueH3
. And, includingscrollAndFocusTarget
will add additional focus management behavior to a page; but only on initial render. See focus management section for more details._override
to thereviewErrors
so the accordions on the review & submit page show error states correctly.List loop page
Loop header
The main page
H3
should always be unique. You can use thenumberToWords
utility function ('platform/forms-system/src/js/utilities/data/numberToWords
) to render headers with ordinal numbers ("first", "second", "fifth", etc). Even this may not be ideal for header text. Ideally, the header should include a key part of the data from the page, and still be unique across all entries.When the modal appears while trying to navigate back with partial or invalid data, the title needs to also include part of the data from the page. We want screen reader users to know which page they are on when making a decision about keeping or removing the entry.
Loop navigation
We weren't able to figure out now to integrate routing to use indexed paths (
/:index
), which is available withshowPagePerItem
form config. The built-in list loop is set up to work with existing array data, so it won't build a new array on the fly.We opted to set up routing within the page using a URL search parameter (e.g.
?index=2
). When navigating to the loop page from the previous page, the undefined index parameter defaults to zero. We did try to use react-router links to pass data, but it seems like the form system interferes with the process, so the link data is lost. Using this index and the array, we are able to set up the desired routing behavior.The navigation behavior is based on the state of the data:
Here is a summary of the behavior:
Code example:
Loop modals
When the user tries to navigate back from a partially filled out entry (see the loop navigation section), a modal opens and asks if you want to keep or discard the current entry. This adds the ability to remove partial entries without waiting to get to the summary page. The summary page does allow editing and removing entries. If canceled, the focus must return to the back navigation button (the action that initiated the modal). If accepted, move focus to the unique page header (
H3
).If there is a need to limit the number of entries, then show a modal after the Veteran uses the "add another entry" action link. The modal content should contain a message that reports the maximum number of entries has been reached, and include any followup action, e.g. remove one before adding another. Once the modal closes, focus moves back to the add another action link.
Loop validation
On the review & submit page, you'll only show the summary page. If using a
CustomPage
for the list loop, set theCustomPageReview
tonull
. And, don't include anyui:validations
rules on this page.For required fields, you will need to add validation checks within this page. Use the same validation functions that would be used within
ui:validation
(since we're adding it to the summary page), and use a custom function (search the code base forcheckValidations
) to process form errors. And because we show validation errors after a field is blurred, keep track of the fielddirty
status (focused, then blurred), pagesubmit
status (you may or may not use theformContext
for this), and the error state for each field.Make sure to only show the error when a field is blurred. One accessibility issue we encountered was after loop page switching. Focus within a field would fire a
blur
event after (somehow delayed?) a navigation button was clicked, and the page index & content changed. To fix this, we set anisBusy
state before the index change, and ignored anyblur
events when that state istrue
.Loop focus management
Focus management is straight-forward here. Focus and scroll to the
H3
on initial load and after switching pages, or focus and scroll to the first form error.If using the
useCustomScrollAndFocus
feature, the initial focus will be handled by thescrollAndFocusTarget
callback; but page updates and error handling will require using the same callback within the page component (within auseEffect
) to control focus.Focus callback example code
In the page component, call the focus callback:
useEffect
Summary page
Summary headers
If including more than one
H3
on the page, make sure to check the review & submit page heading levels because that page renders the summary inside an accordion with anH3
header. The page title (in form config) will be anH4
with an edit button. After clicking on the edit button, theH4
disappears! We worked around this issue by rendering the sameH4
seen in the review & submit page header on the summary page, but make sure to adjust the heading level.Summary navigation
It is important that this page alters the back button behavior to return the user to the last entry index. Doing this allows the user to navigate back through all the entries. The save-in-progress platform component doesn't maintain the location search (
?index=
) parameter, so the user is always returned to the first loop entry when returning to the loop page after a save.When editing an entry (event on the review & submit page), we opted to always return the user to the indexed page within the form flow (using React router
Link
). This does require them to step through the rest of the form to get back to the review & submit page. Doing this ensures that the form remains valid.If you determine that a better user experience would be to return the user to the review & submit page after editing, then change the navigation buttons to "update" and "cancel" and jump back. In this case, you may need to use session storage to save a flag that the edit occurred on the review & submit page (examine Supplemental Claim's
EditContactInfo
component for example code)Removing an entry requires a modal. See the "Summary modal" section for more detail.
Summary modal
When removing an item, the destructive action needs a confirmation step or screen reader feedback (per collaboration cycle review), before removing the entry. We opted to use a modal.
After choosing a modal action, focus management is important:
Summary validation
On the review & submit page, you'll only want to show the summary page. Make sure to include the schema and
ui:validations
rules for all the data. Doing this will prevent form submission with missing or invalid data, should related data get modified on the review & submit page. For example, in form 0995 (Supplemental Claims), the list loop pages depends on issues selected earlier in the form flow. If the issues get modified on the review & submit page, the changes may alter the list loop data; and any error states within the list loop data must show in the correct accordion.To prevent submission of missing or invalid data, add
ui:validations
rules to the summary page. To ensure the accordion error is shown on the correct chapter, add an_override
callback function to thereviewErrors
form config. This function needs to check the error message, and return the appropriate chapter and page key of the summary page (these keys match theconfig/form
object keys for the chapter & page).Other validations checks you might need to include:
Summary focus management
If using the
useCustomScrollAndFocus
config setting, initial focus will be on the uniqueH3
The only other focus management that needs to be address is with the remove entry modal (see Summary modal section). After the remove button is activated, a modal appears asking if you want to keep or remove the entry:
Use the modal
onPrimaryButtonClick
oronSecondaryButtonClick
callback to apply the focusSummary review page
Summary review headers
On the review & submit page, the
CustomPageReview
is rendered in "review" mode (non-edit mode). Once the edit button is activated, theCustomPage
is rendered, and after the page is updated, we return back to "review" mode (CustomPageReview
). At this point, we must focus on the edit button again.Here's an example we used for the header (see Summary headers to learn why) and the edit button with a reference:
Summary review focus management
To set up the focus on the edit button after editing, save the button edit click to session storage then check it within a
useEffect
so you know to move focus to the edit buttonTesting
Events & callback testing
Currently testing-library React does not support querying elements in the shadow DOM. Inside of the web components, an
__events
object is available and contains the available callback functions to use for testing.Here are some examples on how to use these methods:
In this example, we need to test the page after an input is blurred. Within the component code, we use the field name to determine which field was blurred, but passing in a
target
proves to be difficult, so we add optional chaining to thetarget
and fallback on the eventdetail
Then in the unit test, we create an event with the field name in the detail:
Focus testing
If using testing-library/react, querying elements inside the shadow DOM isn't possible, so you can either skip these tests, or use the enzyme library to test the focus.