Vue.js, toggle class on click doesn't work as expected - javascript

I'm learning Due and I'm trying something that should be easy but doesn't work and I'm sure there is something I don't understand.
I have to add a case to a modal...
I simplify the code: I have an array of products from my parent passed the a props and to chunk them in columns I use a computed variabile.
In the computed variable I also add to my object array an attribute active for every object, and I need to use that attribute to add the class.
I cannot change the value: when I click the button the product.active value is changed if I look the console but in my template no, it is false. Why
<template>
<div class="columns" v-for="products in processedProducts">
<div class="column" v-for="product in products">
<pre>{{product.active}}</pre>
<a v-on:click="activeteModal(product)">Pricy History</a>
<price-history :asin="product.asin" :active="product.active"></price-history>
</div>
</div>
</template>
<script>
import PriceHistory from '../components/PriceHistory'
export default {
props: ['results','search','maxprice','discount'],
name: 'product',
components: {
PriceHistory
},
methods: {
activeteModal: function(product){
console.log(product.active);
product.active = !product.active;
console.log(product.active);
}
},
computed: {
processedProducts() {
let products = this.results.map((obj) => {
obj.active = false;
return obj;
})
// Put Array into Chunks
let i, j, chunkedArray = [], chunk = 5;
for (i=0, j=0; i < products.length; i += chunk, j++) {
chunkedArray[j] = products.slice(i,i+chunk);
}
return chunkedArray;
}
}
}
</script>

Computed objects update lazy, set the array as a data property then it will update reactive.
Furthermore computed-objects are by default getter-only.
You better trigger a method that fills the product array when the component is mounted like this:
export default {
props: ['results','search','maxprice','discount'],
name: 'product',
components: {
PriceHistory
},
data: function () {
return {
products :[]
}
},
mounted: function(){
this.processedProducts();
},
methods: {
activeteModal: function(product){
console.log(product.active);
product.active = !product.active;
console.log(product.active);
},
processedProducts() {
let products = this.results.map((obj) => {
obj.active = false;
return obj;
})
// Put Array into Chunks
let i, j, chunk = 5;
for (i=0, j=0; i < products.length; i += chunk, j++) {
this.products[j] = products.slice(i,i+chunk);
}
}
}
}

Related

Vue js: How to filter an array where every tag in a nested array is included in an array of user entered tags?

I have a search function that filters an array if any one of the tags in the search tags array are included in the item's tags array. Instead of using some() to return true if any tag is present, how can I return true if every tag is present?
Here's an example of what I have (which works). If I search "tag1;" and "tag3;", it returns all 3 items. What I want is only item1 and item2 to return true.
<template>
<div class="right-content">
<div class="nav-margin">
<p>Filter array</p>
<input type="text" v-model="searchTag" #keyup="addSearchTag" />
<div class="spacer-20"></div>
<div v-for="searchTag in searchTags" :key="searchTag" class="items">{{searchTag}}</div>
<div class="spacer-20"></div>
<div v-for="item in searchedItems" :key="item.name" class="items">
<div class="title">{{item.name}}:</div>
<div v-for="tag in item.tags" :key="tag" class="tag">{{tag}}</div>
</div>
</div>
</div>
</template>
<script>
import { ref } from "#vue/reactivity";
import { computed } from '#vue/runtime-core';
export default {
name: "TemplatesDash",
setup() {
const searchTag = ref("");
const searchTags = ref([])
const filteredItems = ref(null)
let items = [
{ name: "item1", tags: ["tag1;", "tag2;", "tag3;"] },
{ name: "item2", tags: ["tag1;", "tag3;"] },
{ name: "item3", tags: ["tag2;", "tag3;"] },
];
const addSearchTag = (e) => {
if (e.key === ";" && searchTag.value) {
if (!searchTags.value.includes(searchTag.value)) {
searchTags.value.push(searchTag.value);
}
searchTag.value = "";
}
};
const searchedItems = computed(() => {
filteredItems.value = items;
if (searchTags.value.length) {
filteredItems.value = filteredItems.value.filter((item) => {
return item.tags.some(
(r) => searchTags.value.indexOf(r) !== -1
);
});
}
return filteredItems.value
});
return { searchTag, searchedItems, searchTags, addSearchTag, };
},
};
</script>
I'm brand new to javascript so any help or pointing in the right direction would be great.
Thanks
I believe the .every() function may be useful here. When you filter the items in your searchedItems computed function, return an .every() call on searchTags, returning whether item.tags .includes() every searchTag array member.
const searchedItems = computed(() => {
filteredItems.value = items;
if (searchTags.value.length) {
filteredItems.value = filteredItems.value.filter((item) => {
return searchTags.value.every(t => item.tags.includes(t));
});
}
return filteredItems.value
});
You're off to a great start with javascript! Hope this helps!

