Using multiple counters with one method - javascript

I am trying to keep my practice application as minimal as possible, how would I change my method to allow multiple counters to use one state variable but not change each counter when I increase or decrease?
<template>
<h1>Test Counter</h1>
<div class="container">
<div class="item">
<div class="itemName">Item 1</div>
<button #click="decreaseCount">Decrease</button>
<div class="value">{{ count }}</div>
<button #click="increaseCount">Increase</button>
</div>
<div class="item">
<div class="itemName">Item 2</div>
<button #click="decreaseCount">Decrease</button>
<div class="value">{{ count }}</div>
<button #click="increaseCount">Increase</button>
</div>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
count: 0,
};
},
methods: {
increaseCount() {
this.count += 1;
},
decreaseCount() {
this.count -= 1;
},
},
};
</script>
I have a very basic working example here where I have left the problem in the application - https://codesandbox.io/s/prod-hill-p6kum?file=/src/App.vue
I have tried to rename the state to it's own variable so each counter then has it's own state but when I have changed that the counter will not increase or decrease

Please check this out
<template>
<h1>Test Counter</h1>
<div class="container">
<div class="item">
<div class="itemName">Item 1</div>
<button #click="decreaseCount('counterOne')">Decrease</button>
<div class="value">{{ counterOne }}</div>
<button #click="increaseCount('counterOne')">Increase</button>
</div>
<div class="item">
<div class="itemName">Item 2</div>
<button #click="decreaseCount('counterTwo">Decrease</button>
<div class="value">{{ counterTwo }}</div>
<button #click="increaseCount('counterTwo')">Increase</button>
</div>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
counterOne: 0,
counterTwo: 0
};
},
methods: {
increaseCount(val) {
this[val] += 1;
},
decreaseCount(val) {
this[val] -= 1;
},
},
};
</script>
Another way
<template>
<h1>Test Counter</h1>
<div class="container">
<div class="item" v-for="(item, index) in items" :key="index">
<div class="itemName">{{item.name}}</div>
<button #click="decreaseCount(index)">Decrease</button>
<div class="value">{{ item.counter }}</div>
<button #click="increaseCount(index)">Increase</button>
</div>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
Items: [
{ name: 'Item 1', counter: 0 },
{ name: 'Item 1', counter: 0 },
.......
]
};
},
methods: {
increaseCount(val) {
this.items[val].counter += 1;
},
decreaseCount(val) {
this.items[val].counter -= 1;
},
},
};
</script>

Related

How can I make row number in vue component?

