Setting focus of an input element in vue.js - javascript

I'm trying to set the focus of an input element in Vue.js. I found some help online but none of the explanation worked for me.
Here's my code :
<template>
<form method="post" action="" v-on:submit.prevent="search">
<input type="text" placeholder="Person name" required v-model="name" v-el="nameInput" />
<input type="text" placeholder="Company" required v-model="company" v-el="domainInput" />
<input type="submit" value="Search" class="btn show-m" />
</form>
</template>
<script>
export default {
data () {
return {
contacts: [],
name: null,
company: null
}
},
ready: {
// I tried the following :
this.$$.nameInput.focus();
this.$els.nameInput.focus();
// None of them worked !
}
methods: {
search: function (event) {
// ...
// I also would like to give the focus here, once the form has been submitted.
// And here also, this.$$ and this.$els doesn't work
},
}
}
</script>
I tried this.$$.nameInput.focus(); and this.$els.nameInput.focus(); for what I could find online to target the focus, but this.$$ is undefined, and this.$els is empty.
If that can help, I'm using vue.js v1.0.15
Thank you for your help.

In vue 2.x you can solve it with a directive.
Vue.directive('focus', {
inserted: function (el) {
el.focus()
}
})
Then you can use v-focus attribute on inputs and other elements:
<input v-focus>

Another solution using Vue 2.x and ref.
You can use the ref/$refs attribute to target your input and focus it.
In the example a simple method is used which can target the inputs using the ref attribute supplied to the inputs.
Then access the $refs property on your instance to get a reference to the DOM element.
<script>
export default {
// ...
mounted: function () {
this.focusInput('nameInput');
},
methods: {
// This is the method that focuses the element
focusInput: function ( inputRef ) {
// $refs is an object that holds the DOM references to your inputs
this.$refs[inputRef].focus();
},
search: function (event) {
this.focusInput('domainInput');
},
}
}
</script>
<template>
<form method="post" action="" v-on:submit.prevent="search">
<input type="text" placeholder="Person name" required v-model="name" ref="nameInput" />
<input type="text" placeholder="Company" required v-model="company" ref="domainInput" />
<input type="submit" value="Search" class="btn show-m" />
</form>
</template>
This solution is best for a one off situation or for a reusable component. For a more global approach the directive is the way to go.

Setting focus inside a child element
(for those of you that struggled for hours like me)
Parent:
<template>
<div #click="$refs.theComponent.$refs.theInput.focus()">
<custom-input ref="theComponent"/>
</div>
</template>
Child (CustomInput.vue):
<template>
<input ref="theInput"/>
</template>

There are a couple of issues.
First of all, v-els are defined like this:
<input v-el:input-element/>
That'll turn the variable to a camelCase in the code. You can read up more on this weird functionality here.
Other than that, you should be able to access the variable through this.$els.inputElement. Mind you, it will only appear in the component that you're defining that element (or the main app itself, if you defined it there).
Secondly, the automatic focusing does not seem to be working on Firefox (43.0.4), at least on my machine. Everything works great on Chrome, and focuses as expected.

Using ref I managed to focus an Input on mounted like this.
Template :
<b-form-input v-model="query" ref="searchInput" ></b-form-input>
Javascript :
mounted(){
this.$refs.searchInput.$el.focus()
}

Vue 3.x
Use a custom directive.
app.directive('focus', {
mounted(el) {
el.focus()
}
})
Here is how you use it:
Step 1:
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
app.directive('focus', {
mounted(el) { // When the bound element is inserted into the DOM...
el.focus() // Focus the element
}
})
/* Optional:
Add a slight delay if the input does not focus.
app.directive('focus', {
mounted(el) { // When the bound element is inserted into the DOM...
setTimeout(() => {
el.focus() // Focus the element
}, 500)
}
}) */
await router.isReady()
app.mount('#app')
Then in your component:
Step 2:
// MyInput.vue
<input v-focus>
Vue docs

According to vue 2.x, you can also register "directive" locally in component to get autofocus.
just write directive in component:
export default {
directives: { focus: {
inserted: function (el) {
el.focus()
}
}
}
}
Then in a template, you can use the new v-focus attribute on any element, like this:
<input type="text" v-focus>

Related

Vue - Keep input focused on route change

