Vue function fires multiple time - javascript

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.

Related

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)

Using multiple counters with one method

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>

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

Vue.js show white space (line breaks)

How would I show line space in vue.js. Right now everything is after each other....
Already tried this:
https://laracasts.com/discuss/channels/vue/vuejs-how-to-return-a-string-with-line-break-from-database
But nothing seems work. Trying this for 3 days now -_-.
I'm using Vue.js 1.0 and browserify.
Thanks a lot!
--EDIT--
<template>
<div>
<bar :title="title" />
<div class="Row Center">
<div class="Message Center" v-if="!loading">
<div class="Message__body" v-if="messages">
<div class="Message__item__body" v-for="message in messages" v-link="{ name: 'Message', params: { message: message.slug }}">
<div class="Message__item__body_content">
<p class="Message__title">{{ message.subject }}</p>
</div>
<div class="Message__item__body_content">
<p>Reacties: {{ message.totalReactions }}</p>
</div>
<div class="Message__item__body_content">
<p>Door: {{ message.user.name }} {{ message.user.last_name }}</p>
</div>
</div>
<pagination :last-page="lastPage" :page="page" :name="Message" />
<p v-if="noMessages" class="Collection__none">Er zijn momenteel geen berichten voor het topic {{ topic.name }}.</p>
</div>
</div>
<div class="Loader" v-if="loading">
<grid-loader :loading="loading" :color="color" :size="size" />
</div>
</div>
<div class="Row center" v-if="!loading && page == 1 && topic">
<div>
<button type="submit" class="Btn Btn-main" v-link="{ name: 'NewMessage', params: { topic: topic.slug }}">Nieuw bericht</button>
</div>
</div>
</div>
</template>
<script>
import Bar from '../Shared/Bar.vue';
import Pagination from '../Shared/Pagination.vue';
import Topic from '../../Services/Topic/TopicService';
import { GridLoader } from 'vue-spinner/dist/vue-spinner.min.js';
export default {
components: { Bar, Pagination, GridLoader },
data () {
return {
title: 'Berichten',
messages: [],
topic: null,
noMessages: false,
loading: false,
color: "#002e5b",
page: 1,
lastPage: 1,
}
},
route: {
data ({ to }) {
this.loading = true;
this.page = to.query.page || 1;
Topic.show(this.$route.params.topic, this.page)
.then((data) => {
this.topic = data.data.topic;
if(!data.data.messages.data.length == 0) {
this.messages = data.data.messages.data;
this.lastPage = data.data.messages.last_page;
} else {
this.noMessages = true;
}
this.loading = false;
});
}
}
}
</script>
When I do it like this:
<div class="Message__body__message">
<p>{{ message.message.split("\n"); }}</p>
</div>
It only adds comma's.
--EDIT--
Set container white-space style to pre-line, as in:
<div style="white-space: pre-line;">{{textWithLineBreaks}}</div>
When you split the message, you get multiple data items, which you should handle with a v-for.
But also see LMK's answer wherein you don't have to split the message.
new Vue({
el: '#app',
data: {
message: `this is a message
it is broken across
several lines
it looks like a poem`
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.min.js"></script>
<div id="app">
<template v-for="line in message.split('\n')">{{line}}<br></template>
</div>
You have to transform your data before rendering it with Vue.
const lines = stringWithLineBreaks.split('\n')
// then render the lines
I can give a more specific answer if you share the code you're working with.

Categories