Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@mahoneycm
Copy link
Contributor

@mahoneycm mahoneycm commented Jun 1, 2023

Summary

Refactored Modal JavaScript for added consistency and failsafe measures. This resolves issues when initialization is called more than once on a document.

Breaking change

This is not a breaking change.

Related issue

Closes #5062

Related pull requests

Changelog PR →

Continued from #5208

Preview link

Preview link:
Modal →

Problem statement

From #5062:

In JS frameworks (React, Vue, Angular) when multiple instances of a dynamic uswds components are dynamically created (modals, datepickers...), calling init/on in the create lifecycle and off in the destroy lifecycle is often buggy and results in multiple inits or off() on components that are already torn down.

After inspecting Modal JS, we saw room for improvement to align JS to be more consistent with other Dynamic Components, including adding error messages if key pieces are missing from component code.

Solution

Refactor Modal JS to:

  • Be more consistent with other dynamic USWDS components
  • Break out large setUpModal() function into more palatable, feature-specific functions
  • Add error's when component code is missing key pieces such as id, aria-labelledby, and aria-describedby.

Testing and review

Review

Code Refactor:

  1. Visit modal component page
  2. Confirm no visual or functional regression
  3. Review updated JS
    • Confirm code follows standard dynamic component patterns
    • Check for possible improvements regarding organization and variable names.
  4. On your local, remove ID, aria-labelledby, and/or aria-describedby from usa-modal, allow the preview to rebuild, then inspect the component preview.
  5. Confirm error message in console.

Multiple init in JS Frameworks

  1. Checkout this PR and run npm link
  2. Clone this test repo
  3. Run npm install npm run start
  4. Confirm error on the multiple models page
  5. Return to code and run npm link @uswds/uswds
  6. Return to app and confirm modals work correctly.
  7. The "two modals" page will have a console error because init is called a second time (once per modal)
    • The second init searches for usa-modal class, which has been moved to hidden modal window element
    • This element does not have an ID, which is necessary for component code to dynamically create modal component
    • Initialization is then stopped, preventing the error we saw before.
    • Ideally, the user would not call initialization more than once on each modal, which can be don't by setting the target root

Testing

  • No visual or functional regressions to modal.
  • Component uses behavior pattern.
  • Removing ID, aria-labelledby, and aria-describedby from usa-modal triggers error in console.
  • Resolves multiple init bug caused when initiating multiple Modals using a JS framework (Follow test repo instructions above).

Before opening this PR, make sure you’ve done whichever of these applies to you:

  • Confirm that this code follows the 18F Front End Coding Style Guide and Accessibility Guide.
  • Run git pull origin [base branch] to pull in the most recent updates from your base and check for merge conflicts. (Often, the base branch is develop).
  • Run npm test and confirm that all tests pass.

@mahoneycm
Copy link
Contributor Author

@mejiaj @amyleadem Bumping for review when you have the bandwidth!

Copy link
Contributor

@mejiaj mejiaj left a comment

Choose a reason for hiding this comment

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

Good work here! I've added additional comments & suggestions below.

What I tested in sequential order:

  • PR description: Is PR description accurate and clear. Added suggestion below.
  • No Regressions: Have there been any regressions in visuals/functionality.
  • New features: Removing ID, aria-labelledby, and aria-describedby from usa-modal triggers error in console.
  • Code: Does it meet USWDS code standards for formatting and code style.

PR description

The items I tested related to process. Added suggestions below.

What I tested:

  • Changelog PR exists & is linked.
  • Preview link exists & is accurate.
  • Testing & Review is clear & reflect changes.
    • ⚠️ This is a complex PR, so we could improve clarity by adding a new list that has expectations.

Suggestions for Testing & Review section:

### Review

1. Steps.
2. To.
3. Review.

### Testing

- [ ] No visual or functional regressions to modal.
- [ ] Component uses behavior pattern.
- [ ] Removing `ID, aria-labelledby, and aria-describedby` from `usa-modal` triggers error in console.
- [ ] Repo instructions ― I haven't gone through these steps.

Note
This would just be a new section that only highlights expectations for this work to be considered "done."

Code

