3

The following screenshot shows a combined form for sign-in and sign-up:

Backbone Form

The following module is used to render the AuthView:

MyApp.module("User", function(User, App, Backbone, Marionette, $, _) {

  User.AuthView = Marionette.ItemView.extend({

    className: "reveal-modal",
    template: "user/auth",

    ui: {
      signInForm: "#signin-form",
      signUpForm: "#signup-form"
    },

    events: {
      "focus input": "onFocus"
    },

    onFocus: function() {
      console.log("Some input field has received focus.");
    },

    onRender: function() {  
      this.signInForm = new Backbone.Form({
        schema: {
          signInEmail: { 
            type: "Text", 
            title: "E-Mail address"
          },
          signInPassword: { 
            type: "Password", 
            title: "Password"
          }
        }
      }).render();
      this.ui.signInForm.prepend(this.signInForm.el);

      this.signUpForm = new Backbone.Form({
        schema: {
          signUpEmail: { 
            type: "Text", 
            title: "E-Mail address"
          },
          signUpPassword: { 
            type: "Password", 
            title: "Password"
          },
          signUpPasswordConfirmation: { 
            type: "Password", 
            title: "Password confirmation"
          }
        }
      }).render();
      this.ui.signUpForm.prepend(this.signUpForm.el);
    }

  });
});

How can I automatically focus the first field in each sub-form whenever it is rendered? The first fields would be signInEmail for the signInForm and signUpEmail for the signUpForm.
I tried to listen to focus input events. Such an event is triggered when I click into one of the input fields, not before.


Meanwhile, inspired by the current answers I came up with the following helper function:

focusFirstFormField: function(form) {
  if (_.isUndefined(form)) {
    throw "IllegalStateException: Form is undefined."
  }
  // TODO: AuthView does not focus first sign-in field.
  var firstInput = form.find(':input:first');
  if (_.isObject(firstInput)) {
    if (firstInput.length > 0) {
      firstInput = firstInput[0];
    }
    else {
      throw "IllegalStateException: Form find returns an empty jQuery object."
    }
  }
  _.defer(function() {
    firstInput.focus();
  });
 }

There is still need for improvement, though.

JJD
  • 50,076
  • 60
  • 203
  • 339

4 Answers4

6

The events object are DOM events which are generally triggered by the user so that's not what you'll likely want to use in this case.

If I'm understanding you correctly you would like to put the focus in the first input of each of the forms but since you can only have focus on one thing at a time and they are rendering together you'll have to choose one or the other.

The simplest option is to add another line at the end of onRender focusing on the input. If your input is generating an input something like this:

<input type="text" name="signInEmail">

Then you can add:

this.$('[name=signInEmail]').focus();

If not you'll have to change the selector this.$(xxxx).focus() to suit.

Evan Hobbs
  • 3,552
  • 5
  • 30
  • 42
  • I prefer the **more generic selection approach** as posted by [Billy Chan](http://stackoverflow.com/a/20462054/356895) and [user2503775](http://stackoverflow.com/a/20465871/356895) but still **+1** for pointing me into the right direction. – JJD Dec 09 '13 at 12:07
4

You can use onDomRefresh event of the view. It will be triggered after view rendered and Dom refreshed.

onDomRefresh: function() {
  this.focusFirstInput();
};

focusFirstInput: function() {
  this.$(':input:visible:enabled:first').focus();
};

This solution applies to general cases. However, pay attention if you are using Bootstrap. I can't get this work there. Instead, I set autofocus: 'autofocus' in the field and it works.

Billy Chan
  • 24,625
  • 4
  • 52
  • 68
  • Thx! I tested both implementations. **Comment part 1:** Setting `autofocus: 'autofocus'` in the `editorAttrs` each individual form `schema` works okay for initial loading but **not on page refresh**. Further, it does not work for more complex multi-page forms where I navigate back and forth showing and hiding the individual subforms. – JJD Dec 09 '13 at 12:01
  • **Comment part 2**: `onDomRefresh` is **really helpful** for setting the initial focus on page reload. Nevertheless, I like to mention that I need to `_.defer()` the `.focus()` call to make this happen. Concerning selection I noticed that `:input:visible:enabled:first` does not work on page reload for me. Using `:input:first` works better though I have some issues with a more complex form where it does **not select the first input form** but another. – JJD Dec 09 '13 at 12:02
  • 1
    @JJD, thanks for your comment. Using `defer()` is smart and helpful :) For the field selector, ignoring `:visible` may work for your case. But it needs attention if the form is Rails style which has hidden csrf field at first. – Billy Chan Dec 09 '13 at 12:09
1

You can add it to onRender method.

this.ui.signInForm.find('input[type=text]:first').focus();
this.ui.signUpForm.find('input[type=text]:first').focus();
user2503775
  • 4,267
  • 1
  • 23
  • 41
  • It works for the form though it does not work for more complex forms where I can navigate between subforms back and forth. Still **+1** for the approach but I will go for a more generic approach. – JJD Dec 09 '13 at 12:04
1

On the onRender method I do :

$(this.el).find(':input[autofocus]').focus();

And I add the autofocus="" flag onto my HTML node. This works for refresh.

firebird631
  • 569
  • 7
  • 10