Question on autofocus for form field


#1

I have a textarea that I need to autofocus on every time the page loads. I have it working by setting autofocus=“true” on my textarea. The problem is that when I click a link-to and move around the site then back it doesn’t focus. Only when I manually refresh the page. What is the best way to make this always trigger?


#2

Whenever I want to autofocus (assuming this form field is in a component), I use the didInsertElement hook.

...
didInsertElement() {
  this._super(...arguments);
  this.$('[autofocus]').focus();
});
...

Or, without jquery, this should work:

...
didInsertElement() {
  this._super(...arguments);
  this.element.querySelectory('[autofocus]').focus();
});
...

Every time this component is inserted into the dom, it will force it to focus.


#3

Interesting. I do not have a component for the field. All I have is on the page

{{textarea value=model.srdescription class=“form-control form-control-line” autofocus=“true”}}

I’m new to ember. How would I set up a component for this?


#4

Welcome, Chris! The best way to grasp all of the concepts are to go through the component section of the ember guides.

https://guides.emberjs.com/v3.0.0/components/defining-a-component/

Give it some time to steep and make sure to experiment. Lots of folks are trying to make those guides as helpful as possible. Please report back here if there are things that don’t make sense. I’ll try my best to answer them. Then, if you’re interested, we can work together to get the guides improved.


#5

@Chris_Stewart https://github.com/ember-a11y/ember-component-focus focusses (pun intended) on this :slight_smile:


#6

Thanks! I was able to get it working with the component and your examples listed above.

Being new I’m still in the area of where do I do this? Controller, component, route etc. Coming from rails it seems that models and templates operate in a similar way to rails views and models, but where I had one controller in rails I have routes, controllers and components etc. that do similar things. It just takes time.

Thanks for your help.


#7

This is definitely something that will take time. There are lots of varying opinions, which means there is no absolute right answer. So do what makes sense to you now and adjust as your learn.

Some guidelines I tend to follow:

  1. Understand your interface and what parts represent patterns. If you’ll use an element more than once, make it a component. It’s easy to pre-optimize here and make everything a component. It can be a pain to refactor things when you’re jumping around 15 different components.
  2. Controllers are for state and orchestrating interactions between components on a particular page
  3. Routes are for network related actions (Saving models, etc)

These are super general. There is a lot not covered, but hopefully this gives some rough guidance. If you’re using standard API’s you’ll have a path forward, so don’t waste too many cycles on doing it “right.”


#8

Awesome thanks for the info.


#9

Ok I’ve hit another snag with the component. I am now trying to do a createRecord based on the form fields. As soon as I type in the form field I get errors in the console that state:

“Assertion Failed: Cannot call set with ‘srdescription’ on an undefined object.”

And when I submit the form I get the following error:

‘Cannot read property ‘createRecord’ of undefined’

I am guessing the component doesn’t know about the model maybe? If so how would I fix that?

Here is the create-form.hbs

     <form class="form-material m-t-40" onsubmit={{action "saveRequest"}}>
      <div class="form-group">
          <label>Description of Problem</label>
          {{textarea name='srdescription' value=model.srdescription class="form-control form-control-line" autofocus="true"}}
      </div>
        <div class="form-group">
            <label>Requester Name:</label>
            {{input type="text" class="form-control form-control-line" value=model.requester_name}}
       </div>
       <div class="form-group">
           <label>Requester Phone:</label>
           {{input type="text" class="form-control form-control-line" value=model.requester_phone}}
      </div>
     </form>

Here is the create-form.js

    import Component from '@ember/component';

    export default Component.extend({
      didInsertElement() {
        this._super(...arguments);
        this.$('[autofocus]').focus();
      },
      actions: {
        saveRequest(ev) {
          ev.preventDefault();
          let servicerequest = this.store.createRecord('servicerequest', this.model);
          servicerequest.save()
            .then (() => {
            this.transitionToRoute('servicerequest');
          });

        }
      }

    });

In servicerequests.create hbs I call the component like this {{create-form}}

Any thoughts or suggestions are appreciated.


#10

Hey @Chris_Stewart, Could you post this as a new topic? It’s also a good question and the answer would get lost under a different title.


#11

Think of components as isolated scopes. They won’t know of anything unless you tell them about it. There are two ways to give data to components. 1. Attributes and 2. dependency injection. You’re running into issues with both here.

Component attributes

You’re component doesn’t know about model internally because you need to pass it in. Invoking the component with the model attribute will make model available in your component

{{create-form model=model}}

Dependency injection

For certain things, it’s easier to inject them into a component rather than pass them in. These “things” are known as services in ember. In javascript parlance, they are singletons. This means there is a single one that the framework keeps around for you (rather then destroying and recreating it each time you use it, like components). There are many reasons this is really nice. In your example, the store is a service that you will need to inject in order to use within the component.

import Component from '@ember/component';
import { inject as service } from '@ember/service';  // Import the service injector

export default Component.extend({
  store: service(), // Tell the component what service you want to inject. 
  storeService: service('store') // The above is a shorthand. You could also do this
  ....
  this.get('store').createRecord.....
});

Note the usage this.get(). For now, use .get whenever you access properties.

Anyway, I hope that makes sense.


Issue with createRecord in form component
#12

A gentle point of concern- it’s not good for accessibility to auto-focus on a form field- is there a discussion you could have with your UX designer about it?


#13

Basically the page is a form with no other text to read. I was autofocussing on the first field at the top of that form to save someone the step of having to navigate there. Our accessibility team actually recommended that with the added input that I must be able to tab through all areas of the page.

What are the downsides?


#14

Assistive technology like a screen reader, already handles these things - especially if you’re using semantic HTML.

I have a little guide that helps explain visible focus vs screen-reader focus: https://codepen.io/melsumner/full/ZJeYoP/

The main reason you don’t want to force focus to a specific area (necessarily) is that while it might be more convenient for your sighted users, it will take away a lot of context for your users who need assistive tech.


#15

Gothca. Thanks for the information. I appreciate it. I’ll take a look at the guide.