Dynamic pricing calculator in vue js - javascript

*Edited
I'm building a dynamic pricing calculator feature using vue js. I've made the code and there are still some problems that make the features that I made not work properly. I have a dropdown menu which when clicked will display different tabs and different content (dynamic component). And also I have an estimate card for details and prices, and every item can be deleted.
I have several problems which are as follows:
When I was on the Storage tab, and submit data after I selected several options, the software tab details and price also appeared in estimation card. I want is the pricing details just the Storage. Likewise if I just submit in the Software tab, then only details and prices appear from the Software tab. And if I submit in Software and Storage, the result of both will appear.
When I submit data on the Storage tab, and I go to the Software tab, the price details on the estimation card are gone. I want the data still appear. it's not possible if I use <keep-alive> because it's not a dynamic component.
I have the delete button item in the estimation card. What I want it to be will delete the item according to the option that was clicked. But still not working very well.
Currently, I only have 2 tabs, Storage and Software. If I have other tabs, how do I make this system dynamic?
Can anyone help me solve this problem and explain it step by step?
this is my code that I made on Codesandbox: https://codesandbox.io/s/dynamic-price-calculator-o77ig

You must make the priceEstimationData dynamic. You can show the data from props without using watch. I'm only providing some code here. For full version please go to the codesandbox.
<!-- PriceEstimation.vue -->
<template v-if="priceEstimationData">
<div
:key="id"
v-for="(data, id) in priceEstimationData"
class="estimation-category"
>
<div class="category-price d-flex justify-content-between">
{{ data ? data.type[0].toUpperCase() + data.type.slice(1) : ""}}
Delete
</div>
<table class="table table-borderless">
<tbody>
<tr>
<td>
<p>
Storage Type: <span>{{ data.typeName }}</span>
</p>
<p>
Quantity Storage: <span>{{ data.qunantity }}</span>
</p>
<p>
Duration Storage: <span>{{ data.duration }}</span>
</p>
</td>
<td class="text-right">
<p>${{ data.typeValue }}</p>
<p>${{ data.qunantity * data.quantityPrice }}</p>
<p>${{ data.duration * data.durationPrice }}</p>
</td>
</tr>
</tbody>
</table>
</div>
</template>
After looking at your code. I notice that you are using the watch property. I have removed the watch property to make the estimation data still. See the code below
// TheCalculator.vue
// remove watch that makes the priceEstimationData null
/* watch: {
selectedTab: function () {
this.priceEstimationData = null;
},
}, */
computed: {
selectedTabProps() {
return this.selectedTab === "storage-calculator"
? { event: this.setPriceEstimationData }
: null;
},
},
I have modified your code and move the removeItem methods to parent. Because the child only read the data. See below
// TheCalculator.vue
methods: {
setSelectedTab(tab) {
this.selectedTab = tab;
},
setPriceEstimationData(data) {
this.priceEstimationData.push(data);
},
removeItem(id) {
// remove findIndex function
this.priceEstimationData.splice(id, 1);
},
},
I have modified TheCalculator.vue, PriceEstimation.vue, ServiceCalculator.vue, and StorageCalculator.vue.
Edit
I have fixed the duration bug in ServiceCalculator.vue
// ServiceCalculator.vue
// fixes duration bug
updateDurationServices(val) {
this.duration = val;
},
Codesandbox: https://codesandbox.io/s/dynamic-price-calculator-forked-ls7n6
Enjoy!

