how to run side effect on value change in Vue.js? - vuejs2

my goal is to run a sideeffect when the "monthIndex" changes. In react I would use a useEffect hook with the dependency, but I am new to vue. I am basically incrementing the "monthIndex" via buttons, which changes the index in the array "daysInMonth". Every time the month index changes it should run the updateCalendar method and render the days of the calendar. That is my goal, thanks!
<template>
<div id="app">
<div class="header">
<div class="controls">
<button v-on:click="prevMonthHandler">prev</button>
<h2>Date</h2>
<button v-on:click="nextMonthHandler">next</button>
</div>
</div>
<div class="weekdays">
<p class="weekday" v-for="(weekday, index) in weekdays" :key="index">
{{ weekday }}
</p>
</div>
<div class="grid">
{{ calendarDays }}
<!-- <div class="day" v-for="(day, index) in calendarDays" :key="index">
{{ day }}
</div> -->
</div>
</div>
</template>
<script>
export default {
name: "App",
mounted() {
this.updateCalendar();
},
data() {
return {
monthIndex: 0,
calendarDays: [],
daysInMonth: [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30],
weekdays: [
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
],
};
},
methods: {
updateCalendar() {
for (let i = 0; i < this.daysInMonth[this.monthIndex]; i++) {
this.calendarDays.push(i);
}
},
nextMonthHandler() {
if (this.monthIndex > 12) {
this.monthIndex++;
}
},
prevMonthHandler() {
if (this.monthIndex < 0) {
this.monthIndex--;
}
},
},
};
</script>

You can use watch property to detect changes to a variable.
You can see more functionalities of watch here
watch : {
monthIndex(newVal, oldVal) {
console.log(`Value updated from ${oldVal} to ${newVal}`)
}
}

Related

Changing class on radio button change

My first column (Test1) is active, so the background turns orange. Why does the active class not change to the second column (Test2) when I click the radio button?
I'd like it to change the active class when the radio button is clicked.
I have the following template:
<template v-if="columns" v-for="(item, index) in columns" v-bind="item">
<div class="card-container"
:style="{'height': item.height+'%'}"
:class="{ active: item.active}">
<div class="card-inner">
<input type="radio" v-if="item.selectedDefault" checked v-model="currentInput" name="cardRadioGroup"
:value="item.id" v-on:change="updateDetails(item, index)"/>
<input type="radio" v-else v-model="currentInput" name="cardRadioGroup" :value="item.id"
v-on:change="updateDetails(item, index)"/>
<template v-if="item.gbAmount === null">
Onbeperkt
</template>
<template v-else>
{{ item.gbAmount }} GB
</template>
{{ currentInput }}
</div>
</div>
</template>
With the following Vue code:
import {onMounted} from "vue";
export default {
name: 'Card',
components: {Details},
data() {
const columns =
{
"Test1": {
"id": 1,
"gbAmount": 0,
"price": 5,
"selectedDefault": false,
"informationGet": ["bla", "bla"],
"informationUsing": "Boop.",
"recommended": false,
"height": 16.66,
"active": true,
},
"Test2": {
"id": 2,
"gbAmount": 1,
"price": 10,
"selectedDefault": false,
"informationGet": ["beh", "beh"],
"informationUsing": "Beep",
"recommended": false,
"height": 33.33,
"active": false,
},
}
return {
columns,
currentColumn: {},
checkedCard: [],
currentInput: null,
};
},
methods: {
updateDetails(item, index) {
this.currentColumn = {
...item,
name: index,
active: true,
}
console.log($(this.currentColumn));
},
},
setup() {
return {};
},
};
And CSS:
.active {
background: orange;
}
You update this.currentColumn but in template use this.columns. Correct will be:
updateDetails(item, index) {
Object.keys(this.columns).forEach(key => this.columns[key].active = false);
this.columns[index].active = true;
console.log(this.columns);
}
<template>
<div v-for="column in columns" :key="column.id">
<div
class="card-container"
:style="{ height: column.height + '%' }"
:class="{ active: column.id === currentInput }"
>
<div class="card-inner">
<input
type="radio"
name="cardRadioGroup"
:value="column.id"
v-model="currentInput"
/>
<template v-if="column.gbAmount === null"> Onbeperkt </template>
<template v-else> {{ column.gbAmount }} GB </template>
{{ currentInput }}
</div>
</div>
</div>
</template>
<script lang="ts">
import { ref } from 'vue'
const columns = [
{
id: 1,
gbAmount: 0,
price: 5,
selectedDefault: false,
informationGet: ['bla', 'bla'],
informationUsing: 'Boop.',
recommended: false,
height: 16.66,
},
{
id: 2,
gbAmount: 1,
price: 10,
selectedDefault: false,
informationGet: ['beh', 'beh'],
informationUsing: 'Beep',
recommended: false,
height: 33.33,
},
]
export default {
name: 'Card',
setup() {
const currentInput = ref(columns[0].id)
return {
columns,
currentInput,
}
},
}
</script>