How to get data in real time in vuejs

It is my first project in vue. I am getting the cart-data from the server. i want to change quantity from the vue. When i click change quantity i.e up arrow and down arrow, change is reflected in the server database. but in UI i have to reload the page to see change which i do not want that.
i want to see the change in without reloading when button is click. and i want to run TotalCartPrice method without clicking any button. I mean when i open cart it should be automatically run. what is wrong?
cart.vue
<template>
<CartItem v-for="cart in carts" :key="cart.id" :carts="cart" />
</template>
<script>
import CartItem from "../components/cart/cartItem";
export default {
name: "CartPage",
components: {
CartItem
},
computed: {
...mapGetters(["carts"])
},
created() {},
methods: {
TotalCartPrice() {
var total = 0;
for (var i = 0; i < this.carts.length; i++) {
total += this.carts[i].product.price * this.carts[i].quantity;
}
return total;
}
}
};
CartItem.vue
<template>
<div>
<h5>${{ carts.product.price }}</h5>
<div class="product_count">
<input
disabled
name="qty"
maxlength="12"
:value="carts.quantity"
class="input-text qty"
/>
<button
#click="addProduct()"
class="increase items-count"
type="button"
>
<i class="lnr lnr-chevron-up"></i>
</button>
<button
#click="removeProduct()"
class="reduced items-count"
type="button"
>
<i class="lnr lnr-chevron-down"></i>
</button>
</div>
</div>
</template>
<script>
export default {
name: "cartItem",
props: {
carts: {
required: true,
type: Object
}
},
data() {
return {
cartDetail: {
product: this.carts.product.id,
quantity: null,
customer: null,
checkout: false
}
};
},
computed: {
...mapGetters(["authUser"])
},
methods: {
...mapActions(["addTocart"]),
addProduct() {
this.cartDetail.quantity = 1;
this.cartDetail.customer = this.authUser.id;
this.addTocart(this.cartDetail);
},
removeProduct() {
this.cartDetail.quantity = -1;
this.cartDetail.customer = this.authUser.id;
this.addTocart(this.cartDetail)
}
}
};
</script>
Cart.js
const state = {
carts: []
};
const getters = {
carts: state => state.carts
};
const actions = {
async addTocart({ commit }, data) {
const JsonData = JSON.parse(JSON.stringify(data));
const response = await axios.post("/api/v1/cart/view-set/", JsonData);
return response;
},
async cart({ commit }, data) {
if (data !== "null") {
const response = await axios.get(`/api/v1/cart/cartdetial/${data}`);
commit("setCarts", response.data);
return response;
}
}
};
const mutations = {
setCarts: (state, carts) => {
state.carts = carts;
}
};
export default {
state,
getters,
actions,
mutations
};
TotalCartPrice should be a computed property:
TotalCartPrice: function() {
var total = 0;
for (var i = 0; i < this.carts.length; i++) {
total += this.carts[i].product.price * this.carts[i].quantity;
}
return total;
}
From the docs on the difference between computed and methods:
...computed properties are cached based on their reactive dependencies. A computed property will only re-evaluate when some of its reactive dependencies have changed. This means as long as message has not changed, multiple access to the reversedMessage computed property will immediately return the previously computed result without having to run the function again.
This way, your cart price will update whenever this.carts is updated.

How do you shuffle a table of images every second on the click of a button?