I have two component vue
My first component like this :
<template>
<div>
<b-row>
<div class="pl-2 d-flex">
<div class="card-body">
<p class="mb-0 w-5 w-sm-100">Number</p>
<div class="w-30 w-sm-100">Description</div>
<div class="w-20 w-sm-100">Date</div>
<div class="w-10 w-sm-100">Modified By</div>
</div>
</div>
</b-row>
<b-row key="list">
<b-colxx xxs="12" class="mb-3" v-for="(item,index) in items" :key="index" :id="item.id">
<list-item
:key="item.id"
:data="item"
:index="index"
/>
</b-colxx>
</b-row>
...
<b-pagination-nav
...
>
</b-pagination-nav>
...
</div>
</template>
<script>
import ListItem from "./ListItem";
export default {
components: {
"list-item": ListItem
},
};
</script>
My second component / child component like this :
<template>
<b-card no-body>
<div class="pl-2 d-flex">
<div class="card-body">
<p class="mb-0 text-muted w-5">{{index+1}}</p>
<p class="mb-0 text-muted w-30">{{data.description}}</p>
<p class="mb-0 text-muted w-20">{{data.date}}</p>
<p class="mb-0 text-muted w-10">{{data.created_by}}</p>
</div>
</div>
</b-card>
</template>
<script>
export default {
props: ['data', 'index'],
}
</script>
I use index to give row number. But the problem is when I move the page to another page, the line number will return to number 1
How can I solve this problem?
Please help. Thanks
You can create computed property, add line numbers to your items and loop over it:
new Vue({
el: '#demo',
data() {
return {
items: [],
fields: [{key: "lineNumber", label: "Number",}, {key: "postId", label: "Post ID",}, {key: "id", label: "ID",}, {key: "name", label: "Name",}, {key: "email", label: "Email",}, {key: "body", label: "Body",},],
currentPage: 0,
perPage: 10,
totalItems: 0,
}
},
computed: {
itemsWithLineNumber() {
return this.items.map((item, idx) => {
return {
...item,
lineNumber: (this.currentPage - 1) * this.perPage + idx + 1,
};
});
},
},
methods: {
async fetchData() {
await axios
.get(
`https://jsonplaceholder.typicode.com/comments?_page=${this.currentPage}&_limit=${this.perPage}`
)
.then((res) => {
this.totalItems = 500;
this.items = res.data;
});
},
changePage(nr) {
this.pageNr = nr
}
},
watch: {
currentPage: {
handler: function (value) {
this.fetchData().catch((error) => {
console.error(error);
});
},
},
},
async mounted() {
await this.fetchData()
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.css" />
<script src="https://polyfill.io/v3/polyfill.min.js?features=es2015%2CIntersectionObserver" crossorigin="anonymous"></script>
<script src="https://unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.js"></script>
<script src="https://unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue-icons.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.27.2/axios.min.js" integrity="sha512-odNmoc1XJy5x1TMVMdC7EMs3IVdItLPlCeL5vSUPN2llYKMJ2eByTTAIiiuqLg+GdNr9hF6z81p27DArRFKT7A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<div id="demo">
<div>
<b-table
show-empty
:items="itemsWithLineNumber"
:fields="fields"
:current-page="currentPage"
:per-page="0"
></b-table>
<b-pagination
size="md"
:total-rows="totalItems"
v-model="currentPage"
:per-page="perPage"
></b-pagination>
</div>
</div>

How to toggle between components in vue.js?

I developed one page called Dashboard.vue and this page contains three child components(Display,sortBooksLowtoHigh,sortBooksHightoLow). Dashboard component contains one select options also inside that it have two options "Price:High to Low and Price:Low to High ",
if i click on price:LowToHigh option then it hides the Display component and displays the sortBooksLowtoHigh component utpo this it's working fine,
Now i import one more component called sortBooksHightoLow when i click on "price:High to Low" option it should hides the DisplayComponent and displays the sortBooksHightoLow component.How to acheive this thing please help me
Dashboard.vue
<template>
<div class="main">
<div class="navbar navbar-default navbar-fixed-top">
<div class="navbar-header">
<img src="../assets/education.png" alt="notFound" class="education-image" />
</div>
<ul class="nav navbar-nav">
<li>
<p class="brand">Bookstore</p>
</li>
</ul>
<div class="input-group">
<i #click="handlesubmit();" class="fas fa-search"></i>
<div class="form-outline">
<input type="search" v-model="name" class="form-control" placeholder='search...' />
</div>
</div>
<ul class="nav navbar-nav navbar-right" id="right-bar">
<li><a> <i class="far fa-user"></i></a></li>
<p class="profile-content">profile</p>
<li><a><i class="fas fa-cart-plus"></i></a></li>
<p class="cart-content">cart</p>
</ul>
</div>
<div class="mid-body">
<h6>Books<span class="items">(128items)</span></h6>
<select class="options" #change="applyOption">
<option disabled value="">Sort by relevance</option>
<option value="HighToLow">price:High to Low</option>
<option value="LowToHigh">price:Low to High</option>
</select>
</div>
<DisplayBooks v-show="flag==='noOrder'" />
<sortBooksLowtoHigh v-show="flag==='lowToHigh'" />
<sortBooksHightoLow v-show="flag==='highToLow'" />
</div>
</template>
<script>
import service from '../service/User'
import sortBooksLowtoHigh from './sortBooksLowtoHigh.vue'
import sortBooksHightoLow from './sortBooksHightoLow.vue'
import DisplayBooks from './DisplayBooks.vue'
export default {
components: {
DisplayBooks,
sortBooksLowtoHigh,
sortBooksHightoLow
},
data() {
return {
flag: 'noOrder',
brand: 'Bookstore',
name: '',
visible:true,
books: [{
}]
}
},
methods: {
flip() {
this.flag = !this.flag;
},
applyOption(evt) {
if (evt.target.value === "HighToLow") this.flag = 'highToLow';
else this.flag = 'lowToHigh';
},
}
}
</script>
<style lang="scss" scoped>
#import "#/styles/Dashboard.scss";
</style>
sortBooksHightoLow.vue
<template>
<div class="carddisplay-section">
<div v-for="book in books" :key="book.id" class="card book">
<div class="image-section">
<div class="image-container">
<img v-bind:src="book.file" />
</div>
</div>
<div class="title-section">
{{book.name}}
</div>
<div class="author-section">
by {{book.author}}
</div>
<div class="price-section">
Rs. {{book.price}}<label class="default">(2000)</label>
<button v-if="flag" class="btn-grp" type="submit" #click="handlesubmit();Togglebtn();">close</button>
</div>
<div class="buttons">
<div class="button-groups">
<button type="button" #click="toggle(book.id);flip(book.id);" v-if="state==true" class="AddBag">Add to Bag</button>
<button v-if="state==true" class="wishlist">wishlist</button>
</div>
<div v-if="state==false" class="AddedBag">
<button class="big-btn">Added to Bag</button>
</div>
</div>
</div>
</div>
</template>
<script>
import service from '../service/User'
export default {
data() {
return {
result: 0,
authorPrefix: 'by',
pricePrefix: 'Rs.',
defaultStrikePrice: '(2000)',
buttonValue: 'close',
flag: true,
state: true,
clickedCard: '',
books: [{
id: 0,
file: 'https://images-na.ssl-images-amazon.com/images/I/41MdP5Tn0wL._SX258_BO1,204,203,200_.jpg',
name: 'High to Low',
author: 'Saioii',
price: '1500'
}, ]
}
},
methods: {
toggle(id) {
this.clickedCard = id;
// this.card.content = this.notes.filter((note) => note.id === id);
console.log(this.clickedCard);
},
flip() {
this.state = !this.state;
},
Togglebtn() {
this.flag = !this.flag;
},
handlesubmit() {
service.userDisplayBooksHightoLow().then(response => {
this.books.push(...response.data);
})
},
}
}
</script>
<style lang="scss" scoped>
#import "#/styles/DisplayBooks.scss";
</style>
sortBooksLowtoHigh.vue
<template>
<div class="carddisplay-section">
<div v-for="book in books" :key="book.id" class="card book">
<div class="image-section">
<div class="image-container">
<img v-bind:src="book.file" />
</div>
</div>
<div class="title-section">
{{book.name}}
</div>
<div class="author-section">
by {{book.author}}
</div>
<div class="price-section">
Rs. {{book.price}}<label class="default">(2000)</label>
<button v-if="flag" class="btn-grp" type="submit" #click="handlesubmit();Togglebtn();">close</button>
</div>
<div class="buttons">
<div class="button-groups">
<button type="button" #click="toggle(book.id);flip(book.id);" v-if="state==true" class="AddBag">Add to Bag</button>
<button v-if="state==true" class="wishlist">wishlist</button>
</div>
<div v-if="state==false" class="AddedBag">
<button class="big-btn">Added to Bag</button>
</div>
</div>
</div>
</div>
</template>
<script>
import service from '../service/User'
export default {
data() {
return {
result: 0,
authorPrefix: 'by',
pricePrefix: 'Rs.',
defaultStrikePrice: '(2000)',
buttonValue: 'close',
flag: true,
state: true,
clickedCard: '',
books: [{
id: 0,
file: 'https://images-na.ssl-images-amazon.com/images/I/41MdP5Tn0wL._SX258_BO1,204,203,200_.jpg',
name: 'Default Card',
author: 'Sai',
price: '..'
}, ]
}
},
methods: {
toggle(id) {
this.clickedCard = id;
// this.card.content = this.notes.filter((note) => note.id === id);
console.log(this.clickedCard);
},
flip() {
this.state = !this.state;
},
Togglebtn() {
this.flag = !this.flag;
},
handlesubmit() {
service.userDisplayBooksLowtoHigh().then(response => {
this.books.push(...response.data);
console.log(this.response);
})
},
}
}
</script>
<style lang="scss" scoped>
#import "#/styles/DisplayBooks.scss";
</style>
emmmm...
HightoLow => HighToLow.
There can be several methods, in my opinion, to achieve conditional rendering of components which I think your question asks for. Two of them which are highly useful are:
Using v-if and v-else where you must define a flag that handles the logic for component rendering. Also, wrapping them in a transition tag would a good idea to make the switch with a transition.
<transition>
<component1 v-if="flag" />
<component2 v-else />
</transition>
Dynamic Components, we use the component tag and is attribute. The component can then be switched using the name of the component.
<component is="nameofComponent" />
You can read more about dynamic components in vuejs docs.
While the dynamic component looks neat, a switch with transition can be a nice addition.

Vuejs emit not working form child to parent

I'm working on this app and the idea is to show details of the cars in a sidebar on click. There are several issues like the sidebar is showing four times and I resolve it somehow but I don't know why is it showing four times. now I don't getting any response on emit call help me out please, I try $parent.$emit, $root.$emit but not seems working!!!
<template>
<div class="home">
<!-- warehouse details -->
<div
v-for="(detail, detailindex) in details"
:key="detailindex"
class="container mt-5 mb-5"
>
<h1>
{{ detail.name }}
<span class="location">{{ detail.cars.location }}</span>
</h1>
<!-- vehicle details -->
<SingleGarage :detail="detail"> </SingleGarage>
</div>
<b-sidebar
id="my-sidebar"
title="Sidebar with backdrop"
backdrop-variant="dark"
ref="mySidebar"
backdrop
shadow
#emitData="testingEmit()"
>
<div class="px-3 py-2">
<h1>{{currentCar}}</h1>
</div>
</b-sidebar>
</div>
</template>
<script>
// # is an alias to /src
import axios from "axios";
import SingleGarage from "../components/SingleGarage";
export default {
components: { SingleGarage },
name: "Home",
data: () => ({
details: String,
currentCar: 'String',
}),
methods:{
testingEmit(data){
this.currentCar = data
console.log('data from emit',data)
}
},
mounted() {
axios
.get("https://api.jsonbin.io/b/5ebe673947a2266b1478d892")
.then((response) => {
var results;
response.data.forEach((element) => {
element.cars.vehicles.sort((a, b) => {
a = new Date(a.date_added);
b = new Date(b.date_added);
results = a > b ? -1 : a < b ? 1 : 0;
return results * -1;
});
});
this.details = response.data;
});
},
};
</script>
<template>
<div class="vGrid mt-4">
<div
class="gridItem border vehicle singleCar"
v-for="(vehicle, vehicleIndex) in detail.cars.vehicles"
:class="'griditem' + vehicleIndex"
:key="vehicle._id"
>
<SingleCar
:vehicle="vehicle"
#click.native="testingTef(vehicleIndex)"
></SingleCar>
</div>
</div>
</template>
<script>
import SingleCar from "#/components/SingleCar";
export default {
name: "SingleGarage",
components: { SingleCar },
props: ["detail"],
data: () => ({
dummyImg: require("#/assets/img/dummycar.png"),
currentCar : 1
}),
methods: {
testingTef(vehicleIndex) {
this.$parent.$emit('emitData',this.detail.cars.vehicles[vehicleIndex].make)
this.$root.$emit('bv::toggle::collapse', 'my-sidebar')
console.log(this.detail.cars.vehicles[vehicleIndex].make)
console.log(this.detail.cars.vehicles[vehicleIndex].date_added)
this.currentCar = this.detail.cars.vehicles[vehicleIndex].make;
},
},
};
</script>
<template>
<div class="singleCar">
<!-- conditionally show image -->
<img
class="carImg"
:src="vehicle.img"
v-if="vehicle.img"
alt="No Preview"
/>
<img class="carImg" :src="dummyImg" v-else alt="No Preview" />
<div class="p-3">
<h3 class="make">{{ vehicle.make }}</h3>
<div class="modelDetails">
<div class="model d-flex ">
<p class="bold">Model:</p>
<p class="price ml-auto ">{{ vehicle.model }}</p>
</div>
<div class="price d-flex ">
<p class="bold">Price:</p>
<p class="price ml-auto ">€{{ vehicle.price }}</p>
</div>
</div>
<p class="dateAdded ml-auto ">{{ vehicle.date_added }}</p>
</div>
</div>
</template>
<script>
export default {
name: "SingleCar",
props: ["vehicle"],
data: () => ({
dummyImg: require("#/assets/img/dummycar.png"),
}),
methods:{
working(){
console.log('working');
console.log(this.vehicle.make)
}
}
};
</script>
Thanks for your help.
So a few things you can try to fix this
in your Home.vue you can change
#emitData="testingEmit()"
to
#emitData="testingEmit"
// or
#emitData="testingEmit($event)"
You are telling to the function testingEmit that is not params to parse. So you need to take out the () and Vue will parse everything that comes from the $event or you cant say put the $event as a param in your testingEmit (second option).
For your SingleGarage.vue you can take the $parent.$emit and replace it with
this.$emit('emitData',this.detail.cars.vehicles[vehicleIndex].make)

Vue function fires multiple time

I'm using vue cli and I have function that updates text #click but it keeps running multiple times:
User.vue
<div #click="newText('Volume')">
<Chart :text=text ></Chart>
volume
</div>
<div #click="newText('Temperature')">
<Chart :text=text ></Chart>
temp
</div>
<div #click="newText('Weight')">
<Chart :text=text ></Chart>
weight
</div>
<script>
newText: function(argT) {
const text = argT;
this.text = text;
console.log('text', this.text);
</script>
},
In Chart component when I console.log it ran 9 times!
props: ['text'],
text1(){
console.log('text', this.text)
},
It seems that since my User component is displayed 3 times(intentionally due to an array of 3 users I have) and there is a box for each measurement(temp, vol and weight), that's why it's 9 times. But I'm not sure why it runs each time.
I would like it to run only once for the box I clicked.
Any help would be great, thanks!
Update (additional code)
User.vue
<template >
<div class="user">
<div v-for="(item, index) in users" :key="item.id">
<div>
<div #click.stop="myFunction(index); newData(index, item.Vol); newText('Volume')">
<v-touch v-on:doubletap="isOpen = !isOpen;" >
<transition name="modal">
<div v-if="isOpen">
<div class="overlay" #click.self="isOpen = false;">
<div class="modal">
<Chart :text=text :dat=dat ></Chart>
</div>
</div>
</div>
</v-touch>
volume </div>
<div #click.stop="myFunction(index);newData(index, item.Temp); newText('Temperature')">
<v-touch v-on:doubletap="isOpen = !isOpen;" >
<transition name="modal">
<div v-if="isOpen">
<div class="overlay" #click.self="isOpen = false;">
<div class="modal">
<Chart :text=text :dat=dat ></Chart>
</div>
</div>
</div>
</v-touch>
temp </div>
<div #click.stop="myFunction(index); newData(index, item.Weight); newText('Weight')">
<v-touch v-on:doubletap="isOpen = !isOpen;" >
<transition name="modal">
<div v-if="isOpen">
<div class="overlay" #click.self="isOpen = false;">
<div class="modal">
<Chart :text=text :dat=dat ></Chart>
</div>
</div>
</div>
</v-touch>
weight</div>
</div>
</div>
</div>
</template>
<script>
/* eslint-disable */
import Charts from './Charts'
export default {
name: 'User',
components: {
Charts,
},
methods:{
newData: function(arrIndex, event) {
const dat = event;
this.dat = dat;
},
newText: function(argT) {
const text = argT;
this.text = text;
console.log('text', this.text);
},
myFunction: function (arrIndex) {
const name = this.users[arrIndex].name;
this.name = name;
},
},
}
</script>
Charts.vue
<div class="tabs">
<a v-on:click="activetab=1" v-bind:class="[ activetab === 1 ? 'active' : '' ]">Settings</a>
<a v-on:click="activetab=2" v-bind:class="[ activetab === 2 ? 'active' : '' ]">Chart</a>
</div>
<div class="content">
<div v-if="activetab === 1" class="tabcontent">
<Settings></Settings>
</div>
<div v-if="activetab === 2" class="tabcontent">
<Chart :dat=dat :text=text ></Chart >
</div>
</template>
<script>
import Chart from './Chart'
import Settings from './Settings'
/* eslint-disable */
export default {
name: 'Charts',
props: ['activetab', 'dat','text' ],
components: {
Settings,
Chart,
},
methods: {
text1(){
console.log('text', this.text)
},
</script>
and finally I pass text to a chart:
<template>
<div id="container" ref="chart"></div>
</template>
<script>
title: {
text: this.text,
}
series: [ {
name: this.text,
data: this.dat,
}],
In my case browser-sync was the problem.

How can I get Vue to correctly update data dynamically?

The component I have below allows a user to view a products macro nutrient info and then also modify the serving size which in return updates the macro nutrient amounts.
The issue I'm having is that Im not getting the values to be updated correctly even when using vue set.
I'm using a watcher to run the calcNewNutriValues function.
<template>
<div class="product">
<div class="topbar">
<div class="left">
<p class="left__name">{{ product.name }}</p>
<p class="left__energy">{{ product.energy }}</p>
</div>
<div class="right">
<button class="cancel" #click="removeItem">
<inline-svg
:src="require('../assets/svg/addition-icon.svg')"
></inline-svg>
</button>
</div>
</div>
<div class="details">
<div class="macros">
<p class="details__heading">Macros</p>
<div class="macros__container container">
<div class="wrapper" v-for="(macro, name, index) in product.macros" :key="index">
<p>{{ name }}</p>
<p>{{ product.macros[name] }}</p>
</div>
</div>
</div>
<div class="serving">
<p class="details__heading">Serving Size (g)</p>
<input type="number" placeholder="40" v-model.number="productServSize">
</div>
</div>
</div>
</template>
export default {
data () {
return {
productServSize: 0,
ogServSize: 0,
macros: {
protein: '',
carbs: '',
fats: '',
fibre: ''
},
micros: {},
energy: ''
}
},
props: [
'product',
],
methods: {
serving () {
const num = this.product.servingSize.split(' ')[0]
this.productServSize = parseFloat(num)
this.ogServSize = parseFloat(num)
},
removeItem () {
this.$emit('removeProduct', this.product)
},
calcNewNutriValues () {
Object.keys(this.product.macros).forEach(key => {
let num = parseFloat(this.product.macros[key].split(' ')[0])
let perGram = parseFloat(num / this.ogServSize)
let newTotal = `${(perGram * this.productServSize).toFixed(1)} g`
this.$set(this.macros, key, newTotal)
})
}
},
mounted () {
this.serving()
Object.assign(this.macros, this.product.macros)
this.energy = this.product.energy
},
watch: {
productServSize: {
handler () {
this.calcNewNutriValues()
this.$emit('updatedNutriValues', this.product)
}
}
}
}
It only seems like macros isn't updating because your template displays product.macros instead of macros:
<div class="wrapper" v-for="(macro, name, index) in product.macros" :key="index">
<p>{{ name }}</p>
<!-- <p>{{ product.macros[name] }}</p> DON'T DO THIS -->
<p>{{ macros[name] }}</p>
</div>
demo

Categories