UPDATE: it turns out there is no way to directly apply focus to textbox before DOM rendering finished
I wonder why the focus() function does not work on hidden element.
For example( I am using Vue.js ):
var vm = new Vue({
el: "#app",
data:{
showtext: false
},
methods: {
showTxt(ev){
this.showtext = true
var vm = this;
// if I uncomment setTimeout, then the textbox can set focus
//setTimeout(function(){
vm.$refs.textbox.focus()
//}, 0)
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<button #click="showTxt">
Show Textbox and SetFocus on it
</button>
<div v-show="showtext">
<input ref="textbox" type="text" />
</div>
</div>
What I am trying to do is to click that button and show textbox and put focus in textbox, but currently, the textbox can not get focus if I directly call .focus(). It only works when I wrap a setTimeout around it(which I guess run on the next event loop). I wonder if there is any way to make the focus working without setTimeout?
Thanks
Preferable to use vm.$nextTick or Vue.nextTick (We don't need to care about nextTick even actually uses setTimeout, Vue will guarantee nextTick will do its job, even in future nextTick may use other approaches implement same goal).
As Vue API: nextTick says,
Defer the callback to be executed after the next DOM update cycle. Use
it immediately after you’ve changed some data to wait for the DOM
update.
Also you can check Vue Guide: Async Update Queue
For your case, when click the button to show the input, it will execute this.showtext=true, then execute element.focus. But actually the Dom element is still invisible (VNode is changed, but Vue hasn't re-render&patch).
So you have to use vm.$nextTick or Vue.nextTick to execute .focus after Vue re-render that input out.
Check the demo below:
Vue.config.productionTip = false
var vm = new Vue({
el: "#app",
data:{
showtext: false
},
methods: {
showTxt(ev){
this.showtext = true
var vm = this;
console.log('Current:', this.$el.innerHTML)
this.$nextTick(()=>{
vm.$refs.textbox.focus()
console.log('Nexttick:', this.$el.innerHTML)
})
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<button #click="showTxt">
Show Textbox and SetFocus on it
</button>
<div v-show="showtext">
<input ref="textbox" type="text" />
</div>
</div>
Why setTimeout works:
because setTimeout(,0) adds one task to the task-queue. But it will be executed at next event loop, but render will be exeucted after execute micro-tasks in current event loop.
Check HTML SPEC: event loop processing model (please look into Step 7), after current task (including this.showtext to true, data reactivity triggers re-render) already executed (it will be removed from task queue), the system will render first before pop one task (probably is setTimeout(,0) if setTimeout(,0) task is the oldest task) from the queue.
But Promise is micro-task, it will not work because it will be executed before render (please look into above event loop processing model: step 6).
Vue.config.productionTip = false
var vm = new Vue({
el: "#app",
data:{
showtext: false
},
methods: {
showTxt(ev){
this.showtext = true
var vm = this;
new Promise((resolve, reject)=>{
vm.$refs.textbox.focus()
resolve()
}).then(()=>{
vm.$refs.textbox.focus()
})
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<button #click="showTxt">
Show Textbox and SetFocus on it
</button>
<div v-show="showtext">
<input ref="textbox" type="text" />
</div>
</div>
Or you can look into this Youtube Video, it will decribe better than mine.
Hidden elements cannot get focus by the nature of focus entity concept. Only visible and enabled ones. In any UI system, HTML/CSS DOM included.
You could get rid of setTimeout only if you use other means of setting focus, for example autofocus attribute. Without it you will have to separate setting showtext flag and call for focus into separate event "ticks".
var vm = new Vue({
el: "#app",
data:{
showtext: false
},
methods: {
showTxt(ev){
this.showtext = true
this.$refs.textbox.focus()
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<button #click="showTxt">
Show Textbox and SetFocus on it
</button>
<div v-show="showtext">
<input ref="textbox" autofocus type="text" />
</div>
</div>
Related
I've created a simple vue.js program that uses a for loop to toggle the visibility of text. I have a button that should toggle it, but when it is clicked, it changes the variable but doesn't update the button.
let app = new Vue({
data() {
return {
array: [true,true,false],
text: ["0","1","2"]
}
},
methods:{
change(index){
this.array[index]=!this.array[index]
console.log(this.array[index],index)
}
},
el: "#app"
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="(item,index) in text">
<button #click="change(index)">toggle show</button>
<a v-if="!array[index]">...</a>
<a v-else>{{item}}</a>
</div>
</div>
Is there any way I could get this simple app to work without changing the arrays?
You’re running into a reactivity caveat: you cannot update a specific entry in an array by index in that way.
You’ll need to use Vue.set or this.$set (the latter is simply an alias for the former):
change(index){
this.$set(this.array, index, !this.array[index]);
}
I'm building some app using Laravel & Vue, and so far so good, but I'm no expert with Vue.
So I have one very "begginers" problem, using live data.
So I want to make button that will check if live data is on or off, and if they turn it on, it must refresh data and set liveData state to true.
For example:
This is my "button" and it's not working as expected, It will change state but data is still no live
<div v-if="liveData">
<div #click="liveData = false">
Turn OFF Live data
</div>
</div>
<div v-else="liveData">
<div #click="liveData = true">
Turn On Live data
</div>
</div>
I have defined state like so:
data() {
return {
liveData: false
}
},
And this is my created() function:
created() {
if(this.liveData){
window.Echo.channel("addOrder").listen(".order-created", (order) => {
this.$store.commit("ADD_ORDER", order);
});
}
this.$store.dispatch("GET_ORDERS");
},
So in this case only button is not working, but if I set state to true it's working perfectly.
What do I need to do here? Do I need to make new function to work or?
Created will only be executed once in your component lifecycle. At this point the value of liveData is always false.
If you click on your "button" the value should change but your code inside of created will not be executed once more.
Instead of created you can use an immediate watcher:
watch: {
liveData: {
immediate: true,
handler(val) {
// your code from created here
}
}
Correct the mistake
<div v-else!="liveData">
<div v-if="liveData">
<div #click="liveData = false">
Turn OFF Live data
</div>
</div>
<div v-else!="liveData">
<div #click="liveData = true">
Turn On Live data
</div>
</div>
or
<div v-else>
I want to execute a function when I'm clicking on elements in the dom with a specific class. It just doesn't work, but I'm also receiving any error. This is my
code snippet:
methods: {
initTab: function(){
document.querySelectorAll('.element').onclick = this.nextTab()
}
},
mounted: function () {
this.initTab()
}
I
I want to execute the function every time I click on the element. Would be very thankful if anybody could help me :)
There's very little need (if at all) for document.querySelectorAll() in a Vue app.
In this situation you can take advantage of delegation:
<div #click="onClick">
<!-- Clicks on any element inside this div will be handled -->
</div>
methods: {
onClick(e) {
if (e.target.classList.contains('element')) {
// Handle the click
}
}
}
Add #click="initTab($event)" to the document or template root, that allows you to track every click event on your template, that way you could put your logic to the elements which have only .element class name. If you're using it in a component you could do : <template> <div #click="initTab($event)"> ... </div> </template>
var app = new Vue({
el: '#app',
data() {
return {
}
},
methods: {
nextTab(){
console.log("You clicked on an element with class name =element")
},
initTab(event){
let targetClassNames=event.target.className.split(" ");
targetClassNames.filter(e=>{
if(e==="element"){
this.nextTab();
}
});
}
},
mounted() {
}
})
#app{
height:100px;
display:grid
}
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<div id="app" #click="initTab($event)">
<button class="element">1</button>
<button class="element btn">2</button>
<button class="btn">3</button>
<button class="element btn-primary">4</button>
<button class="btn elementory">5</button>
</div>
You're trying to use general javascript logic within vue. This is not often a good idea.
What I do in such cases is something like this:
<component-name #click="nextTab(tabName)"></component-name>
However, in a v-for loop you can also do something like this:
<ul v-for="tab in tabs">
<li #click="nextTab(tab)">{{tab}}</li>
</ul>
That way in methods you only need:
methods: {
nextTab: function(tab){
// whatever it is you want to do here
}
},
And you won't need mounted at all.
Conclusion: try to avoid repetition by creating components or elements (like li) that repeat - not by trying to add an event-listener to a class.
I am trying to learn vue.js despite not having any background with javascript. I ran into some code when following a video that was teaching about 'computed', and I tried experimenting on it and had a bit of trouble along the way.
<div id='app'>
<p>Do you see me?</p>
<p v-if="show">Do you also see me?</p>
<button #click="showToggle1">Switch!</button>
</div>
new Vue({
el:'#app',
data:{
show = true;
},
computed:{
showToggle1:function(){
return this.show = !this.show
}
},
methods:{
showToggle2:function(){
this.show = !this.show;
}
});
Basically it's making "Do you also see me?" disappear and appear depending on the value of "show". I know that if you write #click:'showToggle2()' instead of #click:'showToggle1' at the button, the value changes and it works. I'm just having some trouble understanding how computed works and why showToggle1 doesn't change the value of show when I click the button
Some problems.
First, you have syntactical problems. data is an object, so instead of:
data:{
show = true;
}
Should be:
data:{
show: true
}
Next, computed properties are to be used like... properties. For example, like declared in data. So, typicall, you read from them. You don't execute computed properties in #click events. So this code:
<button #click="showToggle1">Switch!</button>
Is not correct. It will error because showToggle1 is not a method, it is, as said, a computed property. What you should have in click is a method, like:
<button #click="showToggle2">Switch!</button>
This will work because showToggle2 is a method. And you should use methods to perform changes.
Not, before going into the last and most tricky part, here's a working demo:
new Vue({
el: '#app',
data: {
show: true
},
computed: {
/*showToggle1: function() {
return this.show = !this.show;
}*/
},
methods: {
showToggle2: function() {
this.show = !this.show;
}
}
});
<script src="https://unpkg.com/vue"></script>
<div id='app'>
<p>Do you see me?</p>
<p v-if="show">Do you also see me?</p>
<hr>
Value of show: {{ show }}<br>
<button #click="showToggle2">Switch2!</button>
</div>
The tricky part is your computed property (which I commented out in the code above):
computed:{
showToggle1:function(){
return this.show = !this.show
}
},
Basically what it is doing is it is automatically negating the value of show whenever it changes.
This happens because the computed property is calculated whenever show updates. And what is happening is:
You initialize data with true (because of data: {show: true}).
The showToggle1 computed auto-recalculates, because it has this.show inside of it (it depends on it).
When recalculating, showToggle1 sets the value of show to false (because of return this.show = !this.show).
That's why show becomes false.
And that's also why whenever you change (even from the method, which is the correct place) the value of show to true it will automatically go back to false. Because any change in show triggers the showToggle1 computed recalculation, which sets show back to false.
In summary:
Use methods to perform changes.
Don't change properties inside computed properties.
this question is similar to VueJS re-compile HTML in an inline-template component and also to How to make Vue js directive working in an appended html element
Unfortunately the solution in that question can't be used anymore for the current VueJS implementation as $compile was removed.
My use case is the following:
I have to use third party code which manipulates the page and fires an event afterwards. Now after that event was fired I would like to let VueJS know that it should reinitialize the current DOM.
(The third party which is written in pure javascript allows an user to add new widgets to a page)
https://jsfiddle.net/5y8c0u2k/
HTML
<div id="app">
<my-input inline-template>
<div class="wrapper">
My inline template<br>
<input v-model="value">
<my-element inline-template :value="value">
<button v-text="value" #click="click"></button>
</my-element>
</div>
</my-input>
</div>
Javascript - VueJS 2.2
Vue.component('my-input', {
data() {
return {
value: 1000
};
}
});
Vue.component('my-element', {
props: {
value: String
},
methods: {
click() {
console.log('Clicked the button');
}
}
});
new Vue({
el: '#app',
});
// Pseudo code
setInterval(() => {
// Third party library adds html:
var newContent = document.createElement('div');
newContent.innerHTML = `<my-element inline-template :value="value">
<button v-text="value" #click="click"></button>
</my-element>`; document.querySelector('.wrapper').appendChild(newContent)
//
// How would I now reinialize the app or
// the wrapping component to use the click handler and value?
//
}, 5000)
After further investigation I reached out to the VueJs team and got the feedback that the following approach could be a valid solution:
/**
* Content change handler
*/
function handleContentChange() {
const inlineTemplates = document.querySelector('[inline-template]');
for (var inlineTemplate of inlineTemplates) {
processNewElement(inlineTemplate);
}
}
/**
* Tell vue to initialize a new element
*/
function processNewElement(element) {
const vue = getClosestVueInstance(element);
new Vue({
el: element,
data: vue.$data
});
}
/**
* Returns the __vue__ instance of the next element up the dom tree
*/
function getClosestVueInstance(element) {
if (element) {
return element.__vue__ || getClosestVueInstance(element.parentElement);
}
}
You can try it in the following fiddle
Generally when I hear questions like this, they seem to always be resolved by using some of Vue's more intimate and obscured inner beauty :)
I have used quite a few third party libs that 'insist on owning the data', which they use to modify the DOM - but if you can use these events, you can proxy the changes to a Vue owned object - or, if you can't have a vue-owned object, you can observe an independent data structure through computed properties.
window.someObjectINeedtoObserve = {...}
yourLib.on('someEvent', (data) => {
// affect someObjectINeedtoObserve...
})
new Vue ({
// ...
computed: {
myObject () {
// object now observed and bound and the dom will react to changes
return window.someObjectINeedtoObserve
}
}
})
If you could clarify the use case and libraries, we might be able to help more.