Here's a very crude bunch of components that gets this job done - without the SUBMIT button.
Vue.component("ProductSetup", {
props: ['productKey', 'product'],
methods: {
handleInputChange(e) {
this.$emit("update:product-type", {
...e,
product: this.productKey,
})
},
handleQuantityChange(e) {
this.$emit("update:product-quantity", {
...e,
product: this.productKey,
})
},
},
template: `
<div>
<label
v-for="(val, key) in product.types"
:key="key"
>
{{ key }}
<input
type="radio"
:value="val"
:name="productKey"
#change="() => handleInputChange({ type: key })"
/>
</label>
<input
type="number"
min="0"
#input="(e) => handleQuantityChange({ quantity: e.target.value })"
/>
</div>
`,
})
Vue.component("PriceEstimate", {
props: ["products"],
template: `
<div>
<div
v-for="(product, productKey) in products"
>
{{ productKey }}<br />
{{ productKey }} {{ product.chosen }}: {{ product.types[product.chosen] }}<br />
Quantity {{ productKey }}: {{ product.quantity * product.pricePerPiece }}<br />
TOTAL: {{ product.types[product.chosen] + product.quantity * product.pricePerPiece }}
</div>
</div>
`,
})
new Vue({
el: "#app",
computed: {
estimates() {
return Object.fromEntries(
Object.entries(
this.products
).filter(([key, val]) => {
return val.chosen && val.quantity
})
)
},
},
data() {
return {
products: {
storage: {
chosen: null,
types: {
type1: 60,
type2: 70,
},
pricePerPiece: 20,
quantity: null,
},
os: {
chosen: null,
types: {
os1: 60,
os2: 70,
os3: 80,
},
pricePerPiece: 35,
quantity: null,
},
},
}
},
methods: {
handleUpdateProduct({
type,
product
}) {
this.products[product]["chosen"] = type
},
handleUpdateQuantity({
quantity,
product
}) {
this.products[product]["quantity"] = quantity
}
},
template: `
<div>
<div
v-for="(obj, product) in products"
>
Chosen {{ product }}: {{ obj.chosen }}<br />
Quantity of {{ product }}: {{ obj.quantity }}
<hr />
<product-setup
:product-key="product"
:product="obj"
#update:product-type="(e) => handleUpdateProduct(e)"
#update:product-quantity="(e) => handleUpdateQuantity(e)"
/>
<hr />
</div>
<price-estimate
:products="estimates"
/>
</div>
`,
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>

The problem is in the parameter received by priceEstimationData, it does have servicesDuration nor servicesTypePrice.
In ServiceCalculator.vue, the "data" is:
var data = {
servicesTypeName: selectedServicesType.name,
servicesTypeValue: selectedServicesType.value,
qunantity: this.quantiti,
durationServices: this.durationServices,
};
You must pass also the propierties servicesDuration and servicesTypePrice.
Regards

Related

Vue.js update contents in div dynamically

I am attempting to do a SPA using Vue.js but unfortunately I know almost nothing about it, I followed a tutorial and got something up and running. This should hopefully be relatively simple!
I'm trying to create a simple page that:
Does a REST API call and pulls some JSON
A list with links of a particular field in the list of results is displayed on the left side of the screen
(I've managed until here)
Now I would like to be able to click on one of the links and see on the right side of the screen the value of another field for the same record.
For instance, suppose my JSON is:
{
"jokes":{
[
"setup":"setup1",
"punchline":"punchline1"
],
[
"setup":"setup2",
"punchline":"punchline2"
],
[
"setup":"setup3",
"punchline":"punchline3"
]
}
}
So in my screen I would see:
setup1
setup2
setup3
So if I click in setup1 I see punchline1, setup2 displays punchline2 and so on.
Here is my code - I'm basically trying to display the punchline in the moduleinfo div. I realise the current solution does not work. I've been searching but can't find any similar examples. Any pointers would be greatly appreciated.
<template>
<div class="home">
<div class="module-list">
<input type="text" v-model.trim="search" placeholder="Search"/>
<div>
<ul>
<li class="modules" v-for="value in modulesList" :key="value.id">
{{ value.setup }}
</li>
</ul>
</div>
</div>
<div class="moduleinfo">
<h2>Module info</h2>
<!-- <p>{{ value.punchline }}</p> -->
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'Home',
data: function(){
return {
jokes: [],
search : ""
}
},
mounted() {
this.getModules();
},
methods: {
getModules() {
var self = this
const options = {
method: 'GET',
url: 'https://dad-jokes.p.rapidapi.com/joke/search',
params: {term: 'car'},
headers: {
'x-rapidapi-key': '...',
'x-rapidapi-host': 'dad-jokes.p.rapidapi.com'
}
};
axios.request(options)
.then(response => {
self.jokes = response.data;
console.log(response.data);
}).catch(function (error) {
console.error(error);
});
}
},
computed: {
modulesList: function () {
var jokes = this.jokes.body;
var search = this.search;
if (search){
jokes = jokes.filter(function(value){
if(value.setup.toLowerCase().includes(search.toLowerCase())) {
return jokes;
}
})
}
return jokes;
}
},
};
</script>
Thanks!
I was building a sample Single File Component in my Vue 2 CLI app, and when I came back to post it, Ryoko had already answered the question with the same approach that I recommend, adding a new property to track showing the punchline.
Since I already built it, I figured that I might as well post my component, which does change the layout, using a table instead of a list, but the functionality works.
<template>
<div class="joke-list">
<div class="row">
<div class="col-md-6">
<table class="table table-bordered">
<thead>
<tr>
<th>SETUP</th>
<th>PUNCHLINE</th>
</tr>
</thead>
<tbody>
<tr v-for="(joke, index) in jokes" :key="index">
<td>
{{ joke.setup }}
</td>
<td>
<span v-if="joke.showPunchline">{{ joke.punchline }}</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
jokes: [
{
setup: "setup1",
punchline: "punchline1"
},
{
setup: "setup2",
punchline: "punchline2"
},
{
setup: "setup3",
punchline: "punchline3"
}
]
}
},
methods: {
getPunchline(index) {
this.jokes[index].showPunchline = true;
},
addPropertyToJokes() {
// New property must be reactive
this.jokes.forEach( joke => this.$set(joke, 'showPunchline', false) );
}
},
mounted() {
this.addPropertyToJokes();
}
}
</script>
You can add a new property inside the data object and then make a new method to set it accordingly when you click the <a> tag. Have a look at the code below, it was a copy of your current solution, edited & simplified to show the addition that I made to make it easier for you to find it.
The select method will insert the object of the clicked joke to the selectedJoke so you can render it below the Module Info.
Because it's defaults to null, and it might be null or undefined, you have to add v-if to the attribute to check wether there is a value or not so you don't get error on the console.
<template>
<div class="home">
<div class="module-list">
<input type="text" v-model.trim="search" placeholder="Search"/>
<div>
<ul>
<li class="modules" v-for="value in modulesList" :key="value.id">
{{ value.setup }}
</li>
</ul>
</div>
</div>
<div class="moduleinfo">
<h2>Module info</h2>
<p v-if="selectedJoke">{{ selectedJoke.punchline }}</p>
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'Home',
data: function(){
return {
jokes: [],
search : "",
selectedJoke: null,
}
},
methods: {
select(joke) {
this.selectedJoke = joke;
},
},
};
</script>