In general, please be sure to add JSDoc comments to functions.

) {
const attribute = modalContent.attributes[attributeIndex];

modalAttributes.forEach((attribute) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice improvement!

modalWrapper.appendChild(modalContent);
modalContent.parentNode.insertBefore(overlayDiv, modalContent);
overlayDiv.appendChild(modalContent);
const setModalAttributes = (baseComponent, targetWrapper) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

  1. Can we add a JSDoc comment to this and any new functions?
  2. Does targetWrapper mean the modal content wrapper?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Resolved in 0e34893

and yes! targetWrapper is the wrapper made for the individual modal component on init

Copy link
Contributor

@amyleadem amyleadem Jul 25, 2023

Choose a reason for hiding this comment

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

Would it make sense to rename targetWrapper to something more descriptive like modalContentWrapper?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this adds clarity! I'll add it

Comment on lines 247 to 250
modalContent.parentNode.insertBefore(modalWrapper, modalContent);
modalWrapper.appendChild(modalContent);
modalContent.parentNode.insertBefore(overlayDiv, modalContent);
overlayDiv.appendChild(modalContent);
Copy link
Contributor

@mejiaj mejiaj Jul 6, 2023

Choose a reason for hiding this comment

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

Is there any way to simplify these (lines 247-50) to make it easier to track what's being appended? Here's what I observed when testing:

  1. Go to usa-modal parent, in this component library it's div.margin-y-3.
  2. Empty div is created and immediately inserted.
  3. Another empty div is created inside as a sibling to usa-modal.
  4. Finally we've appended empty divs to DOM and modifying their classes ― ⚠️ this doesn't follow the usual pattern of creating DOM and necessary attributes in JS.

It would make more sense to:

  1. Create elements.
  2. Add attributes.
  3. Ensure structure is what we want.
  4. Append fully scaffolded HTML to DOM.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In this specific example, we need the parent overlay div on the wrapper so that setModalAttributes() can properly set up the modal's closers.

So the process looks a little more like:

  1. Create elements
  2. Add classes
  3. Structure overlay, wrapper, and content in correct order
  4. Set attributes
  5. Append fully scaffolded HTML to DOM

Take a look at this updated code and let me know what you think!

Updated in 270c293

@mahoneycm mahoneycm requested a review from mejiaj July 11, 2023 18:16
Copy link
Contributor

@mejiaj mejiaj left a comment

Choose a reason for hiding this comment

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

Nice work. I've tested the following:

  • No visual regressions.
  • No functional regressions.
  • No a11y regressions.
  • New features: Errors when missing id, aria-labelledby, aria-describedby.
  • No issues with multiple modals; tested two modals on same page.

@mahoneycm
Copy link
Contributor Author

@amyleadem bumping for review when you have time!

Copy link
Contributor

@amyleadem amyleadem left a comment

Choose a reason for hiding this comment

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

This is looking good! Had just a few notes about opportunities to improve the comments for quicker understanding.

I performed the following checks:

  • Confirm that multiple modals are on a page works in the test repo
  • Confirm no visual regressions in Storybook
  • Confirm no functional regressions in Storybook
  • Confirm that removing id, aria-labelledby, or aria-describedby attributes causes console error
  • Confirm that code style and comments meet standards
    • Added some comments about adding detail to the function comments
  • Confirm changelog
    • Added some comments to the changelog PR

modalWrapper.appendChild(modalContent);
modalContent.parentNode.insertBefore(overlayDiv, modalContent);
overlayDiv.appendChild(modalContent);
const setModalAttributes = (baseComponent, targetWrapper) => {
Copy link
Contributor

@amyleadem amyleadem Jul 25, 2023

Choose a reason for hiding this comment

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

Would it make sense to rename targetWrapper to something more descriptive like modalContentWrapper?

@mahoneycm mahoneycm requested a review from amyleadem July 26, 2023 20:02
Copy link
Contributor

@amyleadem amyleadem left a comment

Choose a reason for hiding this comment

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

Thanks for adding those clarifying comments, @mahoneycm. Looks good to me!

I performed the following checks:

  • Confirm that multiple modals are on a page works in the test repo
  • Confirm no visual regressions in Storybook
  • Confirm no functional regressions in Storybook
  • Confirm that removing id, aria-labelledby, or aria-describedby attributes causes console error
  • Confirm that code style and comments meet standards
  • Confirm changelog

@amyleadem amyleadem requested a review from thisisdano July 28, 2023 18:47
const modalContent = baseComponent;
const modalWrapper = document.createElement("div");
const overlayDiv = document.createElement("div");
const createPlaceHolder = (baseComponent) => {
Copy link
Contributor

@mejiaj mejiaj Aug 2, 2023

Choose a reason for hiding this comment

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

Explore cloneNode() [MDN] to allow users to add their own event listeners to modal content. Created a new issue for this #5419.

mejiaj
mejiaj previously requested changes Oct 11, 2023
Copy link
Contributor

@mejiaj mejiaj left a comment

Choose a reason for hiding this comment

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

@mahoneycm can you resolve conflicts, otherwise LGTM?

@mahoneycm
Copy link
Contributor Author

@mejiaj conflicts resolved! 👍

Amy's work in #5493 is included and working as expected

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

USWDS - Bug: Bad inits and teardowns on Uswds components when multiple instances in template in modern JS frameworks

5 participants