I have a bootstrap table with images and names. My data is an array of objects containing image URLs and names. When I click the button I want the image objects to shuffle every 1 second. I am using the Fisher-Yates algorithm to shuffle inside of a setInterval() function.
I would also like to know how to create a stop button.
mock-data:
export const data = [{
url: 'https://via.placeholder.com/80/FF0000',
name: 'ben'
},
{
url: 'https://via.placeholder.com/80/000000',
name: 'jon'
},
{
url: 'https://via.placeholder.com/80/FFFFFF',
name: 'sam'
},
{
url: 'https://via.placeholder.com/80/0000FF',
name: 'bill'
},
{
url: 'https://via.placeholder.com/80/008000',
name: 'tom'
}
];
Here is my Component:
import React, { Component } from 'react';
import { Table, Button } from 'reactstrap';
import './App.css';
import { data } from './mock-data';
import { shuffle } from './helpers/shuffle';
class App extends Component {
constructor(props){
super(props)
this.state = {
images: data
}
}
handleStartShuffle = () => {
this.setState({images: setInterval(shuffle(this.state.images), 1000)});
}
render () {
const imageTable = this.state.images.map((item, index) => {
console.log('item.url', item.url)
return (
<tr key={index}>
<td>
<img src={item.url} alt='random' />
</td>
<td>{item.name}</td>
</tr>
)
})
return (
<div>
<Table>
<thead>
<tr>
<th>image</th>
<th>name</th>
</tr>
</thead>
<tbody>
{imageTable}
</tbody>
</Table>
<Button onClick={this.handleStartShuffle}>Start</Button>
</div>
);
}
}
export default App;
Here is my helper function for shuffling:
export function shuffle(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * i);
const temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
}
I would do it like this:
let interval;
handleStartShuffle = () => {
interval = setInterval(() => {
this.setState({images: shuffle(this.state.images)});
}, 1000);
}
stopShuffle = () => {
if(interval) {
clearInterval(interval);
}
}
#TShort already showed you exactly how to keep track of the interval and stop it properly, so I'm not going to copy/paste that here.
But, your shuffle function is currently modifying the array in-place. Since the array is in your component's state, this can lead to unpredictable results. You'll want to change your function to return a new array.
The simplest solution is to make a copy at the start of the function:
export function shuffle(oldArray) {
const array = [...oldArray];
setInterval returns a timer id, then you can call to clearInterval with that id. You example is a little blurry, specially when you store the interval id in state.images. See Using setInterval in React Component

VueJS Typescript WebPack, impossible to remove object from array with splice or delete

i try to remove a object from array in vueJS but this is impossible.
I tyr a lot of thing and read some solution on stackoverflow but nothing work for me.
I have a fake list like this in my vue.html component :
<div class="custo-list-c">
<div v-for="(item, index) in valuesFounded"
#click="addItem(item)"
v-bind:class="{ 'selected': itemSelected(item) }">
{{ item.value }}
<span v-if="itemSelected(item)">
<i class="fa fa-remove" #click="itemDeleted(item)"></i>
</span>
</div>
</div>
And my component look something like this :
import Vue from 'vue';
import Component from 'vue-class-component';
import { Prop, Watch, Emit } from "vue-property-decorator";
#Component({
name: 'custolist-component',
template: require('./custo-list.component.vue.html'),
components: {}
})
export default class CustoListComponent extends Vue {
public custoListActive: boolean = false;
public valuesFounded: Array<{key: string, value: string}> = [];
public selectedItems_: Array<{key: string, value: string}> = [];
#Prop() list;
#Watch('list') onListChanged(newList, oldList) {
// this.list = newList;
}
#Prop() placeholder;
#Watch('placeholder') onPlaceholderChanged(newPlaceholder, oldPlaceholder) {
// console.log(newPlaceholder);
}
#Prop() disabled;
#Watch('disabled') onDisabledChanged(newDisabled, oldDisabled) {
// console.log(newPlaceholder);
}
public open(event) {
this.custoListActive = true;
if (!event.target.value) {
this.valuesFounded = this.list;
} else {
this.valuesFounded = [];
const re = new RegExp(event.target.value, 'ig');
for (var i=0; i<this.list.length; i++) {
if (this.list[i].key.match(re) || this.list[i].value.match(re)) {
this.valuesFounded.push(this.list[i]);
}
}
}
}
public addItem(item: {key: string, value: string}) {
if (!this.isSelectedItem_(item)) {
this.selectedItems_.push(item);
// this.custoListActive = false;
};
this.$emit('itemclicked', item);
}
public itemSelected(item) {
return this.isSelectedItem_(item);
}
public itemDeleted(item) {
for (var i=0; i<this.selectedItems_.length; i++) {
if (item.key == this.selectedItems_[i].key) {
this.selectedItems_.splice(i, 1);
break;
}
}
this.$emit('itemdeleted', item);
}
private isSelectedItem_(item) {
const filtered = this.selectedItems_.filter(m => {
return m.key == item.key;
});
return filtered.length > 0;
}
}
but when i do this.selectedItems_.splice(i, 1); that does not work !!
Thank for your help
More precisions about my code. Here the method where i remove item from my array :
public itemDeleted(item) {
const filtered = this.selectedItems_.filter(m => {
return m.key != item.key;
});
console.log(filtered, this.selectedItems_.length);
this.selectedItems_ = filtered;
console.log(this.selectedItems_, this.selectedItems_.length);
this.$emit('itemdeleted', item);
}
And the result in the console
console
What's wrong?
Another test :
public itemDeleted(item) {
this.selectedItems_ = this.selectedItems_.filter(m => {
return m.key != item.key;
});
this.selectedItems_.splice(this.selectedItems_.length);
console.log(this.selectedItems_, this.selectedItems_.length);
this.selectedItems_ = [];
console.log(this.selectedItems_, this.selectedItems_.length);
this.$emit('itemdeleted', item);
}
result :
console
May be a bug VueJS
Sorry, it was my fault, replace
<i class="fa fa-remove" #click="itemDeleted(item)"></i>
by
<i class="fa fa-remove" v-on:click.stop="itemDeleted(item)"></i>
Having multiple steps of finding value with combination of for loop and if statement reduces readability and code predictability. In addition, invoking array mutating method splice may not trigger reactive update of this property.
I'd suggest to use filter and re-assign selectedItems_ inside itemDeleted method as follows:
public itemDeleted(item) {
this.selectedItems_ = this.selectedItems_.filter(selectedItem => selectedItem.key !== item.key)
this.$emit('itemdeleted', item);
}
This way, after method execution, selectedItems_ will consist of all previous items except the one provided as an argument to the method and all dependent properties will be re-computed.

