How to update counter inside a v-loop in vue.js? - vue.js

How can I i update the counters separatly inside v-for? Now it updates both when incrementing, since the list can be dynamic i cant set variables for all the types.
The code i run inside the v-for is:
<a #click.prevent="decrease(quote, min)">
Decrease
</a>
<input name="types" type="text" v-model="quote" >
<a #click.prevent="increase(quote, max)">
Increase
</a>
It looks like:
And here is the vue script:
<script>
var app = new Vue({
el: "#contents",
data: {
quote: 1,
},
computed: {
},
methods: {
decrease: function(quote, min){
app.quote--;
if(app.quote < min){
app.quote = min;
}
},
increase: function(quote, max){
if(app.quote > max){
app.quote = max;
}
},
},
});
</script>

You should do something like this:
<template>
<div>
<div v-for="item in d" :key="item">
<Counter></Counter>
</div>
</div>
</template>
<script>
import Counter from "./Counter.vue";
export default {
name: "HelloWorld",
components: { Counter },
props: {
msg: String
},
data: () => {
return {
d: [1, 2]
};
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>
/// counter component with its own state
<template>
<div>
<div>
<span>{{count}}</span>
<button v-on:click="increment">increment</button>
<button v-on:click="decrement">decrement</button>
</div>
</div>
</template>
<script>
export default {
name: "Counter",
data: () => {
return {
count: 0
};
},
methods: {
increment() {
this.count++;
},
decrement() {
this.count--;
}
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>
demo: https://codesandbox.io/s/silly-solomon-0hk16?file=/src/components/Counter.vue:0-516
The idea is that you should put the logic inside each component, but in your case you set just one state ant try to share it for many instance, instead you should create a reusable component with its own state, like in my case : count

Related

How can I select element Id with a function in Vue.js?

I want to use the Id of the element in which I use the function as a parameter. Example:
<div
class="col-2 column-center"
id="myId"
#mouseover="aFunction(id)"
>
methods: {
aFunction(id){
alert(id);
}
}
But it doesn't work. How can I do it?
In another way, you can make your id attribute bind with the data property, like this :
<template>
<div class="col-2 column-center" v-bind:id="id" #mouseover="aFunction(id)">
test
</div>
</template>
<script>
export default {
name: 'Test',
data() {
return {
id: 'myId',
};
},
methods: {
aFunction(id) {
alert(id);
},
},
};
</script>
You can pass the event to the method and then get the id from event.target.id.
https://jsfiddle.net/tr0f2jpw/
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!'
},
methods: {
aFunction(event){
alert(event.target.id);
}
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<div
class="col-2 column-center"
id="myId"
#mouseover="aFunction($event)"
>
some stuff
</div>
</div>

Watching properties in child component does not work

I have 2 components, a parent and a child. I want to modify the value of a prop in my parent component (by calling a method when a button is clicked) and send it to the child component. In the child component, I want to watch for changes in my prop, so that anytime it changes, it does something (for testing purposes, I tried to console.log() the prop).
Parent:
<template>
<div>
<h5>Your Feeds</h5>
<div id="feeds">
<div class="card" v-for="feed in feeds">
<div class="card-body" :id="feed['_id']" >
{{ feed['name'] }}
<button v-on:click="loadFeed(feed['_id'])">Button</button>
</div>
</div>
</div>
</div>
</template>
<script>
import GridComponent from "./GridComponent";
export default {
name: "FeedsListComponent",
data() {
return {
feeds: []
}
},
mounted() {
axios
.get("/getFeeds")
.then(response => (this.feeds = response.data))
.catch(error => console.log(error))
},
methods: {
loadFeed(id) {
this.feedId = id
}
},
components: {
GridComponent
}
}
</script>
Child:
<template>
<div id="grid">
<v-grid
theme="compact"
:source="rows"
:columns="columns"
></v-grid>
</div>
</template>
<script>
import VGrid from "#revolist/vue-datagrid";
export default {
name: "Grid",
props: ['feedId'],
data() {
return {
columns: [],
rows: [],
};
},
watch: {
feedId: function(val, oldVal) {
console.log(val)
console.log(oldVal)
console.log(this.feedId)
//here I want to send an ajax request with feedId to one of my controllers in order to get
//the data needed for rows and colums
}
},
components: {
VGrid,
},
};
</script>
I put together a sample that is working in order to help you diagnose why yours isn't working:
Parent.vue
<template>
<div class="parent">
<h3>Parent</h3>
<div class="row">
<div class="col-md-6">
<button class="btn btn-secondary" #click="incrementCounter">Change parent message</button>
</div>
</div>
<child :propMessage="message" />
</div>
</template>
<script>
import Child from '#/components/stackoverflow/watch-prop/Child'
export default {
components: {
Child
},
data() {
return {
counter: 0
}
},
computed: {
message() {
return 'Message' + this.counter;
}
},
methods: {
incrementCounter() {
this.counter++;
}
}
}
</script>
Child.vue
<template>
<div class="child">
<hr>
<div class="row">
<div class="col-md-6">
<label>Message in child from watched prop:</label>{{ dataMessage }}
</div>
</div>
</div>
</template>
<script>
export default {
props: {
propMessage: {
type: String,
required: true
}
},
data() {
return {
dataMessage: this.propMessage
}
},
watch: {
propMessage(newMessage) {
this.dataMessage = newMessage;
}
}
}
</script>
<style scoped>
label {
font-weight: bold;
margin-right: 0.5rem;
}
</style>

How to make a function from another brother component be called when pressed?

Good afternoon, tell me please, I wrote a calendar displaying the events that the user sets, and now I want to make a detailed display of the event when I click it. The problem is that the events are in my component and the whole display logic is in another, how can I use them together.I want to make it so that the getDetailInformation() function in the ModalWindowDetail component component is called by clicking on an event in another component Calendar.vue. I use the event bus, but nothing works for me, I don’t understand why. Please help me solve this problem.
Screenshot of Calendar and error in console
Code of Calendar on GitHub
App.vue:
<template>
<div class="all">
<app-calendar #sendTextEvent="text = $event"></app-calendar>
<app-detail v-if="modalWindowDetail"
:eventText="text"></app-detail>
</div>
</template>
<script>
import appCalendar from './components/Calendar.vue'
import appDetail from './components/ModalWindowDetail.vue'
export default {
data(){
return{
text: String
}
},
components: {
appCalendar,
appDetail
},
computed: {
modalWindowDetail() {
return this.$store.state.modalWindowDetail;
}
}
};
</script>
Calendar.vue component which display calendar:
<template>
<div class="overflow-div">
<transition :name="nameOfClass" >
<div :key="currentPage" class="fade_wrapper">
<div v-for="(week, i) in getCalendar" class="d_day">
<li v-for="day in week" class="li_day">
<div class="day">{{ day }}</div>
<div v-for="event in buildEvents(i, day)"
class="event"
v-bind:class="{ 'eventBrown': eventBrown(event),
'eventPurple': eventPurple(event),
'eventOrange': eventOrange(event),
'eventBlue': eventBlue(event) }"
v-on:click="openModalDetail(event)">{{ event }}
</div>
</li>
</div>
</div>
</transition>
</div>
</template>
<script>
import json from './Calendar_data.json'
import { mapState } from "vuex";
import { eventBus } from './../main.js'
export default {
mounted(){
eventBus.$on('getDetailInformation', this.openModalDetail())
},
computed: {
modalWindowDetail() {
return this.$store.state.modalWindowDetail;
},
},
methods: {
openModalDetail(text){
this.$emit('sendTextEvent', text);
}
};
</script>
The component in which the getDetailInformation() is located ModalWindowDetail.vue:
<template>
<div class="underModalWindow">
<div class="modalWindow">
<img src="src/assets/x.png" width="20px" height="20px">
<div class="nameofModal">Вся детальная информация о событии</div>
<div v-for="(key, name) in eventDetail" class="detailEvent">{{ name }}: {{ key }}</div>
<button>Окей</button>
</div>
</div>
</template>
<script>
import { eventBus } from './../main.js'
export default {
props: ['eventText'],
data(){
return{
options: [
{ text: 'Встреча', value: '8' },
{ text: 'День Рождения', value: '4' },
{ text: 'Праздник', value: '1' },
{ text: 'Другое', value: '16' }
],
eventDetail: Object,
}
},
computed: {
eventsData() {
return this.$store.state.eventData;
},
modalWindowDetail() {
return this.$store.state.modalWindowDetail;
},
},
created(){
eventBus.$emit('getDetailInformation', this.getDetailInformation())
},
methods: {
getDetailInformation(){
let arrOfEvents = this.eventsData.events;
for(let z = 0; z < arrOfEvents.length; z++){
let memo = arrOfEvents[z].memo;
console.log(this.memo)
if(memo === this.eventText){
let dataStartOfEvent = arrOfEvents[z].starts_at;
let getStartDataOfEvent = new Date(dataStartOfEvent);
let dataEndOfEvent = arrOfEvents[z].ends_at;
let getEndDataOfEvent = new Date(dataEndOfEvent);
if((getStartDataOfEvent.getHours() - 3) > 0){
this.$store.commit('changeModalWindowDetail', this.modalWindowDetail);
this.eventDetail = {
'Событие': this.eventText,
'Начало события': getStartDataOfEvent.toLocaleTimeString(),
'Конец события': getEndDataOfEvent.toLocaleTimeString(),
'Тип события': this.getType(arrOfEvents[z].type)
}
}else if(getStartDataOfEvent.getDate() != getEndDataOfEvent.getDate()){
this.$store.commit('changeModalWindowDetail', this.modalWindowDetail);
this.eventDetail = {
'Событие': this.eventText,
'Начало события': getStartDataOfEvent.toLocaleDateString(),
'Конец события': getEndDataOfEvent.toLocaleDateString(),
'Тип События': this.getType(arrOfEvents[z].type)
}
}
}
}
}
}
};
</script>
You should remove the () from the function name in eventBus.$on('getDetailInformation', this.openModalDetail()) - you want to reference the function, not to call it and use the result as a reference.
Also, your function getDetailInformation() does not return anything - but you seem to expect that it returns a text. You should correct this.
And finally, I think that #sendTextEvent="text = arguments[0]" would be more appropriate - and using a dedicated method/function will be the best.

Invalid prop: type check failed error in VueJS

I am trying to use a component named CardRenderer.vue which renders card using array of Objects. I am using the same component again & again to render the data. I am having this error "[Vue warn]: Invalid prop: type check failed for prop "renderData" when I tried passing prop from component.
I tried passing different values and different types but it did'nt work.
Here is the code:
CardRenderer.vue
<template lang="html">
<div>
<b-container class="bv-example-row">
<b-row v-for="(row, i) of rows" v-bind:key="i">
<b-col v-for="(item, j) of row" v-bind:key="j" >
<!-- you card -->
<b-card
:title="item.title"
img-src="item.icon"
img-alt="Image"
img-top
tag="article"
style="max-width: 20rem;"
class="mb-2"
>
<b-card-text>
<h1>{{item.name}}</h1>
<pre>{{item.description}}</pre>
</b-card-text>
<b-button :href="'/dashboard/'+item.name" variant="primary">More</b-button>
</b-card>
</b-col>
</b-row>
</b-container>
</div>
</template>
<script lang="js">
export default {
name: 'CardRenderer',
props: {
renderData: []
},
data() {
return {
rows: null
}
},
mounted() {
const itemsPerRow = 3
let rowss = []
let arr = this.renderData
// eslint-disable-next-line
// console.log(this.renderData)
for (let i = 0; i < arr.length; i += itemsPerRow){
let row = []
for (let z = 0; z < itemsPerRow; z++) {
row.push(arr[z])
}
rowss.push(row)
}
this.rows = rowss
// eslint-disable-next-line
console.log(this.rows)
},
methods: {
},
computed: {
// rows() {
// }
}
}
</script>
<style scoped>
</style>
CardGrouper.vue:
<template lang="html">
<div class = "full" >
<div class="h-50" style=" background-color: #C8544F">
<h1 align="center">{{$store.getters.responseAPI.title}} </h1>
<CardRenderer :renderData=this.$store.getters.responseAPI.apps />
</div>
</div>
</template>
<script>
import CardRenderer from "./CardRenderer.vue"
/* eslint-disable */
export default {
name: 'CardGrouper',
components: {
CardRenderer
},
props: [],
mounted() {
},
data() {
return {
}
},
methods: {
},
computed: {
}
}
</script>
<style scoped >
.full{
width: 100vw;
height: 90vh;
background: linear-gradient(to bottom, Red 30%, white 50%);
}
</style>
Something.vue
<template lang="html">
<!-- <h1>Something</h1> -->
<CardRenderer :renderData=valObj />
</template>
<script lang="js">
import CardRenderer from './CardRenderer'
export default {
name: 'something',
components: {
CardRenderer
},
props: [],
data() {
return {
valObj: []
}
},
mounted() {
let key = this.findUrl()
let value = this.$store.getters.responseAPI.apps.filter((elem) => {
if(elem.name == key) return elem.apps
})
if (value.length > 0)
this.valObj = value[0].apps
//eslint-disable-next-line
console.log(this.valObj)
},
methods: {
findUrl() {
let url = window.location.pathname.split("/").slice(-1)[0];
return url
}
},
computed: {
}
}
</script>
<style scoped >
.something {
}
</style>
I am having this error.
It looks like this.
This is the data I am passing as a prop from Something.vue
This is how value looks like
Error is being generated somewhere from Something.vue.
I am passing array of objects as a prop.
How do i rectify this error, to make it work.
Set the renderData type as Array and default value to []:
props: {
renderData: {
type: Array,
deafult: () => []
}
}
It appears that you are defining your renderData prop as an array [] but probably are passing an object to it or something.
Either simplify it and do...
props: ['renderData']
Or if you are passing an object to it do..
props: {
renderData: {
type: Object,
}
}
If it is an array of objects do..
props: {
renderData: {
type: Array,
default: () => [{}];
}
just for doc.
// Object with a default value
propE: {
type: Object,
// Object or array defaults must be returned from
// a factory function
default: function () {
return { message: 'hello' }
}
},
This is in vue prop documentation

How to pass and change index of array in vue?

I have the gist of how to do this, but I'm a beginner in vue, and I'm struggling with how to put it together. I need Control.vue to update the index in Exhibitor.vue. I know I'll have an $emit event happening in Control when I click on the button to pass the index data to the parent, and I'd have to use props to pass data from Exhibitor to its children, but how? I can't understand how to pass the index of an array with my code.
Exhibitor.vue
<template>
<div id="exhibitor">
<section class="exhibitor_info">
<h1 class="exhibitor_name">{{ exhibitors[index].firstName }} {{ exhibitors[index].lastName }}</h1>
<h2>Tag Number: {{ exhibitors[index].tagNum }} <em>{{ exhibitors[index].species }}</em></h2>
</section>
<div class="frame"><img :src="getImgUrl(exhibitors[index].picture)" alt="Exhibitor-Picture" class="image"></div>
</div>
</template>
<script>
export default {
name: 'Exhibitor',
data() {
return {
exhibitors: [],
index: 0
}
},
created: function() {
this.fetchExhibitors();
},
methods: {
fetchExhibitors() {
let uri = 'http://localhost:8081/exhibitor'
this.axios.get(uri).then(response => {
this.exhibitors = response.data
})
},
getImgUrl: function(pic) {
return require('../assets/' + pic)
}
}
}
</script>
Display.vue
<template>
<div id="display">
<exhibitor></exhibitor>
<buyer></buyer>
</div>
</template>
<script>
import Exhibitor from './Exhibitor.vue';
import Buyer from './Buyer.vue';
export default {
components: {
'exhibitor': Exhibitor,
'buyer': Buyer
}
}
</script>
Control.vue
<template>
<div id="control">
<display></display>
<button v-on:click="incrementLeft">Left</button>
<button v-on:click="incrementRight">Right</button>
</div>
</template>
<script>
import Exhibitor from './Exhibitor.vue';
import Display from './Display.vue';
export default{
props: ['exhibitors', 'buyers', 'index'],
data() {
return {
index: 0
}
},
methods: {
incrementRight: function() {
// Note that '%' operator in JS is remainder and NOT modulo
this.index = ++this.index % this.exhibitors.length
},
incrementLeft: function() {
// Note that '%' operator in JS is remainder and NOT modulo
if (this.index === 0) {
this.index = this.exhibitors.length - 1
} else {
this.index = --this.index % this.exhibitors.length
}
}
},
components: {
'display': Display
}
}
</script>
So you can get what you want to happen and there are two ways of making it happen that I can think of. First I will just clarify the terms relating to this because you seem to have them the wrong way around. Let's look at you tier structure which is like this:
Control.vue
contains: Display.vue
contains: Exhibitors.vue & Buyers.vue.
Therefore Control.vue is the parent of Display.vue which is the parent of Buyers.vue and Exhibitors.vue.
Anyway, What we need to do is control the array of exhibitors (and I guess buyers but you didn't include them in your code so I'll do likewise) which is in Exhibitors.vue from Control.Vue even though they don't have a direct parent child relationship. What I've done is set a prop that is passed to Display.vue which uses scoped slots to render the exhibitors from Exhibitors.Vue. Because the left and right buttons need to know when to stop going I have emitted the array length from Exhibitors.vue to Display.vue and again to Control.vue. It all works so heres some code.
//Control.vue
<template>
<div class="content">
<display v-on:finalLength="setIndexLimit" :i="index"></display>
<button #click="changeDown">Down</button>
<button #click="changeUp">Up</button>
<p>{{indLimit}}</p>
</div>
</template>
<script>
import Display from '#/components/Display'
export default {
components: {
Display
},
data: () => ({
index: 0,
indLimit: 0
}),
methods: {
changeUp() {
if (this.indLimit === this.index+1) {
this.index=0
}
else {
this.index ++
}
},
changeDown() {
if (this.index === 0) {
this.index = this.indLimit - 1
}
else {
this.index --
}
},
setIndexLimit(e) {
this.indLimit = e
}
}
}
</script>
and
//Display.vue
<template>
<div id="display">
<p>From Display</p>
<exhibitors v-on:ExLength="setLength">
<p>{{i}}</p>
</exhibitors>
<exhibitors>
<p slot-scope="{ exhibitors }">{{exhibitors[i].firstName}}</p>
</exhibitors>
<exhibitors>
<p slot-scope="{ exhibitors }">{{exhibitors[i].lastName}}</p>
</exhibitors>
<exhibitors>
<p slot-scope="{ exhibitors }">{{exhibitors[i].tagNum}}</p>
</exhibitors>
<exhibitors>
<p slot-scope="{ exhibitors }">{{exhibitors[i].species}}</p>
</exhibitors>
</div>
</template>
<script>
import Child from '#/components/admin/Exhibitors'
export default {
components: {
Exhibitors
},
props: [
'i'
],
data: () => ({
exhibitLength: null
}),
methods: {
setLength(e) {
this.exhibitLength = e
this.$emit('finalLength',e)
}
}
}
</script>
And finally:
//Exhibitors.vue
<template>
<div>
<slot :exhibitors="exhibitors"><p>I'm the child component!</p></slot>
</div>
</template>
<script>
export default {
data: () => ({
exhibitors: [
{
firstName: 'Joe',
lastName: 'Burns',
tagNum: 1,
species: 'ant'
},
{
firstName: 'Tom',
lastName: 'Yorke',
tagNum: 2,
species: 'bee'
},
{
firstName: 'Flit',
lastName: 'Argmeno',
tagNum: 3,
species: 'giraffe'
}
],
}),
mounted: function () {
this.$nextTick(function () {
let length = this.exhibitors.length
this.$emit('ExLength', length)
})
}
}
</script>
So as I said all that works, you can click the buttons and it will loop through the array contents. You can style it how you want wherever you like. However, it is a bit messy with props and slots and emits and whatnot just to relay a single index number and it would be far easier in my opinion to have a vuex store where 'index' is stored as a state item and can be used and changed anywhere without all the above carry on.