When my component receives an authentication error, I want to display an error to the user.
This part works. I ALSO want to set the focus to the OK button. This part does NOT work.
Looking for answers show a lot of outdated ember versions (I’m using 5.4) that I don’t know how to apply. I assume there’s a quick and easy answer, but I don’t know what it is.
{{#if this.hasAuthenticationError}}
<Bui::FormAlert
@type="error" @isModal={{true}} @subject="Authentication Failed!"
@on-click-away={{this.clearAuthenticationError}}>
<p class="text-sm">The user name you provided is not valid, or the associated account is inactive, or the entered password is incorrect.</p>
<p class="text-sm">Please try again.</p>
<div class="flex justify-center mt-2">
<button id="login-page-error-OK-button" type="button" {{on "click" this.clearAuthenticationError}}
class="bg-gray-100 w-1/2 py-2 px-2 shadow outline-none focus:bg-gray-300 hover:bg-gray-300">OK</button>
</div>
</Bui::FormAlert>
{{/if}}
The OK button is in the contents of a component that is conditionally displayed. So, I think the problem is that the dialog hasn’t rendered yet.
In showAuthenticationError(), the flag, hasAuthenticationError, is set to true, and then immediately, the query for the button that will exist happens before it exists.
How would I test this to see if that’s true? Is there a post update message/function to use?
Is this an actual improvement or is there an easier way? The autofocus modifier only works for base level tags and not components. I have a fancy text box component with a label, error message and fancy border elements, etc. I’m very proud of it!
To get {{autofocus}} to work on components I came up with putting an attribute, focus-target, on the element in the component that should have the focus. Then {{autofocus}} will find that and put the focus there.
But, is there a better way?
import { modifier } from 'ember-modifier';
export default modifier(function autofocus(element, ) {
function singleElement(element) {
switch(element?.tagName) {
case 'INPUT':
case 'TEXTAREA':
case 'BUTTON':
element.focus()
return true
}
return false
}
if( !singleElement(element) ) {
singleElement(element.querySelector( "[focus-target]" ))
}
})```
There are a couple things you can do, although I’m not 100% it’s what fits your needs the best. Off the top of my head…
One option is that you can add modifiers to a component that has splattributes:
// my-fancy-input.hbs
<div> <!-- some container -->
<label>{{@label}}</label>
<input
foo=bar
...attributes <!-- this is the "splattributes", where attributes and modifiers are forwarded -->
>
{{#if @error}}
error here, etc
{{/if}}
</div>
// somewhere else
<MyFancyInput {{autofocus}} />
^ this will get forwarded to the splattributes, so the <input> tag
you could also add the modifier inside your component, and (assuming you dont’ want it to always autofocus) add an extra arg, something like:
// my-fancy-input.hbs
<div> <!-- some container -->
<label ...>{{@label}}</label>
<input
foo=bar
{{autofocus enabled=@autofocus}} <!-- this is where attributes and modifiers are forwarded -->
>
{{#if @error}}
error here, etc
{{/if}}
</div>
// somewhere else
<MyFancyInput @autofocus={{true}} />
yet another option would be to yield the actual input out contextually and then apply the autofocus modifier to it, although that doesn’t seem like what you want.
finally you could whip up some javascript and put it in the component or make the modifier more sophisticated (like you’ve already done)