VueJs not working on first click or first event - javascript

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.

Related

Vue v-for autofocus on new textarea

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

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)

Vue.js 2 - Array change detection

Here's a simplified version of my code :
<template>
/* ----------------------------------------------------------
* Displays a list of templates, #click, select the template
/* ----------------------------------------------------------
<ul>
<li
v-for="form in forms.forms"
#click="selectTemplate(form)"
:key="form.id"
:class="{selected: templateSelected == form}">
<h4>{{ form.name }}</h4>
<p>{{ form.description }}</p>
</li>
</ul>
/* --------------------------------------------------------
* Displays the "Editable fields" of the selected template
/* --------------------------------------------------------
<div class="form-group" v-for="(editableField, index) in editableFields" :key="editableField.id">
<input
type="text"
class="appfield appfield-block data-to-document"
:id="'item_'+index"
:name="editableField.tag"
v-model="editableField.value">
</div>
</template>
<script>
export default {
data: function () {
return {
editableFields: [],
}
},
methods: {
selectTemplate: function (form) {
/* ------------------
* My problem is here
*/ ------------------
for (let i = 0; i < form.editable_fields.length; i++) {
this.editableFields.push(form.editable_fields[i]);
}
}
}
}
</script>
Basically I want to update the array EditableFields each time the user clicks on a template. My problem is that Vuejs does not update the display because the detection is not triggered. I've read the documentation here which advise to either $set the array or use Array instance methods only such as splice and push.
The code above (with push) works but the array is never emptied and therefore, "editable fields" keep pilling up, which is not a behavior I desire.
In order to empty the array before filling it again with fresh data, I tried several things with no luck :
this.editableFields.splice(0, this.editableFields.length);
for (let i = 0; i < form.editable_fields.length; i++) {
this.editableFields.push(form.editable_fields[i]);
}
==> Does not update the display
for (let i = 0; i < form.editable_fields.length; i++) {
this.$set(this.editableFields, i, form.editable_fields[i]);
}
==> Does not update the display
this.editableFields = form.editable_fields;
==> Does not update the display
Something I haven't tried yet is setting a whole new array with the fresh data but I can't understand how I can put that in place since I want the user to be able to click (and change the template selection) more than once.
I banged my head on that problem for a few hours now, I'd appreciate any help.
Thank you in advance :) !
I've got no problem using splice + push. The reactivity should be triggered normally as described in the link you provided.
See my code sample:
new Vue({
el: '#app',
data: function() {
return {
forms: {
forms: [{
id: 'form1',
editable_fields: [{
id: 'form1_field1',
value: 'form1_field1_value'
},
{
id: 'form1_field2',
value: 'form1_field2_value'
}
]
},
{
id: 'form2',
editable_fields: [{
id: 'form2_field1',
value: 'form2_field1_value'
},
{
id: 'form2_field2',
value: 'form2_field2_value'
}
]
}
]
},
editableFields: []
}
},
methods: {
selectTemplate(form) {
this.editableFields.splice(0, this.editableFields.length);
for (let i = 0; i < form.editable_fields.length; i++) {
this.editableFields.push(form.editable_fields[i]);
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="app">
<ul>
<li v-for="form in forms.forms"
#click="selectTemplate(form)"
:key="form.id">
<h4>{{ form.id }}</h4>
</li>
</ul>
<div class="form-group"
v-for="(editableField, index) in editableFields"
:key="editableField.id">
{{ editableField.id }}:
<input type="text" v-model="editableField.value">
</div>
</div>
Problem solved... Another remote part of the code was in fact, causing the problem.
For future reference, this solution is the correct one :
this.editableFields.splice(0, this.editableFields.length);
for (let i = 0; i < form.editable_fields.length; i++) {
this.editableFields.push(form.editable_fields[i]);
}
Using only Array instance methods is the way to go with Vuejs.

Categories