VueJs watching deep changes in object

I have this 3 components in VueJS. The problem i want to solve is: When i click at vehicle component, it needs to be selected (selected = true) and other vehicles unselected.
What i need to do for two-way data binding? Because i'm changing this selected property in VehiclesList.vue component and it also need to be changed in Monit.vue (which is a parent) and 'Vehicle.vue' need to watch this property for change class.
Also problem is with updating vehicles. In Monit.vue i do not update full object like this.vehicles = response.vehicles, but i do each by each one, and changing only monit property.
Maybe easier would be use a store for this. But i want to do this in components.
EDITED:Data sctructure
{
"m":[
{
"id":"v19",
"regno":"ATECH DOBLO",
"dt":"2017-10-09 13:19:01",
"lon":17.96442604,
"lat":50.66988373,
"v":0,
"th":0,
"r":0,
"g":28,
"s":"3",
"pow":1
},
{
"id":"v20",
"regno":"ATECH DUCATO_2",
"dt":"2017-10-10 01:00:03",
"lon":17.96442604,
"lat":50.6698494,
"v":0,
"th":0,
"r":0,
"g":20,
"s":"3"
},
]
}
Monit.vue
<template>
<div class="module-container">
<div class="module-container-widgets">
<vehicles-list :vehicles="vehicles"></vehicles-list>
</div>
</div>
</template>
<script>
import VehiclesList from '#/components/modules/monit/VehiclesList.vue';
export default {
name: "Monit",
data (){
return {
vehicles: null
}
},
components: {
VehiclesList
},
methods: {
getMonitData(opt){
let self = this;
if (this.getMonitDataTimer) clearTimeout(this.getMonitDataTimer);
this.axios({
url:'/monit',
})
.then(res => {
let data = res.data;
console.log(data);
if (!data.err){
self.updateVehicles(data.m);
}
self.getMonitDataTimer = setTimeout(()=>{
self.getMonitData();
}, self.getMonitDataDelay);
})
.catch(error => {
})
},
updateVehicles(data){
let self = this;
if (!this.vehicles){
this.vehicles = {};
data.forEach((v,id) => {
self.vehicles[v.id] = {
monit: v,
no: Object.keys(self.vehicles).length + 1
}
});
} else {
data.forEach((v,id) => {
if (self.vehicles[v.id]) {
self.vehicles[v.id].monit = v;
} else {
self.vehicles[v.id] = {
monit: v,
no: Object.keys(self.vehicles).length + 1
}
}
});
}
},
},
mounted: function(){
this.getMonitData();
}
};
</script>
VehiclesList.vue
<template>
<div class="vehicles-list" :class="{'vehicles-list--short': isShort}">
<ul>
<vehicle
v-for="v in vehicles"
:key="v.id"
:data="v"
#click.native="select(v)"
></vehicle>
</ul>
</div>
</template>
<script>
import Vehicle from '#/components/modules/monit/VehiclesListItem.vue';
export default {
data: function(){
return {
isShort: true
}
},
props:{
vehicles: {}
},
methods:{
select(vehicle){
let id = vehicle.monit.id;
console.log("Select vehicle: " + id);
_.forEach((v, id) => {
v.selected = false;
});
this.vehicles[id].selected = true;
}
},
components:{
Vehicle
}
}
</script>
Vehicle.vue
<template>
<li class="vehicle" :id="data.id" :class="classes">
<div class="vehicle-info">
<div class="vehicle-info--regno font-weight-bold"><span class="vehicle-info--no">{{data.no}}.</span> {{ data.monit.regno }}</div>
</div>
<div class="vehicle-stats">
<div v-if="data.monit.v !== 'undefined'" class="vehicle-stat--speed" data-name="speed"><i class="mdi mdi-speedometer"></i>{{ data.monit.v }} km/h</div>
</div>
</li>
</template>
<script>
export default {
props:{
data: Object
},
computed:{
classes (){
return {
'vehicle--selected': this.data.selected
}
}
}
}
</script>
Two-way component data binding was deprecated in VueJS 2.0 for a more event-driven model: https://v2.vuejs.org/v2/guide/components.html#One-Way-Data-Flow
This means, that changes made in the parent are still propagated to the child component (one-way). Changes you make inside the child component need to be explicitly send back to the parent via custom events: https://v2.vuejs.org/v2/guide/components.html#Custom-Events or in 2.3.0+ the sync keyword: https://v2.vuejs.org/v2/guide/components.html#sync-Modifier
EDIT Alternative (maybe better) approach:
Monit.vue:
<template>
<div class="module-container">
<div class="module-container-widgets">
<vehicles-list :vehicles="vehicles" v-on:vehicleSelected="onVehicleSelected"></vehicles-list>
</div>
</div>
</template>
<script>
import VehiclesList from '#/components/modules/monit/VehiclesList.vue';
export default {
name: "Monit",
data (){
return {
vehicles: null
}
},
components: {
VehiclesList
},
methods: {
onVehicleSelected: function (id) {
_.forEach((v, id) => {
v.selected = false;
});
this.vehicles[id].selected = true;
}
...other methods
},
mounted: function(){
this.getMonitData();
}
};
</script>
VehicleList.vue:
methods:{
select(vehicle){
this.$emit('vehicleSelected', vehicle.monit.id)
}
},
Original post:
For your example this would probably mean that you need to emit changes inside the select method and you need to use some sort of mutable object inside the VehicleList.vue:
export default {
data: function(){
return {
isShort: true,
mutableVehicles: {}
}
},
props:{
vehicles: {}
},
methods:{
select(vehicle){
let id = vehicle.monit.id;
console.log("Select vehicle: " + id);
_.forEach((v, id) => {
v.selected = false;
});
this.mutableVehicles[id].selected = true;
this.$emit('update:vehicles', this.mutableVehicles);
},
vehilcesLoaded () {
// Call this function from the parent once the data was loaded from the api.
// This ensures that we don't overwrite the child data with data from the parent when something changes.
// But still have the up-to-date data from the api
this.mutableVehilces = this.vehicles
}
},
components:{
Vehicle
}
}
Monit.vue
<template>
<div class="module-container">
<div class="module-container-widgets">
<vehicles-list :vehicles.sync="vehicles"></vehicles-list>
</div>
</div>
</template>
<script>
You still should maybe think more about responsibilities. Shouldn't the VehicleList.vue component be responsible for loading and managing the vehicles? This probably would make thinks a bit easier.
EDIT 2:
Try to $set the inner object and see if this helps:
self.$set(self.vehicles, v.id, {
monit: v,
no: Object.keys(self.vehicles).length + 1,
selected: false
});

Categories