I have a TopNavBar component, that is present on every route. This component includes a search input field. When a user clicks on the input field the route changes from /bar to /foo but input focus is lost. How can I (re)focus on the input?
TopNavBar.vue
<template>
<input type="search" name="search-library" v-focus ref="searchInput" #focus="initSearch". />
</template>
<script setup>
const searchInput = ref(null);
<input type="search" name="search-library" v-focus ref="searchInput" #focus="initSearch". />
function initSearch() {
if (router.currentRoute.value.name != "/foo") {
router.push({ path: "/foo", query: { initSearch: true }, key: route.fullPath });
}
}
watch(
() => router.currentRoute.value.path,
(newRoute) => {
if (newRoute == "/foo") {
searchInput.value.focus();
}
}
);
</script>
I'm using Vue3 and Nuxt3. v-focusz directive is declared globally in /plugins` folder and works as expected.
Update
TopNavBar is inside Nuxt 3 layout. Also, upon further investigation I've realised that the input does focus on route change but immediately loses it again.
You can achieve this by using $refs, Attach a reference on input element and then call focus method on it.
In template:
<parent-component>
<search-component ref="searchComponentRef" />
</parent-component>
In script:
mounted() {
this.$refs.searchComponentRef.$el.focus();
}

vuejs: v-on does not trigger function

I have an input component that should call a method getUserSearch when the user types, and update the variable v_on_search with the variable v_model_search value.
I am displaying the content with the variables: v_model_search and v_on_search to test, although only the v-model seems to be working.
<template>
<div>
<input type="text" placeholder="search here" v-model="v_model_search" v-on:oninput="getUserSearch()">
<p>{{v_model_search}}</p>
<p>{{v_on_search}}</p>
</div>
</template>
<script>
export default {
name: 'SearchBox',
data () {
return {
v_model_search: '',
v_on_search: ''
}
},
methods: {
getUserSearch ()
{
this.v_on_search = this.v_model_search
}
}
}
</script>
Try
v-on:input="getUserSearch()"
or shorter
#input="getUserSearch()"
Vue recognizes HTML events but they don't have the "on" prefix

How to implement communication between two arbitrary elements in Vue?

I'm currently building an app using the Vue framework and came across a strange issue that I was unable to find a great solution for so far:
What I'm trying to do is add a class to a parent container in case a specific element inside the container (input, select, textarea etc.) gets focus. Here's the example code:
<div class="form-group placeholder-label">
<label for="desc"><span>Description</span></label>
<div class="input">
<input id="desc" type="text" />
</div>
</div>
In Vanilla JS of course, this is easily done:
const parent = document.querySelector('.placeholder-label');
const input = parent.querySelector('input');
input.addEventListener('focus', (e) => {
parent.classList.add('active');
});
In the same way, you could loop through all .placeholder-label elements and add the event to their child inputs/selects etc. to add this basic functionality. There are two moving parts here:
You don't know the type of the parent element, just that it has .placeholder-label on it.
You don't know the type of the child element, just that it is some sort of HTML form element inside the parent element.
Can I build a Vue component that toggles a class on a given parent element based on focus/blur of a given child element? The best I could come up with is use slots for the child elements, but then I still need to build a component for each parent. Even when using mixins for the reused parts it's still quite a mess compared to the five lines of code I need to write in pure JS.
My template:
<template>
<div
class="form-group"
:class="{ 'active': active }"
>
<label :for="inputID"><span>{{ inputLabel }}</span></label>
<slot
name="input"
:focusFunc="makeActive"
:blurFunc="makeInactive"
:inputLabel="inputLabel"
:inputID="inputID"
/>
</div>
</template>
<script>
export default {
name: 'TestInput',
props: {
inputLabel: {
type: String,
default: '',
},
inputID: {
type: String,
required: true,
},
},
// data could be put into a mixin
data() {
return {
active: false,
};
},
// methods could be put into a mixin
methods: {
makeActive() {
this.active = true;
},
makeInactive() {
this.active = false;
},
},
};
</script>
Usage:
<test-input
:input-i-d="'input-2'"
:input-label="'Description'"
>
<template v-slot:input="scopeVars">
<!-- this is a bootstrap vue input component -->
<b-form-input
:id="scopeVars.inputID"
:state="false"
:placeholder="scopeVars.inputLabel"
#blur="scopeVars.blurFunc"
#focus="scopeVars.focusFunc"
/>
</template>
</test-input>
I guess I'm simply missing something or is this a problem that Vue just can't solve elegantly?
Edit: In case you're looking for an approach to bubble events, here you go. I don't think this works with slots however, which is necessary to solve my issue with components.
For those wondering here are two solutions. Seems like I did overthink the issue a bit with slots and everything. Initially I felt like building a component for a given element that receives a class based on a given child element's focus was a bit too much. Turns out it indeed is and you can easily solve this within the template or css.
CSS: Thanks to #Davide Castellini for bringing up the :focus-within pseudo-selector. I haven't heard of that one before. It works on newer browsers and has a polyfill available.
TEMPLATE I wrote a small custom directive that can be applied to the child element and handles everything.
Usage:
v-toggle-parent-class="{ selector: '.placeholder-label', className: 'active' }"
Directive:
directives: {
toggleParentClass: {
inserted(el, { value }) {
const parent = el.closest(value.selector);
if (parent !== null) {
el.addEventListener('focus', () => {
parent.classList.add(value.className);
});
el.addEventListener('blur', () => {
parent.classList.remove(value.className);
});
}
},
},
},
try using $emit
child:
<input v-on:keyup="emitToParent" />
-------
methods: {
emitToParent (event) {
this.$emit('childToParent', this.childMessage)
}
}
Parent:
<child v-on:childToParent="onChildClick">
--------
methods: {
// Triggered when `childToParent` event is emitted by the child.
onChildClick (value) {
this.fromChild = value
}
}
use this pattern to set a property that you use to change the class
hope this helps. let me know if I misunderstood or need to better explain!

