Add a UUID function to create domIDs for forms and ARIA attributes

Bug #2018410 reported by Stephanie Leary
6
This bug affects 1 person
Affects Status Importance Assigned to Milestone
Evergreen
New
Undecided
Unassigned

Bug Description

We have a number of accessibility bugs related to form labels and ARIA attributes where we need to reference another element by ID. Angular does not provide a built-in way to access its encapsulation IDs, so we have several methods of generating IDs throughout Evergreen. It would be great to standardize this so that it's easier for devs to use IDs consistently in a way that lets us add labels and ARIA attributes painlessly when we need them.

https://www.npmjs.com/package/angular2-uuid exists and we could add it to our packages. However, Mike Rylander suggests that rather than add a dependency, we might just rip the smallest necessary function out of this and incorporate it into our core module, since that's already included everywhere.

Revision history for this message
Bill Erickson (berick) wrote :

We can also create app-wide unique values with static variables. Something along the lines of:

export class IdGen {
    static id = 0;
    static next(): number {
        return ++IdGen.id
    }
}

<span id={{IdGen.next()}}/> <!-- or similar / untested -->

Revision history for this message
Bill Erickson (berick) wrote :

Here's one possible solution:

https://git.evergreen-ils.org/?p=working/Evergreen.git;a=shortlog;h=refs/heads/user/berick/lp2018410-dom-auto-id

Sandbox example usage:

<label [for]="myInput.id">I'm a Label</label>
<input #myInput [id]="autoId.next(myInput)"/>

It's an interesting issue to solve, because linking two elements (e.g. label + input) means there's has to be a shared reference between the two -- "myInput" in this case. It sort of defeats the purpose, because you are manually giving one element an identifier (#myInput), just so it can generate another identifier (e.g. DOM ID "auto-1"). I.e. the first identifier (#myInput) could just as well be the DOM ID. Not sure if there's a way around that shared ref requirement... In any event, this approach does provide some consistency and forces the IDs to be unique.

Changed in evergreen:
assignee: nobody → Stephanie Leary (stephanieleary)
Changed in evergreen:
assignee: Stephanie Leary (stephanieleary) → nobody
Revision history for this message
Stephanie Leary (stephanieleary) wrote :

The #myInput template identifier leads to lifecycle errors in every case I've tried. Mike and I worked on this for a while and could not find a good way to resolve them; I think we're just going to have to abandon that approach. (Bill and I discussed this at Hack-a-way, but I should leave a comment here for everyone else!)

I did some more digging and found this suggestion to create a let-* directive that can be used anywhere, not just in *ngIf and *ngFor.
https://medium.com/@AustinMatherne/angular-let-directive-a168d4248138

This would require us to have a wrapping element around our input/label pair, but we often have that anyway, and we can use <ng-container> if we really need to avoid extra markup.

We'd still need to pair it with the UUID package, so we'd end up with something like:

<ng-container *ngLet="domId = uuid()">
  <label for="{{domId}}">Label</label>
  <input id="{{domId}}" value="whatever">
</ng-container>

Not sure of the exact syntax there (and the author notes that using the ng prefix is bad), but I wanted to throw this out for opinions on the general idea.

Revision history for this message
Bill Erickson (berick) wrote (last edit ):

Stephanie, I suspect the *ngLet approach would lead to a stream of:

ExpressionChangedAfterItHasBeenCheckedError

(Are these the lifecycle errors you were referring to?)

... since the value of domId would change with every change detection cycle.

===

Here's another approach that might work. Kind of like yours, it requires a wrapper, but the wrapper has to be a real element, since it uses the sibling relationship of the DOM nodes to apply the correct attributes.

https://git.evergreen-ils.org/?p=working/Evergreen.git;a=commitdiff;h=e44501b54ca88ec75ed758d341d5e8729b1ee9dc

(Note to self, more code comments)

Bill Erickson (berick)
Changed in evergreen:
assignee: nobody → Bill Erickson (berick)
Revision history for this message
Bill Erickson (berick) wrote :

I pushed another commit to make the linking more flexible, add some docs. It's now based on the ancestor/child relationship, so there's more flexibility in the DOM node layout. Example:

  <div egAutoAttr class="row row-cols-auto">
    <div class="col-2">
      <label egAutoAttrTarget attribute="for" class="col" i18n>Label with Auto "for"</label>
    </div>
    <div class="col">
      <input egAutoAttrTarget attribute="id" type="text" class="col form-control"/>
    </div>
  </div>

Branch:

https://git.evergreen-ils.org/?p=working/Evergreen.git;a=shortlog;h=refs/heads/user/berick/lp2018410-linked-dom-ids

Changed in evergreen:
assignee: Bill Erickson (berick) → nobody
To post a comment you must log in.
This report contains Public information  
Everyone can see this information.

Other bug subscribers

Remote bug watches

Bug watches keep track of this bug in other bug trackers.