I have 2 Components Login and Register each input field I have included the autofocus attribute. But when I change the route from the Register component to the Login component autofocus doesn't work. I know this is because the page doesn't reload. But is there any way to do so?
This is my Login.vue, Same as the Register.vue
<form>
<input type="text" autofocus />
<RouterLink to="/login">Login now!</RouterLink>
</form>
You can use $refs to access your input element with JS :
<form>
<input type="text" autofocus ref="myInput"/>
<RouterLink to="/login">Login now!</RouterLink>
</form>
then, watch the route change and can trigger focus() accordingly
watch:{
$route (to, from){
if(to !== from){
this.$refs.myInput.focus()
}
}
}
For composition api it should be something like this depending on how you use router:
setup(){
const myInput = ref(null)
const route = useRoute()
watch(route.value, (to, from) => {
if(to !== from ){
myInput.value.focus()
}
});
}
When you navigate to another page, vue-router doesn't actually reload the page. You need to assign the location in JavaScript to go to another page. You can do this by creating a function in your setup script to navigate to a new location. In the example below, I use web hash for navigation, so you may need to implement different functionality to your code.
<script setup>
// declare the navigation function
function nav(path) {
location = path
}
</script>
<template>
<input type="text" autofocus>
<!-- Navigate to #/about on click -->
<button #click="nav('#/about')"></button>
</template>
Related
Getting the Error Cannot read properties of undefined (reading '$refs') though I'm having a reference in the template. Does it mean I must use Vue mounted hook ?
<div class="input-wrapper">
<input type="text" id="phone" placeholder="(555) 555-5555" ref="input"/>
</div>
<script>
this.$refs.input.addEventListener('input', function () {
// some code
});
</script>
Inside root of <script> of a Vue component, in both Vue 2 and Vue 3, this is undefined:
<script>
console.log(this) // undefined
</script>
See it here.
Vue template refs can only be accessed inside any hook or method happening after the component has been mounted and before it is unmounted.
Which means the earliest you can reference this.$refs is inside mounted. And the latest is in beforeUnmount. And you can also access them in any hook or method happening between those two moments.
Considering you're attempting to add an event listener to a HTMLInputElement, I'd recommend using the v-on directive, which automatically adds the event listener on mount and removes it on unmount.
In your case:
<template>
<div class="input-wrapper">
<input
type="text"
id="phone"
placeholder="(555) 555-5555"
#input="myFn" />
</div>
</template>
<script>
export default {
methods: {
myFn(event) {
console.log(event)
}
}
}
</script>
As a side note, you should know that a regular function's this doesn't have access to the component context, unless it's an arrow function:
export default {
mounted() {
this.$refs.input.addEventListener('input', function() {
/*
* Here `this` is the context of the current function, you can't
* access methods, computed, data or props of the component.
* You'd need to make it an arrow function to access the component scope
*/
})
}
}
Whereas in any method (e.g: the above myFn), this is the component context, which has access to all component members.
I have a form that contains a selector reusable component like this
<template>
<div class="channelDetail" data-test="channelDetail">
<div class="row">
<BaseTypography class="label">{{ t('channel.detail.service') }}</BaseTypography>
<BaseSelector
v-model="serviceId"
data-test="serviceInput"
class="content"
:option="servicePicker.data?.data"
:class="serviceIdErrorMessage && 'input-error'"
/>
</div>
<div class="row">
<BaseTypography class="label">{{ t('channel.detail.title') }}</BaseTypography>
<BaseInput v-model="title" data-test="titleInput" class="content" :class="titleErrorMessage && 'input-error'" />
</div>
</div>
</template>
I'm going to test this form by using vue-test-utils and vitest.
I need to set option props from the script to the selector.
In my thought, this should be worked but not
it('test', async () => {
const wrapper=mount(MyForm,{})
wrapper.findComponent(BaseSelector).setProps({option:[...some options]})
---or
wrapper.find('[data-test="serviceInput"]').setProps({option:[...some options]})
---or ???
});
Could anyone help me to set the props into components in the mounted wrapper component?
The answer is that you should not do that. Because BaseSelector should have it's own tests in which you should test behavior changes through the setProps.
But if you can't do this for some reason, here what you can do:
Check the props passed to BaseSelector. They always depend on some reactive data (props, data, or computed)
Change those data in MyForm instead.
For example
// MyForm.vue
data() {
return {
servicePicker: {data: null}
}
}
// test.js
wrapper = mount(MyForm)
wrapper.setData({servicePicker: {data: [...some data]})
expect(wrapper.findComponent(BaseSelector)).toDoSomething()
But I suggest you to cover the behavior of BaseSelector in separate test by changing it's props or data. And then in the MyForm's test you should just check the passed props to BaseSelector
expect(wrapper.findComponent(BaseSelector).props('options')).toEqual(expected)
I have read this post which goes in depth about renderless components:
https://adamwathan.me/renderless-components-in-vuejs/
A renderless component would pretty much look like this:
export default {
render() {
return this.$scopedSlots.default({})
},
}
Now I would like to use this renderless component but also add a click listener to whatever is being passed into the slot.
In my case it would be a button. My renderless component would simply wrap a button and add a click listener to it, which in turn performs an AJAX request.
How would I go about adding a click listener to the element that is being passed into the slot?
Assuming you want to bind the click handler within the renderless component, I think from this post that you need to clone the vnode passed in to renderless, in order to enhance it's properties.
See createElements Arguments, the second arg is the object to enhance
A data object corresponding to the attributes you would use in a template. Optional.
console.clear()
Vue.component('renderless', {
render(createElement) {
var vNode = this.$scopedSlots.default()[0]
var children = vNode.children || vNode.text
const clone = createElement(
vNode.tag,
{
...vNode.data,
on: { click: () => alert('clicked') }
},
children
)
return clone
},
});
new Vue({}).$mount('#app');
<script src="https://unpkg.com/vue#2.6.11/dist/vue.js"></script>
<div id="app">
<renderless>
<button type="button" slot-scope="{props}">Click me</button>
</renderless>
</div>
Here's one way to go about this.
Your renderless component wrapper would consist of a single action (i.e. the function to issue the AJAX request) prop.
Vue.component('renderless-action-wrapper', {
props: ['action'],
render() {
return this.$scopedSlots.default({
action: this.action,
});
},
});
Then another component which uses the aforementioned wrapper would enclose a customisable slot with a #click handler, which invokes the action that is passed in when triggered.
Vue.component('clickable', {
props: ['action'],
template: `
<renderless-action-wrapper :action="action">
<span slot-scope="{ url, action }">
<span #click="action()">
<slot name="action"></slot>
</span>
</span>
</renderless-action-wrapper>
`,
});
Finally, wire up the specialised version of the wrapper.
<clickable :action="doAjaxRequest">
<button type="button" slot="action">Button</button>
</clickable>
Here's a live example of the above suggestion you can play around with.
I have a vue component with
<form #keydown="console.error($event.target.name);">
gives
app.js:47961 [Vue warn]: Property or method "console" is not defined
on the instance but referenced during render.
window.console doesn't work either
What is the proper way to use console and window in a template to debug?
Simplest way of providing global objects to the template is to place them in computed, like this:
console: () => console. Same goes for window,
computed: {
console: () => console,
window: () => window,
}
See it here.
If you want to run it inline instead of using a method, just add this to the form:
Codepen: https://codepen.io/x84733/pen/PaxKLQ?editors=1011
<form action="/" #keydown="this.console.log($event.target.name)">
First: <input type="text" name="fname"><br>
Second: <input type="text" name="fname2"><br>
</form>
But it'd be better to use a method instead of running functions inline, so you have more control over it:
<!-- Don't forget to remove the parenthesis -->
<form action="/" #keydown="debug">
First: <input type="text" name="fname"><br>
Second: <input type="text" name="fname2"><br>
</form>
...
methods: {
debug (event) {
console.log(event.target.name)
}
}
You can use $el.ownerDocument.defaultView.console.log() inside your template
Pro: Doesn't require any component changes
Con: Ugly
Also if you want to access console from {{ }} you can use global mixin:
Vue.mixin({
computed: {
console: () => console
}
})
If using Vue 3, do:
const app = createApp(App)
app.config.globalProperties.$log = console.log
If using Vue 2, do:
Vue.prototype.$log = console.log
Use $log inside template:
<h1>{{ $log(message) }}</h1>
To not interfere with the rendering, use $log with ?? (or || if using Vue 2, since ?? is not supported in the template):
<h1>{{ $log(message) ?? message }}</h1>
You can use this.console instead console or wrap call to console in a method, i am using eslint config with rule 'no-console': 'off'
You can use computed property or methods for this case.
If you need to code it as javascript in the Vue template. you have to define console in the data.
Please check the code below.
data(){
return {
selected :"a",
log : console.log
}
}
<span>{{log(selected)}}</span>
This will make functionality of console.log available, while resolving the template.
I'd make a getter for console template variable:
get console() { return window.console; }
For Vue 3, SFC Composition API, you have to define a function and call console or alert inside that function
<script setup>
import Child from "./Child.vue";
function notify(message) {
alert(message);
}
</script>
<template>
<Child #some-event="notify('child clicked')" />
</template>
I have a logic like this: is the user is V2 use the user to the url in subHeaderRouter.router. If the user isn't launch this.openModal:
<router-link
v-for="subHeaderRouter in subHeaderRouters"
:to="subHeaderRouter.router"
#click="handleOpenModal()">
</router-link>
handleOpenModal () {
if (this.IsV2User) return
this.openModal('changeUserType', 'user.changeUserType')
}
The only thing I need to do now is to stop :to then she user is not V2. How to accomplish that?
You can prevent the default <router-link> behavior by specifying no default event to listen to and handling the click event manually with the .native modifier:
<router-link
v-for="subHeaderRouter in subHeaderRouters"
event=""
:to="subHeaderRouter.router"
#click.native.prevent="handleOpenModal(subHeaderRouter.router)"/>
handleOpenModal(route) {
if (this.IsV2User) {
this.$router.push(route)
} else {
this.openModal('changeUserType', 'user.changeUserType')
}
}
If the event="" seems weird to you, it also works with an empty attribute:
<router-link
v-for="subHeaderRouter in subHeaderRouters"
event
:to="subHeaderRouter.router"
#click.native.prevent="handleOpenModal(subHeaderRouter.router)"/>
Vue 3 Solution
In Vue3, the event has been removed from "<router-link>, you will need to use v-slot API instead.
<router-link :to="yourRoute" custom v-slot="{ href, navigate }">
<a v-if="yourBoolean" #click="handleEvent()">Run the function</a>
<a
v-else
:href="href"
#click="navigate">
Go to route in "to"
</a>
</router-link>
// Vue 3 Composition API
export default {
setup() {
const handleEvent = () => {
// Add your logic here
}
}
};
I had the same problem and used dynamic Vue Components as solution. You will also have to check the difference in styling for router and div tag.
<component
:is=" condition ? 'router-link' : 'div' "
#click="handleOpenModal"
:to="your_link">
</component>