Octane, trying to set the focus to an OK button on a 'modal' dialog

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.

showAuthenticationError() {
    this.hasAuthenticationError = true
    let okbutton = this.element.querySelector('#login-page-error-OK-button')
    okbutton.foucus()
}
{{#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}}

I think there are a couple things you could do here. As the error states this.element no longer exists, so you can:

  1. just query the document instead, if you’re sure that is specific enough
    let okbutton = document.querySelector('#login-page-error-OK-button')
  1. assign an element id manually to your component, and scope your query based on the component first then the button
  2. write a simple modifier which could focus it’s element when (for example) a condition is met
        <button id="login-page-error-OK-button" type="button" {{on "click" this.clearAuthenticationError}} {{focus-when this.hasAuthenticationError}} ...>

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?

Actually reading the code again can you just skip all the js and add the autofocus attribute to the button so it focuses when rendered?

Sadly, no, that doesn’t work.

<button id=“login-page-error-OK-button” autofocus type=“button” {{on “click” this.clearAuthenticationError}}

However, I found this gem on your site.

{{autofocus}}

Template Lifecycle, DOM, and Modifiers - Components - Ember Guides (emberjs.com)

Ah nice that would have been my next suggestion

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! :slight_smile:

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)

1 Like

Thank you, I will try these out!