Vue JS focus next input on enter
Asked Answered
D

7

29

I have 2 inputs and want switch focus from first to second when user press Enter. I tried mix jQuery with Vue becouse I can't find any function to focus on something in Vue documentation:

<input v-on:keyup.enter="$(':focus').next('input').focus()"
    ...>
<input  ...>

But on enter I see error in console:

build.js:11079 [Vue warn]: Property or method "$" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option. (found in anonymous component - use the "name" option for better debugging messages.)warn @ build.js:11079has @ build.js:9011keyup @ build.js:15333(anonymous function) @ build.js:10111
build.js:15333 Uncaught TypeError: $ is not a function
Dexamethasone answered 2/10, 2016 at 19:27 Comment(4)
I don't know the answer but please, try this v-on:keyup.enter="this.nextSibling.focus()"Raphael
Thanks for helping! Result is build.js:15333 Uncaught TypeError: Cannot read property 'focus' of undefinedDexamethasone
Your second input has an ID ? <input id="XX" ...> . Try to add one and change this line v-on:keyup.enter="document.getElementById('XX').focus()"Raphael
I can add it if needed. If I will able execute vanilla JS in vue click handler I can make document.getElementById('next_id').focus(); but it again gives build.js:11079 [Vue warn]: Property or method "getElementById" is not defined on the instance but referenced during render.Dexamethasone
R
34

You can try something like this:

<input v-on:keyup.enter="$event.target.nextElementSibling.focus()" type="text">

JSfiddle Example

Update

In case if the target element is inside form element and next form element is not a real sibling then you can do the following:

html

<form id="app">
  <p>{{ message }}</p>

  <input v-on:keyup.enter="goNext($event.target)" type="text">
  
  <div>
      <input type="text">
  </div>

</form>

js

new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue.js!',
    focusNext(elem) {
      const currentIndex = Array.from(elem.form.elements).indexOf(elem);
      elem.form.elements.item(
        currentIndex < elem.form.elements.length - 1 ?
        currentIndex + 1 :
        0
      ).focus();
    }
  }
})

JSFiddle Example

Rebate answered 2/10, 2016 at 20:0 Comment(3)
Thanks - that fiddle no longer worked due to the way that Vue was loaded. This revised one works: jsfiddle.net/dyq08v0pRecept
Is there a way to find the next form element, if it is not a sibling according to DOM.Anethole
@Anethole I updated my answer to cover this caseRebate
P
12

Following up from this answer is in my opinion a cleaner alternative using the $refs API.

Using the $refs API, can allow you to target element in a simpler fashion without traversing the DOM.

Example

Pascia answered 5/9, 2017 at 15:40 Comment(1)
This no longer performs the desired behavior.Dustindustman
R
5

After some tests, it's working

new Vue({
    el:'#app',
    methods: {
      nextPlease: function (event) {
        document.getElementById('input2').focus();
      }
    }
});
<script src="https://vuejs.org/js/vue.js"></script>
<div id='app'>
    <input type="text" v-on:keyup.enter="nextPlease">
    <input id="input2" type="text">
</div>
Raphael answered 2/10, 2016 at 19:59 Comment(0)
L
2
directives: {
    focusNextOnEnter: {
        inserted: function (el,binding,vnode) {
            let length = vnode.elm.length;
            vnode.elm[0].focus();
            let index = 0;
            el.addEventListener("keyup",(ev) => {
                if (ev.keyCode === 13 && index<length-1) {
                  index++;
                  vnode.elm[index].focus();
                }
            });
            for (let i = 0;i<length-1;i++) {
                vnode.elm[i].onfocus = (function(i) {
                  return function () {
                    index = i;
                  }
                })(i);
            }
        }
    }
}

Use it:

<el-form v-focusNextOnEnter>
...
</el-form>
Lysozyme answered 27/7, 2018 at 7:52 Comment(0)
F
2

Try this:

<input ref="email" /> 
this.$refs.email.focus()
Figone answered 15/8, 2022 at 16:25 Comment(0)
C
1

Whilst I liked the directives answer due to it working with inputs inside other elements (style wrappers and so on), I found it was a little inflexible for elements that come and go, especially if they come and go according to other fields. It also did something more.

Instead, I've put together the following two different directives. Use them in your HTML as per:

<form v-forcusNextOnEnter v-focusFirstOnLoad>
...
</form>

Define them on your Vue object (or in a mixin) with:

directives: {
        focusFirstOnLoad: {
            inserted(el, binding, vnode) {
                vnode.elm[0].focus();
            },
        },
        focusNextOnEnter: {
            inserted(el, binding, vnode) {
                el.addEventListener('keyup', (ev) => {
                    let index = [...vnode.elm.elements].indexOf(ev.target);

                    if (ev.keyCode === 13 && index < vnode.elm.length - 1) {
                        vnode.elm[index + 1].focus();
                    }
                });
            },
        },
    },

On an enter key pressed, it looks for the index of the current input in the list of inputs, verifies it can be upped, and focuses the next element.

Key differences: length and index are calculated at the time of the click, making it more suitable for field addition/removal; no extra events are needed to change a cached variable.

Downside, this will be a little slower/more intensive to run, but given it's based off UI interaction...

Cannelloni answered 27/1, 2020 at 9:17 Comment(0)
D
1

Vue.js's directive is good practice for this requirement.

Define a global directive:

Vue.directive('focusNextOnEnter', {
  inserted: function (el, binding, vnode) {
    el.addEventListener('keyup', (ev) => {
      if (ev.keyCode === 13) {
        if (binding.value) {
          vnode.context.$refs[binding.value].focus()
          return
        }
        if (!el.form) {
          return
        }
        const inputElements = Array.from(el.form.querySelectorAll('input:not([disabled]):not([readonly])'))
        const currentIndex = inputElements.indexOf(el)
        inputElements[currentIndex < inputElements.length - 1 ? currentIndex + 1 : 0].focus()
      }
    })
  }
})

Note: We should exclude the disabled and readonly inputs.

Usage:

<form>
<input type="text" v-focus-next-on-enter></input>
<!-- readonly or disabled inputs would be skipped -->
<input type="text" v-focus-next-on-enter readonly></input>
<input type="text" v-focus-next-on-enter disabled></input>
<!-- skip the next and focus on the specified input -->
<input type="text" v-focus-next-on-enter='`theLastInput`'></input>
<input type="text" v-focus-next-on-enter></input>
<input type="text" v-focus-next-on-enter ref="theLastInput"></input>
</form>
Decemvirate answered 8/7, 2021 at 2:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.