Vue v-for autofocus on new textarea - javascript

I'm making a blog and would like the user to be able to create new textareas when they hit enter and for it to autofocus on the newly created textarea. I've tried using the autofocus attribute, but that doesn't work. I've also tried using the nextTick function, but that doesn't work. How do I do this?
<div v-for="(value, index) in content">
<textarea v-model="content[index].value" v-bind:ref="'content-'+index" v-on:keyup.enter="add_content(index)" placeholder="Content" autofocus></textarea>
</div>
and add_content() is defined as follows:
add_content(index) {
var next = index + 1;
this.content.splice(next, 0, '');
//this.$nextTick(() => {this.$refs['content-'+next].contentTextArea.focus()})
}

You're on the right path, but this.$refs['content-'+next] returns an array, so just access the first one and call .focus() on that
add_content(index) {
var next = index + 1;
this.content.splice(next, 0, {
value: "Next"
});
this.$nextTick(() => {
this.$refs["content-" + next][0].focus();
});
}
Working Example
var app = new Vue({
el: '#app',
data() {
return {
content: [{
value: "hello"
}]
};
},
methods: {
add_content(index) {
var next = index + 1;
this.content.splice(next, 0, {
value: "Next"
});
this.$nextTick(() => {
this.$refs["content-" + next][0].focus();
});
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="(value, index) in content">
<textarea v-model="content[index].value" v-bind:ref="'content-' + index" v-on:keyup.enter="add_content(index);" placeholder="Content" autofocus></textarea>
</div>
</div>
Also, your value in the array seems to be an object rather than a string, so splice in an object rather than an empty string

Related

Can't dynamically add class in Vue.js

Tried all variations of this. Used the official Vue guides extensively, as well as other stack overflow posts and various tutorials. I've tried with and without quotes, I've tried array syntax, I've tried just about everything. For some reason even though I can toggle the data property successfully, my css class doesn't get applied to elements when isLearned is true.
Here's my HTML:
<li
v-for="(flashcard, index) in flashcards"
v-bind:class="{learned: isLearned, flashcard}"
#click="toggleSide(flashcard)">
<p>{{flashcard.isFlipped ? flashcard.phrase : flashcard.definition}}</p>
<button #click="learnedCard(flashcard, index)">Learned</button>
</li>
Here's my JS:
new Vue({
el: "#esl-flashcards",
data: {
flashcards: flashcards,
inputPhrase: '',
inputDef: '',
isLearned: false,
},
methods: {
learnedCard: function(flashcard, index) {
for (let i = 0; i < flashcards.length; i += 1){
if (i === index) {
flashcards[i].isLearned = !flashcards[i].isLearned;
}
};
},
},
});
Few issues here:
isLearned is actually a property of each flashcard object. So, when you do:
flashcards[i].isLearned = !flashcards[i].isLearned;
you are actually updating that property, but you are checking for static data isLearned property change in the class like
v-bind:class="{learned: isLearned, flashcard}"
Thus you don't see any class change at all. You simply need to call it like:
v-bind:class="{learned: flashcard.isLearned, flashcard}"
Also, here:
for (let i = 0; i < flashcards.length; i += 1){
if (i === index) {
flashcards[i].isLearned = !flashcards[i].isLearned;
}
};
You are calling flashcards directly which will be always undefined in vie. You need to call it like this.flashcards. But as you are already passing the index of the array to learnedCard() method you don't need to loop here. You can simply use .find() method to achieve the same result in few lines like:
learnedCard: function(flashcard, index) {
var card = this.flashcards.find((f,i) => i===index)
card.isLearned = !card.isLearned;
},
Working Demo:
new Vue({
el: "#esl-flashcards",
data: {
flashcards: Array.from({length:4}, (_, i) => ({text: `Item ${i+1}`, isLearned: false})),
inputPhrase: '',
inputDef: '',
isLearned: false,
},
methods: {
learnedCard: function(flashcard, index) {
var card = this.flashcards.find((f,i) => i===index)
card.isLearned = !card.isLearned;
},
},
});
li.learned {background-color:skyblue;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="esl-flashcards">
<ul>
<li v-for="(flashcard, index) in flashcards" v-bind:class="{learned: flashcard.isLearned, flashcard}">
<p>{{flashcard.text}}</p>
<button #click="learnedCard(flashcard, index)">Learned</button>
</li>
</ul>
</div>

VueJs not working on first click or first event

I was able to create this dynamic elements.
My purpose in this is to create dynamic divs that will be based on "count", and inside that div, I can add multiple textboxes.
Here's what I came up with
You'll notice that the first click, it will not be the expected result. But when you click it the 2nd time, it will work.
I should be missing something. But I don't know what it is as I'm new to vue.
Here's the code as well:
<div id="app">
<button #click="populate">Populate</button>
<div v-for="(input, act) in inputs" >
Id
<div v-for="(i, ii) in input.items">
<input type="text" v-model="i.name">
</div>
<button v-show="act > 0" #click=input_add(act)>Add</button>
</div>
{{inputs}}
</div>
const app = new Vue({
el: '#app',
data: {
inputs: [],
counter: 0,
count: 3
},
methods: {
populate(){
var x = 1
while(x <= this.count){
this.inputs.push(
{
id: this.counter + 1,
items: []
}
)
this.input_add(x)
this.counter++
x++
}
},
input_add(x){
this.inputs[x].items.push(
{
name: null
}
)
}
}
})
Try setting var x = 1 to var x = 0 - that way it should have the functionality you get on the second click on initial.

Vue.js get element's html value by clicking on it's neighboring element

Hello everyone I'm building a simple notes app and I can't figure out how to implement one feature.
I have a card element and delete button as a child of this element. I need to check if the card element child's(.card-title) html value(jQuery's .html()) is equal to the localStorage(I'm using for to loop through the localStorage object) key by clicking on Delete button(that is a child of the card element alongside with the card's title) .Then, if true, I need to delete the localStorage item by key that is equal to the .card-title's html value.
So basically I have
.card
.card-title (with html value I need to get)
.card-body (nothing to do with it)
.delete-button (by clicking on it I need to get .card-title's html value)
That's only my point of view, which, most likely, is wrong. So, maybe, there is a better approach for deleting notes in my app?
Any ideas?
Full code on CodePen
Thank you very much for spending your precious time with my issue! Thank you for any help!
So I have a code like this :
<div id="notes">
<div class="container">
<div class="form-group">
<label for="title">Enter title</label>
<input class="form-control" id="title"/>
</div>
<div class="form-group">
<label for="body">Enter body</label>
<textarea class="form-control" id="body"></textarea>
</div>
<div class="form-group">
<button class="btn btn-primary" #click="add">Add</button>
<button class="btn btn-danger" #click="clear">Delete all</button>
</div>
<div class="card" v-for="o,t,b,c in notes">
<div class="card-body">
<h5 class="card-title">{{t}}</h5>
<p class="card-text">{{o[b]}}</p>
<a class="card-link" #click="remove">Delete</a>
</div>
</div>
</div>
</div>
new Vue({
el: "#notes",
data: {
notes: {}
},
methods: {
add: function() {
localStorage.setItem($("#title").val(), $("#body").val());
location.reload(true);
},
clear: function() {
localStorage.clear();
location.reload(true);
},
remove: function(e) {
for (i = 0; i < localStorage.length; i++) {
if (
localStorage.key(i) ==
$(this)
.closest(".card")
.find(".card-title")
.html()
) {
alert(true);
}
}
}
},
created: function() {
for (i = 0; i < localStorage.length; i++) {
this.notes[localStorage.key(i)] = [
localStorage.getItem(localStorage.key(i)),
"red"
];
}
}
});
so i built this very simple app so you can check it out
https://jsfiddle.net/vrxonsq1/2/
new Vue({
el:"#app",
data:{
form:{
title:"",
body:""
},
notes:[]
},
methods:{
add: function(){
this.notes.push({
title: this.form.title,
body: this.form.body
});
this.form.title = "";
this.form.body = "";
this.save();
},
remove: function(title){
this.notes.forEach(function(note,index){
if (note.title == title){
this.notes.splice(index,1);
}
})
this.save();
},
save: function(){
localStorage.setItem("notes", JSON.stringify(this.notes) );
}
},
created: function(){
notes = JSON.parse(localStorage.getItem("notes") );
this.notes = notes ? notes : []
}
})
it doesn't use jquery, only vuejs, I think it is better this way
simply create an array contains 'note' objects where each one have title and body.
Tell me if you have any questions.
with jQuery you can get an elements parent element with .parent().
So in this case you should be able to do this to get the html you're looking for:
$(this).parent().find('.card-title').html()
Well, found the solution myself, thanks everyone for help!
Updated code :
new Vue({
el: "#notes",
data: {
notes: {}
},
methods: {
add: function() {
localStorage.setItem($("#title").val(), $("#body").val());
location.reload(true);
},
clear: function() {
localStorage.clear();
location.reload(true);
},
remove: function(e) {
var t = $(e.target)
.parent()
.find(".card-title")
.html();
for (i = 0; i < localStorage.length; i++) {
if (localStorage.key(i) == t) {
localStorage.removeItem(localStorage.key(t));
location.reload(true);
}
}
}
},
created: function() {
for (i = 0; i < localStorage.length; i++) {
this.notes[localStorage.key(i)] = [
localStorage.getItem(localStorage.key(i)),
"red"
];
}
}
});
It may help to use VueJS' $refs
Assigning a ref to your elements gives you access to the specifically named DOM element within your component via a $refs property on this e.g
<div ref="myDiv">I'm a div</div> // = { myDiv: [DOMELEMENT] }
created () {
console.log(this.$refs.myDiv.innerHTML) // I'm a div
}
By using refs you should be able to use querySelector to query child elements of parent elements and vice versa.

Counter not increment in vue.js

I have a button and added a click event on which data value increment by 5
but it is appended by 5
https://jsfiddle.net/neyaz90/dkvmmrbd/
<div id="react">
<button #click='counter += 5'>Increment</button>
<p>{{result}}</p>
new Vue({
el:'#react',
data:{
counter:'0'
},
computed:{
result:function(){
return this.counter;
}
}
});
please help in this.
You need to use a Number instead of a String for 0 See jsfiddle here.
HTML
<div id="react">
<button #click="counter += 5">Increment</button>
<p>{{ result }}</p>
</div>
JS
new Vue({
el: '#react',
data: {
counter: 0
},
computed: {
result: function() {
return this.counter;
}
}
})
counter is defined with '0'(String) instead of 0(number).
You also don't need the computed value to show the result.
Only {{counter}} would be enough.

Load more data using vue js when page is bottom area

I tried to make my Load More data when my page scroll to the bottom. The first thing is I make a div element that I put at the end of the data loop.
<div class="products">
<p>{{ status }}</p>
<div class="product" v-for="(item, index) in items">
<div>
<div class="product-image"><img :src="item.link" alt=""></div>
</div>
<div>
<h4 class="product-title">{{ item.title }}</h4>
<p>Price : {{ price }}</p>
<button class="add-to-cart btn" #click="addItem(index)">Add Item To Cart</button>
</div>
</div>
<div id="product-list-bottom"></div>
</div>
Div element with id product-list-bottom I will detect it using scrollMonitor.js
My default data :
data: {
status: 'Empty product',
total: 0,
items: [],
cart: [],
newSearch: 'anime',
lastSearch: '',
price: STATIC_PRICE,
result: []
}
Inside mounted I detected scroll to bottom :
mounted: function() {
this.onSubmit()
var vueInstance = this
var elem = document.getElementById('product-list-bottom')
var watcher = scrollMonitor.create(elem)
watcher.enterViewport(function() {
vueInstance.appendItems()
})
}
Inside mounted I call onSubmit :
onSubmit: function() {
this.items = ''
this.status = "Searching keyword '" + this.newSearch + "' on server ..."
this.$http.get('/search/'.concat(this.newSearch))
.then(function(response) {
this.lastSearch = this.newSearch,
this.status = 'Find ' + response.data.length + ' data'
this.result = response.data
this.appendItems()
})
}
And inside onSubmit I call appendItems function :
appendItems: function() {
if(this.items.length < this.result.length) {
var start = this.items.length
var end = parseInt(this.items.length + 5)
var append = this.result.slice(start, end)
this.items = this.items.concat(append)
console.log(append)
}
}
All goes well, but when I scroll down I get an error message :
This is because this line :
this.items = this.items.concat(append)
How do I make the data on xxx change (always added five new data from the array) according to the command on the line :
var end = parseInt(this.items.length + 5)
Thanks
it seems '/search/'.concat(this.newSearch) gets evaluated into function and not an actual string value
Try this if you are using babel/webpack
this.$http.get(`/search/`${this.newSearch}`)
Or if not
this.$http.get('/search/' + this.newSearch)
I think since Vue 2.3+ or so you can get this done without any jQuery stuff or any other dependencies:
<style>
.scrollbar{
overflow-y: scroll;
//...
}
.styled-scrollbar::-webkit-scrollbar
.styled-scrollbar::-webkit-scrollbar-thumb
.styled-scrollbar::-webkit-scrollbar-track{
//styling
}
</style>
<template>
//...
<div #scroll="scroll" class="scrollbar">
<div v-for="item in items" :key="item.id">
//TODO: item content
</div
</div>
//...
</template>
<script>
{
data: {
//..
lastScrollUpdate:0
}
//..
mounted: {
scroll:function (e) {
var scrollBar=e.target;
if((scrollBar.scrollTop + scrollBar.clientHeight >= scrollBar.scrollHeight-20)){
var t=new Date().getTime();
if((t-this.lastScrollUpdate)>3000){
this.lastScrollUpdate=t;
console.log('reached end: '+scrollBar.scrollTop+' '+scrollBar.clientHeight+' '+scrollBar.scrollHeight);
//TODO: load more data
}else{
console.log("< 3sec between scoll. no update");
}
}
},
//..
}
}
</script>
You may also want to adjust this to "#scroll.passive", in order to let the scroll-function be executed parallel to the UI (https://v2.vuejs.org/v2/guide/events.html#Event-Modifiers)

Categories