How to change input value for individual object in an array using v-for in Vue?

I'm making a shopping cart. I want to show the price of each product based on their quantity.
I made the buttons to add or subtract the quantity of products. However the quantity changes in all products because they all share the same data. How can I do to change quantity for the specific product?
<div v-for="(cartProduct,index) in cartProducts" :key="index" class="px-3 py-2">
<div id="cartProducts">
<p>{{cartProduct.description}}</p>
<button #click="subtract" >-</button> <p>{{quantity}}</p> <button #click="add">+</button>
<p>$ {{cartProduct.price*quantity}}</p>
</div>
</div>
export default {
data(){
return{
quantity:1
}
},
methods:{
add(){
this.quantity++;
},
subtract(){
this.quantity--;
},
}
You have to do is, build an object for all products having quantity field, just like this
<template>
<div>
<div
v-for="(cartProduct, index) in cartProducts"
:key="index"
class="px-3 py-2"
>
<div id="cartProducts">
<p>{{ cartProduct.description }}</p>
<button #click="subtractProduct(index)">-</button>
<p>{{ cartProduct.quantity }}</p>
<button #click="addProduct(index)">+</button>
<p>$ {{ cartProduct.price * cartProduct.quantity }}</p>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
cartProducts: [
{
description: "product 1",
price: 100,
quantity: 0,
},
{
description: "product 2",
price: 100,
quantity: 0,
},
{
description: "product 3",
price: 100,
quantity: 0,
},
],
};
},
methods: {
addProduct(index) {
this.cartProducts[index].quantity++;
},
subtractProduct(index) {
if (this.cartProducts[index].quantity !== 0)
this.cartProducts[index].quantity--;
},
},
};
</script>

vuejs2 el-table-column column prop with computed value