v-on:change not calling method in nuxt.js

This seems like a simple thing to do so I'm not exactly sure what I'm doing wrong here, I followed this question how to fire an event when v-model changes
but I cant seem to get it to work..
This is my component
<template>
<div>
<input type="text" v-model="searchTerm" v-on:change="search" />
</div>
</template>
<script>
export default {
data() {
return {
searchTerm: ''
}
},
methods: {
search() {
console.log(this.searchTerm);
}
}
}
</script>
now basically what I'm trying to do is when a user starts typing in the input, fire this event and console.log() the updated searchTerm, but when I start typing nothing is logged to the console??
Am I doing something wrong? Is this not how you listen to v-model changes in nuxt?
Try to use #input instead of #change event like so :
<template>
<div>
<input type="text" v-model="searchTerm" v-on:input="search" />
</div>
</template>

Svelte: Replace nested component by changing binded variable

I am writing Svelte project, where I have Message component which represents some js object.
There is ability to edit this object. For this purpose I desided to use two nested component MessageEditable and MessageReadable.
They should replace each other, depending on Message component state.
The problem is that when I am trying to save result of editing and
change MessageEditable to MessageReadable by setting isEditing property to false I get error:
image of error from console
Did I make a mistake or this is normal behavior? Is binding a good approach or there is more optimal for exchanging of values with parent components?
Message:
<div class="message">
{#if isEditing}
<MessageEditable bind:message bind:isEditing />
{:else}
<MessageReadable {message}/>
{/if}
<div class="message__controllers">
<button on:click="set({isEditing: true})">Edit</button>
</div>
</div>
<script>
import MessageEditable from './MessageEditable.html';
import MessageReadable from './MessageReadable.html';
export default {
components:{
MessageEditable,
MessageReadable,
},
data:() => ({
message:{
id: '0',
text: 'Some message text.'
},
isEditing: false,
}),
}
</script>
MessageEditable:
<form class="message-editable" on:submit>
<label><span >text</span><input type="text" bind:value=message.text required></label>
<label><span>id</span><input type="text" bind:value=message.id required></label>
<div><button type="submit">Save</button></div>
</form>
<script>
export default {
events:{
submit(node){
node.addEventListener('submit', (event) => {
event.preventDefault();
this.set({isEditing: false});
});
},
},
};
</script>
MessageReadable:
<div class="message-readable">
<p><span>text: </span>{message.text}</p>
<p><span>id: </span>{message.id}</p>
</div>
Its probably better to use a method than a custom event handler since you are performing actions on submit. I tested this code in the REPL and didn't experience the error you are getting. I changed your events to a methods property and removed the node functionalities.
<form class="message-editable" on:submit="save(event)">
<label><span >text</span><input type="text" bind:value=message.text required></label>
<label><span>id</span><input type="text" bind:value=message.id required></label>
<div><button type="submit">Save</button></div>
</form>
<script>
export default {
methods: {
save(event){
event.preventDefault();
this.set({isEditing: false});
},
},
};
</script>
https://svelte.technology/repl?version=2.10.0&gist=d4c5f8e3864856d27a3aa8cb5b2e8710

Categories

Resources