VueJS component display other component content

The objective: Vue component input-address has to be inside Vue component mail-composer and display a list of addresses only when someone click Address Book button. When someone click one of displayed mails or fill the To field by hand, createdmail.to has to get the value and I have to hide the list of addresses.
Vue component mail-composer. This component receives a list of addresses. (Everything is working here, I think the only part that is not working properly is v-model inside input-address tag)
Vue.component('mail-composer', {
props: ['addressesbook'],
methods: {
send: function(createmail) {
this.$emit('send', createmail);
}
},
template:
`
<div>
<input-address :addresses="addressesbook" v-model="createmail.to"></input-address>
<p><b>Subject: </b><input type="text" v-model="createmail.subject"></input></p>
<p><b>Body: </b><textarea v-model="createmail.body"></textarea></p>
<button #click="send(createmail)">Send</button>
</div>
`,
data(){
return{
createmail:{
to: '',
subject: '',
body: ''
}
}
}
});
The other Vue component is this one, which is in the same file. (I think all problems are here).
I need to display the list of addresses only when someone click Address Book button, and I have to hide it when someone click again the button or one of the emails which are in the list. When someone clicks a mail from list, the createmail.to property from the mail-composer has to get the value of the mail , also if I decide to put the mail by hand it has to occurs the same.
Vue.component('input-address',{
props:["addresses"],
template:
`
<div>
<label><b>To: </b><input type="text"></input><button #click="!(displayAddressBook)">Address Book</button></label>
<ul v-if="displayAddressBook">
<li v-for="address in addresses">
{{address}}
</li>
</ul>
</div>
`,
data(){
return{
displayAddressBook: false
}
}
})
There're some errors in your code:
#click="!(displayAddressBook)" should be #click="displayAddressBook = !displayAddressBook" - the first really does nothing (interesting), the second (suggested) sets the value of displayAddressBook to the opposite it has currently.
the input-address component does not really do anything with the input field (missing v-model)
the changes in the child component (input-address) are not sent back to the parent (added a watcher to do that in the child component)
the parent component (mail-composer) has to handle the values emitted from the child (added the #address-change action handler)
the v-for in your input-address component does not have a key set. Added key by using the index for it (not the best solution, but easy to do).
just put createmail.to: {{ createmail.to }} at the end of MailComposer, so you can see how it changes
Suggestions
always use CamelCase for component names - if you get used to it, then you get less "why is it not working?!" moments
watch for typos: createmail doesn't look good - createEmail or just simply createemail would be better (ok, it doesn't look so nice - maybe you should choose a totally different name for that)
Vue.component('InputAddress', {
props: ["addresses"],
data() {
return {
displayAddressBook: false,
address: null
}
},
template: `
<div>
<label><b>To: </b>
<input
type="text"
v-model="address"
/>
<button
#click="displayAddressBook = !displayAddressBook"
>
Address Book
</button>
</label>
<ul v-if="displayAddressBook">
<li
v-for="(address, i) in addresses"
:key="i"
#click="clickAddressHandler(address)"
>
{{address}}
</li>
</ul>
</div>
`,
watch: {
address(newVal) {
// emitting value to parent on change of the address
// data attribute
this.$emit('address-change', newVal)
}
},
methods: {
clickAddressHandler(address) {
// handling click on an address in the address book
this.address = address
this.displayAddressBook = false
}
}
})
Vue.component('MailComposer', {
props: ['addressesbook'],
data() {
return {
createmail: {
to: '',
subject: '',
body: ''
}
}
},
methods: {
send: function(createmail) {
this.$emit('send', createmail);
},
addressChangeHandler(value) {
this.createmail.to = value
}
},
template: `
<div>
<input-address
:addresses="addressesbook"
v-model="createmail.to"
#address-change="addressChangeHandler"
/>
<p>
<b>Subject: </b>
<input
type="text"
v-model="createmail.subject"
/>
</p>
<p>
<b>Body: </b>
<textarea v-model="createmail.body"></textarea>
</p>
<button #click="send(createmail)">Send</button><br />
createmail.to: {{ createmail.to }}
</div>
`
});
new Vue({
el: "#app",
data: {
addressesbook: [
'abcd#abcd.com',
'fghi#fghi.com'
]
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<mail-composer :addressesbook="addressesbook" />
</div>

setTimeout for v-alert after adding Item to an array basket

this is my rightTableMenu template
<template>
<div>
<h1 align="center">{{ title }}</h1>
<v-alert type="info" icon="mdi-emoticon-sad" v-if="basketStatus">
Empty Basket, please add some to basket
</v-alert>
<div v-if="changeAlertStatus()">
<v-alert
type="success"
icon="mdi-emoticon-happy"
:value="alert"
transition="fade-transition"
>
thank you
</v-alert>
<v-simple-table>
<template v-slot:default>
<thead>
<tr>
<th class="text-left">Quantity</th>
<th class="text-left">Name</th>
<th class="text-left">Price</th>
</tr>
</thead>
<tbody>
<tr v-for="item in basket" :key="item.name">
<td>
<v-icon #click="increaseQuantity(item)">add_box</v-icon>
<span>{{ item.quantity }}</span>
<v-icon #click="decreaseQuantity(item)"
>indeterminate_check_box
</v-icon>
</td>
<td>{{ item.name }}</td>
<td>{{ (item.price * item.quantity).toFixed(2) }}</td>
</tr>
</tbody>
</template>
</v-simple-table>
<v-divider color="black"></v-divider>
<v-row id="basket_checkout" style="margin: 0">
<v-col>
<p>Subtotal:</p>
<p>Delivery:</p>
<p>Total amount:</p>
</v-col>
<v-col class="text-right">
<p>${{ subTotalResult }}</p>
<p>$10</p>
<p class="font-weight-bold">${{ totalPriceResult }}</p>
</v-col>
</v-row>
<v-row>
<v-spacer></v-spacer>
<v-btn depressed class="orange" v-on:click="submitOrder">
<v-icon>shopping_basket</v-icon>
</v-btn>
</v-row>
</div>
</div>
</template>
as you see there are two alerts one is showing when there is not item inside the array basket by checking the following
basketStatus() {
return this.$store.getters.basket.length === 0;
},
which is computed property
my data property section is
data() {
return {
title: "Current Basket",
alert: false,
};
},
but for the second v-alert, I wanna to have the alert to be shown and disappear after few sec and so far I have done the following for it
async changeAlertStatus() {
if (this.$store.getters.basket.length !== 0) {
this.alert = true;
try {
const response = await setTimeout(() => {
this.alert = false;
}, 100);
console.log("this is the resonse " + response);
} catch (err) {
console.log("fetch failed", err);
}
} else {
this.alert = false;
}
},
which is a method
I am confused how to interject the function inside the div part without using v-if directive and my async changeAlertStatus gets in the infinite loop when I check it inside the console and the v-alert does not get disappear
any thoughts on that?
if there is more info needed , please let me know
thank you
just in case my leftTableMenu is follows
<template>
<div>
<div v-if="showError['situation']">
<!--
basically, when you close the alert, the value of the alert goes to false
so you need to turn it to true when there is an error :value="showError.situation" -->
<app-alert :text="showError.message" :value.sync="showError.situation"></app-alert>
</div>
<h1 align="center">{{ title }}</h1>
<v-simple-table od="menu-table">
<template v-slot:default>
<thead>
<tr>
<th class="text-left">Name</th>
<th class="text-left">Price</th>
<th class="text-left">Add</th>
</tr>
</thead>
<tbody>
<tr v-for="item in menuItems" :key="item.name">
<td>
<span id="id_name">{{ item.name }}</span>
<br />
<span id="menu_item_description">{{ item.description }}</span>
</td>
<td>{{ item.price }}</td>
<td>
<v-btn text v-on:click="addToBasket(item)">
<v-icon color="orange">1add_shopping_cart</v-icon>
<span></span>
</v-btn>
</td>
</tr>
</tbody>
</template>
</v-simple-table>
</div>
</template>
<script>
export default {
name: 'LeftTableMenu',
data() {
return {
title: "Menu Items",
};
},
methods: {
addToBasket(item) {
this.$store.dispatch("addToBasket", item);
},
},
computed: {
showError() {
return this.$store.getters.showError;
},
menuItems() {
return this.$store.getters.menuItems;
},
},
};
You can add a watcher on your computed property to see if it's changed.
When it changes you can update your data to show or the "Success" alert and then set a timeout to hide it back again after some time.
Here is an updated example with some changed param names for clarity.
I changed the computed name to be emptyBasket
computed: {
emptyBasket() {
return this.$store.getters.basket.length === 0;
}
},
I added showSuccessAlert to data
data() {
return {
showSuccessAlert: false
};
},
And here it the watcher that updates the showSuccessAlert
watch: {
emptyBasket: {
immediate: true,
handler(newVal, oldVal) {
this.showSuccessAlert = !newVal;
setTimeout(() => {
this.showSuccessAlert = oldVal;
}, 5000);
}
}
}
The watcher will be triggered immediately (not sure you need it),
newVal and oldVal are representing the new and old state of emptyBasket.
So when newVal is false it means that the basket is not empty, hence the update of showSuccessAlert = !newVal
I created a simple working sandbox with your code.
Here is the link:
https://codesandbox.io/s/smoosh-cherry-ngpqu?file=/src/App.vue
Should probably be watching backStatus and then do your alert stuff
watch: {
// whenever question changes, this function will run
backStatus: function (newVal, oldVal) {
this.alert = newVal;
const response = setTimeout(() => {
this.alert = oldVal;
}, 100);
// swap the vals around if needed
}
}
maybe you might need immediate too, but that's up to how your want to display things.
https://v2.vuejs.org/v2/guide/computed.html#Watchers
Rather than calling changeAlertStatus in the v-if directive, can that just be bound to the this.alert property? Then, when the Add to Cart button is clicked, its callback can set this.alert to true, causing the alerts to display. Just after setting this.alert to true, register the setTimeout to revert it back to false
Example: (Please excuse the abstract-ness of it, I feel like this is some missing code from the original post, specifically the add to cart button)
<template>
<div id="app">
<div class="alerts" v-if="alert">
<div>Thank you</div>
</div>
<button #click="handleAddToCart">
Add to cart
</button>
</div>
</template>
<script>
module.exports = {
el: "#app",
data: {
alert: false,
basketStatus: false
},
methods: {
handleAddToCart() {
this.alert = true;
setTimeout(() => {
this.alert = false;
}, 3000);
}
}
};
</script>
You can achieve this timeout on alert using watch like the others guys said:
<template>
<div class="w-full">
<div class="w-full" v-for="item in cart" :key="item.id">
<p>{{item.name}}</p>
</div>
<div class="w-full p-2 bg-yellow" v-if="alert">
<p>Your cart is empty</p>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'CartList',
data() {
return {
cart: [],
alert: true
}
},
watch: {
cart(val) {
if(!val.length) {
this.alert = true
} else {
setTimeout(() => {
this.alert = false
}, 2000)
}
}
},
mounted() {
this.getCart()
},
methods: {
getCart() {
axios('/cart/get').then((response) => {
this.cart = response.data.cart
})
}
}
}
</script>
But you can add some extra code to your request function and add the timeout there too:
getCart() {
axios('/cart/get')
.then((response) {
if(response.data.cart.length) {
setTimeout( () => {
this.alert = false
}, 2000)
}
})
}

The problem of data transfer between two child components

Good afternoon, I have two child components Header and Pagination. In Header, I have an input search engine and two inputs (title and body) in order to be able to add a post to Pagination. I managed to transfer the search value to the Pagination component, but I don’t know how to transfer the value from two inputs (title, body). I use to transfer the event bus. Help me please pass the value of the two inputs (title, body) into the Pagination component when you click the AddPost button.
My code on GitHub
Screenshot of app
My code of component Header:
<template>
<div class="header">
<input type="text" v-model="search" class="header_input_search" placeholder="Search" #input="saveMessage" />
<img src="src/assets/milk.png">
<div class="header_div_inputs">
<input type="text" v-model="createTitle" class="created"/>
<p><input type="text" v-model="createBody" class="createBody"/></p>
</div>
<button #click="addPost()" class="addPost">AddPost</button>
</div>
</template>
<script>
import axios from 'axios';
import {eventEmitter} from './main'
export default {
name: 'Header',
data () {
return {
search: '',
createTitle: '',
createBody: '',
}
},
methods:{
saveMessage(){
eventEmitter.$emit('messageSave', this.search)
},
}
}
</script>
My code of component Pagination:
<template>
<div class = "app">
<ul>
<li v-for="(post, index) in paginatedData" class="post" :key="index">
<router-link :to="{ name: 'detail', params: {id: post.id, title: post.title, body: post.body} }">
<img src="src/assets/nature.jpg">
<p class="boldText"> {{ post.title }}</p>
</router-link>
<p> {{ post.body }}</p>
</li>
</ul>
<div class="allpagination">
<button type="button" #click="page -=1" v-if="page > 0" class="prev"><<</button>
<div class="pagin">
<button class="item"
v-for="n in evenPosts"
:key="n.id"
v-bind:class="{'selected': current === n.id}"
#click="page=n-1">{{ n }} </button>
</div>
<button type="button" #click="page +=1" class="next" v-if="page < evenPosts-1">>></button>
</div>
</div>
</template>
<script>
import {mapState} from 'vuex'
import {eventEmitter} from './main'
export default {
name: 'app',
data () {
return {
current: null,
page: 0,
visiblePostID: '',
pSearch: ''
}
},
mounted(){
this.$store.dispatch('loadPosts')
},
computed: {
...mapState([
'posts'
]),
evenPosts: function(posts){
return Math.ceil(this.posts.length/6);
},
paginatedData() {
const start = this.page * 6;
const end = start + 6;
return this.filteredPosts.slice(start, end);
},
filteredPosts() {
return this.posts.filter((post) => {
return post.title.match(this.pSearch);
});
},
},
created(){
eventEmitter.$on('messageSave', (string) => {
this.pSearch = string
})
}
}
</script>
You can wrap title and body in an object
addPost() {
const post = {
title: this.createTitle,
body: this.createBody
}
eventEmitter.$emit('postAdd', post)
}
and then listen as normal
created(){
eventEmitter.$on('postAdd', (post) => {
console.log(post)
// do whatever you want
})
}
I have not worked on vue js but agreed with #ittus answer. You can make an object consisting of your required data which you want to share across the component and pass it as an event data.

Vue.js - Computed property not updating - child component

I've created a simple component named DefaultButton.
It bases on properties, that are being set up whenever this component is being created.
The point is that after mounting it, It does not react on changes connected with "defaultbutton", that is an object located in properties
<template>
<button :class="buttonClass" v-if="isActive" #click="$emit('buttonAction', defaultbutton.id)" >
{{ this.defaultbutton.text }}
</button>
<button :class="buttonClass" v-else disabled="disabled">
{{ this.defaultbutton.text }}
</button>
</template>
<script>
export default {
name: "defaultbutton",
props: {
defaultbutton: Object
},
computed: {
buttonClass() {
return `b41ngt ${this.defaultbutton.state}`;
},
isActive() {
return (this.defaultbutton.state === "BUTTON_ACTIVE" || this.defaultbutton.state === "BUTTON_ACTIVE_NOT_CHOSEN");
}
}
};
</script>
Having following component as a parent one:
<template>
<div v-if="state_items.length == 2" class="ui placeholder segment">
{{ this.state_items[0].state }}
{{ this.state_items[1].state }}
{{ this.current_active_state }}
<div class="ui two column very relaxed stackable grid">
<div class="column">
<default-button :defaultbutton="state_items[0]" #buttonAction="changecurrentstate(0)"/>
</div>
<div class="middle aligned column">
<default-button :defaultbutton="state_items[1]" #buttonAction="changecurrentstate(1)"/>
</div>
</div>
<div class="ui vertical divider">
Or
</div>
</div>
</template>
<script type="text/javascript">
import DefaultButton from '../Button/DefaultButton'
export default {
name: 'changestatebox',
data() {
return {
current_active_state: 1
}
},
props: {
state_items: []
},
components: {
DefaultButton
},
methods: {
changecurrentstate: function(index) {
if(this.current_active_state != index) {
this.state_items[this.current_active_state].state = 'BUTTON_ACTIVE_NOT_CHOSEN';
this.state_items[index].state = 'BUTTON_ACTIVE';
this.current_active_state = index;
}
},
},
mounted: function () {
this.state_items[0].state = 'BUTTON_ACTIVE';
this.state_items[1].state = 'BUTTON_ACTIVE_NOT_CHOSEN';
}
}
</script>
It clearly shows, using:
{{ this.state_items[0].state }}
{{ this.state_items[1].state }}
{{ this.current_active_state }}
that the state of these items are being changed, but I am unable to see any results on the generated "DefaultButtons". Classes of objects included in these components are not being changed.
#edit
I've completely changed the way of delivering the data.
Due to this change, I've abandoned the usage of an array; instead I've used two completely not related object.
The result is the same - class of the child component's object is not being
DefaulButton.vue:
<template>
<button :class="buttonClass" v-if="isActive" #click="$emit('buttonAction', defaultbutton.id)" >
{{ this.defaultbutton.text }}
</button>
<button :class="buttonClass" v-else disabled="disabled">
{{ this.defaultbutton.text }}
</button>
</template>
<style lang="scss">
import './DefaultButton.css';
</style>
<script>
export default {
name: "defaultbutton",
props: {
defaultbutton: {
type: Object,
default: () => ({
id: '',
text: '',
state: '',
})
}
},
computed: {
buttonClass() {
return `b41ngt ${this.defaultbutton.state}`;
},
isActive() {
return (this.defaultbutton.state === "BUTTON_ACTIVE" ||
this.defaultbutton.state === "BUTTON_ACTIVE_NOT_CHOSEN");
}
}
};
</script>
ChangeStateBox.vue:
<template>
<div class="ui placeholder segment">
{{ this.state_first.state }}
{{ this.state_second.state }}
{{ this.current_active_state }}
<div class="ui two column very relaxed stackable grid">
<div class="column">
<default-button :defaultbutton="state_first" #buttonAction="changecurrentstate(0)"/>
</div>
<div class="middle aligned column">
<default-button :defaultbutton="state_second" #buttonAction="changecurrentstate(1)"/>
</div>
</div>
<div class="ui vertical divider">
Or
</div>
</div>
</template>
<script type="text/javascript">
import DefaultButton from '../Button/DefaultButton'
export default {
name: 'changestatebox',
data() {
return {
current_active_state: 1
}
},
props: {
state_first: {
type: Object,
default: () => ({
id: '',
text: ''
})
},
state_second: {
type: Object,
default: () => ({
id: '',
text: ''
})
},
},
components: {
DefaultButton
},
methods: {
changecurrentstate: function(index) {
if(this.current_active_state != index) {
if(this.current_active_state == 1){
this.$set(this.state_first, 'state', "BUTTON_ACTIVE_NOT_CHOSEN");
this.$set(this.state_second, 'state', "BUTTON_ACTIVE");
} else {
this.$set(this.state_first, 'state', "BUTTON_ACTIVE");
this.$set(this.state_second, 'state', "BUTTON_ACTIVE_NOT_CHOSEN");
}
this.current_active_state = index;
}
},
},
created: function () {
this.state_first.state = 'BUTTON_ACTIVE';
this.state_second.state = 'BUTTON_ACTIVE_NOT_CHOSEN';
}
}
</script>
You're declaring props wrong. It is either an array of prop names or it is an object with one entry for each prop declaring its type, or it is an object with one entry for each prop declaring multiple properties.
You have
props: {
state_items: []
},
but to supply a default it should be
props: {
state_items: {
type: Array,
default: []
}
},
But your problem is most likely that you're mutating state_items in such a way that Vue can't react to the change
Your main problem is the way you are changing the button state, according with Array change detection vue can't detect mutations by indexing.
Due to limitations in JavaScript, Vue cannot detect the following
changes to an array:
When you directly set an item with the index, e.g.
vm.items[indexOfItem] = newValue When you modify the length of the
array, e.g. vm.items.length = newLength
In case someone will be having the same issue:
#Roy J as well as #DobleL were right.
The reason behind this issue was related with the wrong initialization of state objects.
According to the documentation:
Vue cannot detect property addition or deletion.
Since Vue performs the getter/setter conversion process during instance
initialization, a property must be present in the
data object in order for Vue to convert it and make it reactive.
Before reading this sentence, I used to start with following objects as an initial data:
var local_state_first = {
id: '1',
text: 'Realized',
};
var local_state_second = {
id: '2',
text: 'Active'
};
and the correct version of it looks like this:
var local_state_first = {
id: '1',
text: 'Realized',
state: 'BUTTON_ACTIVE'
};
var local_state_second = {
id: '2',
text: 'Active',
state: 'BUTTON_ACTIVE'
};
whereas declaring the main component as:
<change-state-box :state_first="local_state_first" :state_second="local_state_second" #buttonAction="onbuttonAction"/>
Rest of the code remains the same ( take a look at #edit mark in my main post )

Categories