I'm new in vuejs. I don't see how to use a "computed" value in a table of the ui-element librairy. Here is how I tried ..
<template>
<div class="row">
<div class="col-md-12">
<h4 class="title">Commandes en cours</h4>
</div>
<!--<div v-if="$can('manage-order')">You can manage order.</div>-->
<div class="col-12">
<card title="">
<div>
<div class="col-12 d-flex justify-content-center justify-content-sm-between flex-wrap">
<el-select
class="select-default mb-3"
style="width: 200px"
v-model="pagination.perPage"
placeholder="Per page">
<el-option
class="select-default"
v-for="item in pagination.perPageOptions"
:key="item"
:label="item"
:value="item">
</el-option>
</el-select>
<el-input type="search"
class="mb-3"
style="width: 200px"
placeholder="Search records"
v-model="searchQuery"
aria-controls="datatables"/>
</div>
<div class="col-sm-12">
<el-table stripe
style="width: 100%;"
:data="queriedData"
border>
<el-table-column v-for="column in tableColumns"
:key="column.label"
:min-width="column.minWidth"
:prop="column.prop"
:label="column.label">
</el-table-column>
<el-table-column
:min-width="120"
fixed="right"
label="Actions">
<template slot-scope="props">
<a v-tooltip.top-center="'Like'" class="btn-info btn-simple btn-link"
#click="handleLike(props.$index, props.row)">
<i class="fa fa-heart"></i></a>
<a v-tooltip.top-center="'Edit'" class="btn-warning btn-simple btn-link"
#click="handleEdit(props.$index, props.row)"><i
class="fa fa-edit"></i></a>
<a v-tooltip.top-center="'Delete'" class="btn-danger btn-simple btn-link"
#click="handleDelete(props.$index, props.row)"><i class="fa fa-times"></i></a>
</template>
</el-table-column>
</el-table>
</div>
</div>
<div slot="footer" class="col-12 d-flex justify-content-center justify-content-sm-between flex-wrap">
<div class="">
<p class="card-category">Showing {{from + 1}} to {{to}} of {{total}} entries</p>
</div>
<l-pagination class="pagination-no-border"
v-model="pagination.currentPage"
:per-page="pagination.perPage"
:total="pagination.total">
</l-pagination>
</div>
</card>
</div>
</div>
</template>
<script>
import {Table, TableColumn, Select, Option} from 'element-ui'
import LPagination from 'src/components/Pagination.vue'
import Fuse from 'fuse.js'
export default {
components: {
LPagination,
[Table.name]: Table,
[Select.name]: Select,
[Option.name]: Option,
[TableColumn.name]: TableColumn
},
computed: {
clientName(customer){
return customer.firstname + ' '+ customer.lastname
},
pagedData () {
return this.tableData.slice(this.from, this.to)
},
/***
* Searches through table data and returns a paginated array.
* Note that this should not be used for table with a lot of data as it might be slow!
* Do the search and the pagination on the server and display the data retrieved from server instead.
* #returns {computed.pagedData}
*/
queriedData () {
let result = this.tableData
if (this.searchQuery !== '') {
result = this.fuseSearch.search(this.searchQuery)
this.pagination.total = result.length
}
return result.slice(this.from, this.to)
},
to () {
let highBound = this.from + this.pagination.perPage
if (this.total < highBound) {
highBound = this.total
}
return highBound
},
from () {
return this.pagination.perPage * (this.pagination.currentPage - 1)
},
total () {
this.pagination.total = this.tableData.length
return this.tableData.length
}
},
data () {
return {
pagination: {
perPage: 5,
currentPage: 1,
perPageOptions: [5, 10, 25, 50],
total: 0
},
searchQuery: '',
propsToSearch: ['id_order'],
tableColumns: [
{
prop: 'id_order',
label: 'ID',
minWidth: 200
},
{
prop: "clientName(customer)",
label: 'Client',
minWidth: 200,
}
],
fuseSearch: null,
tableData:[]
}
},
methods: {
handleLike (index, row) {
alert(`Your want to like ${row.name}`)
},
handleEdit (index, row) {
alert(`Your want to edit ${row.name}`)
},
handleDelete (index, row) {
let indexToDelete = this.tableData.findIndex((tableRow) => tableRow.id === row.id)
if (indexToDelete >= 0) {
this.tableData.splice(indexToDelete, 1)
}
}
},
mounted () {
this.fuseSearch = new Fuse(this.tableData, {keys: ['id_order']})
},
created (){
this.$store.dispatch('ps_orders/get_ps_orders').then(
this.tableData = this.$store.getters["ps_orders/orders"])
}
}
</script>
<style>
</style>
My object is like (for a row)
{
"id_order": 4641,
"customer": {
"id_customer": 9008,
"firstname": "Pierre",
"lastname": "dupont"
}
}
In the column "Client" I would like to have "customer.firstname + " " + customer.lastname ... but my computed "method" is not working (I guess it is completly wrong)
Thanks for your help
Here the answer : you can't declare a computed with a parameter, here is how to solve
<el-table-column
label="Client" >
<template slot-scope="scope">
{{ clientName(scope.row.customer) }}
</template>
</el-table-column>
AND
computed: {
clientName(){
return (customer) => customer.firstname + ' '+ customer.lastname
},

Vue: Getting a default value for v-select

I have a list of cartItems and I'm generating a dropdown for each one. Each cartItem has a field called orig_quantity that I'm looking to set the default value of the drop down to. I tried doing :value="item.orig_quantity" but that doesn't seem to be doing it.
computed: {
quantityOptions: function() {
return [1,2,3]
}
}
<div v-for="(item, index) in cartItems"
<div>{item.product_name}</div>
<v-select :options="quantityOptions"
v-on:change="updateQuantity($event,item)">
</v-select>
</div>
Sorry about that - I misunderstood your question at first.. I have updated my answer below.. This should be sufficient to get the idea across (the code stands to be cleaned - its 'pseudo' enough to get the idea across, though)..
In CodePen form, which I find easier to read:
https://codepen.io/oze4/pen/vMLggE
Vue.component("v-select", VueSelect.VueSelect);
new Vue({
el: "#app",
data: {
cartItems: [{
product_name: "Chair",
original_quantity: 7,
total_quantity: 9,
pending_quantity: null,
price: "$19.99"
},
{
product_name: "Couch",
original_quantity: 3,
total_quantity: 6,
pending_quantity: null,
price: "$29.99"
}
],
},
methods: {
getStock(cartItem) {
let ci = this.cartItems.find(i => {
return i.product_name === cartItem.product_name;
});
return [...Array(ci.total_quantity + 1).keys()].slice(1);
},
updateQty(cartItem) {
alert("this is where you would post");
let ci = this.cartItems.find(i => {
return i.product_name === cartItem.product_name;
});
ci.original_quantity = ci.pending_quantity;
}
}
});
h5,
h3 {
margin: 0px 0px 0px 0px;
}
.reminder {
color: red;
}
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.10/dist/vue.js"></script>
<script src="https://unpkg.com/vue-select#2.6.4/dist/vue-select.js"></script>
<div id="app">
<h4>Cart Items:</h4>
<div v-for="(item, index) in cartItems">
<h3>{{ item.product_name }}</h3>
<h5>Price: {{ item.price }} | InBasket: {{ item.original_quantity }}</h5>
<small>Change Quantity: </small>
<v-select :value="item.original_quantity" :options="getStock(item)" #change="item.pending_quantity = $event"></v-select>
<button #click="updateQty(item)" type="button">Update {{ item.product_name }} Qty</button><small class="reminder">*update after changing quantity</small>
<br/>
<hr/>
</div>
</div>
You should be able to add a v-model attribute to the select.
<div v-for="(item, index) in cartItems"
<v-select v-model="item.orig_quantity" :options="quantityOptions"
v-on:change="updateQuantity($event,item)">
</v-select>
</div>

Having trouble passing data between Components

How do I pass an Object Array created by a method within my component to another component? I've tried using props but perhaps my understanding of how props work isn't correct.
<template>
<div class="media-body">
<ul>
<li v-for="item in items" v-bind:class="{ active: item.active }">{{ item.text }}
</li>
</ul>
<button type="button" class="btn btn-danger" v-on:click="addItem">Test</button>
</template>
<script>
export default {
data() {
return {
item: "a",
item2: "b",
item3: "c",
item4: "d",
items: [],
}
},
methods: {
addItem: function () {
var testArray = this.item.concat(this.item2, this.item3, this.item4);
for (var i = 0; i < testArray.length; i++) {
this.items.push({
text: testArray[i],
active: false });
}
// if I check console.log(this.items) at this point I can see the data I want to pass
},
}
}
</script>
Secondary Component I'm trying to pass Data to.
<template>
<div class="media-body">
<div class="media-label">Title:
<textarea class="form-control" placeholder="Title"></textarea>
</div>
</div>
</template>
<script>
export default {
props: ['items'],
data() {
return {
}
},
</script>
To pass the props to other component you have to write following code in the first component:
added <secondComponent :items="items" /> in HTML code.
import and use secondComponent in vue component like this: components: [secondComponent]
Here is complete code with these changes:
<template>
<div class="media-body">
<ul>
<li v-for="item in items" v-bind:class="{ active: item.active }">{{ item.text }}
</li>
</ul>
<button type="button" class="btn btn-danger" v-on:click="addItem">Test</button>
<secondComponent :items="items" />
</template>
<script>
import secondComponent from '/path/of/secondComponent'
export default {
components: [secondComponent]
data() {
return {
item: "a",
item2: "b",
item3: "c",
item4: "d",
items: [],
}
},
methods: {
addItem: function () {
var testArray = this.item.concat(this.item2, this.item3, this.item4);
for (var i = 0; i < testArray.length; i++) {
this.items.push({
text: testArray[i],
active: false });
}
// if I check console.log(this.items) at this point I can see the data I want to pass
},
}
}
</script>
In the second component you have already defined items as props, which you can as well use in template/HTML of second component.