How can I change the backgroundColor of a row in a beufy datatable with a given color for each row? - vue.js

I'm new to web technologies and I'm stuck this this problem: I would like to be able to change the color of a row dynamically.
I tried different things with my knowledge and none worked well. I have a json with information for the table with the color code inside (ex: #D465F2). I wasn't able to find a way to color the entire row in one time dynamically. I can do it with predetermined css but it's not what I need.
The only way it worked is this one:
Template:
<template>
<div>
<p class="title">logs</p>
<b-table
:data="loginfo"
:sticky-header="stickyHeaders"
height="1200px"
:row-class="() => 'relative'"
>
<b-table-column v-slot="props" field="log_id" label="color">
<div class="colored-cell" :style="{ backgroundColor: props.row.color }">
<p>{{ props.row.color }}</p>
</div>
</b-table-column>
</b-table>
</div>
</template>
Script:
<script>
import axios from 'axios'
export default {
props: {
id: Number,
state: Number,
},
data() {
return {
loginfo: [],
stickyHeaders: true,
dateSearchable: false,
}
},
watch: {
id(newId) {
this.newLog()
},
},
methods: {
async newLog() {
await axios
.get('http://127.0.0.2:5001/api/log?sequence=' + this.id)
.then((response) => (this.loginfo = response.data.data))
},
},
}
Exemple of json send to the script:
"data": [
{
"color": "#15986E",
"log_id": 25,
"logged_at": "2022-05-17 16:27:00.015",
},
and this is my css:
.relative {
position: relative;
}
.colored-cell {
position: absolute;
top: 0;
left: 0;
padding: 8px 12px;
}
With this code I color each cell one by one but it makes the table unreadable and all broken. Everything is on top of each other. TableBroken Screen
If anyone have the solution to make the table looks great again/color row dynamically or anything that can help me, another lib which allows me to change the row color dynamically.

Just use a td-attrs prop as documented
<b-table>
:data="loginfo"
:sticky-header="stickyHeaders"
height="1200px"
>
<b-table-column v-slot="props" field="log_id" label="color" :td-attrs="columnTdAttrs">
{{ props.row.color }}
</b-table-column>
</b-table>
</div>
export default {
methods: {
columnTdAttrs(row, column) {
return {
style: {
backgroundColor: row.color
}
}
}
},
}

Related

Stopping an animation with an click event in Vuejs?

I have a MainMenuItem that has a bunch of static titles. There is one variable item that is for news and events and that has a horizontal ticker, scroll animation on it. This animation is happening inside of the MainMenuItem component, but the click event will be happening from the parent. The click event currently needs to stop the animation and change the font weight (can be seen in the readLink class).
Wth Vue I cannot mutate the prop type itself with something like #click="itemType = ''".
So how can I turn this ticker animation off on click?
Basically, all I would need to do is make the MainMenuItem's itemType = '' and make it the same as every other item. Do I need to make a data type? Or a click event that $emit's an event to the child/base component MainMenuItem?
If there is anymore code that is necessary please let me know!
Cheers!
MainMenuItem.vue (base component with the ticker animation )
<template>
<q-btn align="left" dense flat class="main-menu-item" v-on="$listeners">
<div class="flex no-wrap items-center full-width">
<iconz v-if="iconz" :name="iconz" type="pop" color="black" class="mr-md" />
<q-icon :name="menuIcon" />
<div v-if="itemType === 'news'" class="_ticker">
<div class="ellipsis _ticker-item">{{ title }}</div>
</div>
<div v-else>
<div class="ellipsis">{{ title }}</div>
</div>
<q-icon name="keyboard_arrow_right" class="_right-side" />
</div>
</q-btn>
</template>
<style lang="sass" scoped>
// $
.main-menu-item
display: block
font-size: 15px
position: relative
width: 100%
border-bottom: 1px solid #F5F5F5
+py(10px)
._left-side
color: #000000
._ticker
font-weight: bold
margin-left: 2em
width: 83%
white-space: nowrap
overflow: hidden
position: absolute
&-item
display: inline-block
padding-left: 100%
animation: ticker 8s linear infinite
#keyframes ticker
to
transform: translateX(-100%)
</style>
<script>
import { iconz } from 'vue-iconz'
export default {
name: 'MainMenuItem',
components: { iconz },
props: {
comingSoonShow: { type: Boolean, default: false },
title: { type: String, default: 'menu' },
subtitle: { type: String, default: '' },
menuIcon: { type: String, default: '' },
iconz: { type: String, default: '' },
itemType: { type: String, default: '' },
}
}
</script>
MainMenu.vue (just the location I am using the base component)
<template>
<MainMenuItem
v-for="news in newsList"
:key="news.id"
#click="openNews(news)"
:title="news.title"
itemType="news"
:class="{ readLink: readNewsList[news.id] }"
menuIcon="contactless"
></MainMenuItem>
</template>
export default {
name: 'MainMenu',
methods: {
openNews(postInfo) {
dbAuthUser().merge({ seenNewsPosts: { [postInfo.id]: true } })
Browser.open({ url: postInfo.url, presentationStyle: 'popover' })
}
},
computed: {
readNewsList() {
return this.authUserInfo?.seenNewsPosts || {}
},
}
<style lang="sass" scoped>
.readLink
font-weight: 500
</style>
Since it's a prop the simplest way is to emit an event that executes what you need in the parent and also bind a variable not static prop to be able to change it. example in the child :
<button #click="onClickButton">stop animation </button>
export default {
methods: {
onClickButton (event) {
this.$emit('stopAnimation')
}
}
}
And in the parent you listen to it as so:
<MainMenuItem
v-for="news in newsList"
:key="news.id"
#click="openNews(news)"
:title="news.title"
:itemType="itemType"
:class="{ readLink: readNewsList[news.id] }"
menuIcon="contactless"
v-on:stopAnimation="itemType = ''"
></MainMenuItem>
export default {
data() {
return {
itemType: "news",
}
}
}
}

Google Maps showing grey box in Vue modal

I have a <b-modal> from VueBootstrap, inside of which I'm trying to render a <GmapMap> (https://www.npmjs.com/package/gmap-vue)
It's rendering a grey box inside the modal, but outside the modal it renders the map just fine.
All the searching I've done leads to the same solution which I'm finding in some places is google.maps.event.trigger(map, 'resize') which is not working. Apparently, it's no longer part of the API [Source: https://stackoverflow.com/questions/13059034/how-to-use-google-maps-event-triggermap-resize]
<template>
<div class="text-center">
<h1>{{ title }}</h1>
<div class="row d-flex justify-content-center">
<div class="col-md-8">
<GmapMap
ref="topMapRef"
class="gmap"
:center="{ lat: 42, lng: 42 }"
:zoom="7"
map-type-id="terrain"
/>
<b-table
bordered
dark
fixed
hover
show-empty
striped
:busy.sync="isBusy"
:items="items"
:fields="fields"
>
<template v-slot:cell(actions)="row">
<b-button
size="sm"
#click="info(row.item, row.index, $event.target)"
>
Map
</b-button>
</template>
</b-table>
<b-modal
:id="mapModal.id"
:title="mapModal.title"
#hide="resetInfoModal"
ok-only
>
<GmapMap
ref="modalMapRef"
class="gmap"
:center="{ lat: 42, lng: 42 }"
:zoom="7"
map-type-id="terrain"
/>
</b-modal>
</div>
</div>
</div>
</template>
<script>
// import axios from "axios";
import { gmapApi } from 'gmap-vue';
export default {
name: "RenderList",
props: {
title: String,
},
computed: {
google() {
return gmapApi();
},
},
updated() {
console.log(this.$refs.modalMapRef);
console.log(window.google.maps);
this.$refs.modalMapRef.$mapPromise.then((map) => {
map.setCenter(new window.google.maps.LatLng(54, -2));
map.setZoom(2);
window.google.maps.event.trigger(map, 'resize');
})
},
data: function () {
return {
items: [
{ id: 1, lat: 42, long: 42 },
{ id: 2, lat: 42, long: 42 },
{ id: 3, lat: 42, long: 42 },
],
isBusy: false,
fields: [
{
key: "id",
sortable: true,
class: "text-left",
},
{
key: "text",
sortable: true,
class: "text-left",
},
"lat",
"long",
{
key: "actions",
label: "Actions"
}
],
mapModal: {
id: "map-modal",
title: "",
item: ""
}
}
},
methods: {
// dataProvider() {
// this.isBusy = true;
// let promise = axios.get(process.env.VUE_APP_LIST_DATA_SERVICE);
// return promise.then((response) => {
// this.isBusy = false
// return response.data;
// }).catch(error => {
// this.isBusy = false;
// console.log(error);
// return [];
// })
// },
info(item, index, button) {
this.mapModal.title = `Label: ${item.id}`;
this.mapModal.item = item;
this.$root.$emit("bv::show::modal", this.mapModal.id, button);
},
resetInfoModal() {
this.mapModal.title = "";
this.mapModal.content = "";
},
},
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1 {
margin-bottom: 60px;
}
.gmap {
width: 100%;
height: 300px;
margin-bottom: 60px;
}
</style>
Does anyone know how to get the map to display properly in the modal?
Surely, I'm not the first to try this?
Had this problem, in my case it was solved by providing the following options to google maps:
mapOptions: {
center: { lat: 10.365365, lng: -66.96667 },
clickableIcons: false,
streetViewControl: false,
panControlOptions: false,
gestureHandling: 'cooperative',
mapTypeControl: false,
zoomControlOptions: {
style: 'SMALL'
},
zoom: 14
}
However you can probably make-do with just center and zoom.
Edit: Try using your own google maps components, follow this tutorial:
https://v2.vuejs.org/v2/cookbook/practical-use-of-scoped-slots.html#Base-Example
You can use the package described in the tutorial to load the map, dont be scared by the big red "deprecated" warning on the npm package page.
However for production, you should use the package referenced by the author, which is the one backed by google:
https://googlemaps.github.io/js-api-loader/index.html
The only big difference between the two:
The 'google' object is not returned by the non-deprecated loader, it is instead attached to the window. See my answer here for clarification:
'google' is not defined Using Google Maps JavaScript API Loader
Happy coding!

Cannot get computed property (array)

Trying to get a 'displayImages' array as a computed property. Using a default 'selected' property = 0.
this.selected changes accordingly on mouseover and click events.
When trying to get the computed 'displayImages' it says:
"this.variations[this.selected] is undefined."
I'm using an api to get my product data and images.
<template>
<div id="product-page">
<v-card width="100%" class="product-card">
<div class="image-carousel">
<v-carousel height="100%" continuos hide-delimiters>
<v-carousel-item
v-for="(image, i) in displayImages"
:key="i"
:src="image"
>
</v-carousel-item>
</v-carousel>
</div>
<div class="details">
<h2>{{ this.title }}<br />Price: ${{ this.price }}</h2>
<p>{{ this.details }}</p>
<ul style="list-style: none; padding: 0">
<li
style="border: 1px solid red; width: auto"
v-for="(color, index) in variations"
:key="index"
#mouseover="updateProduct(index)"
#click="updateProduct(index)"
>
{{ color.color }}
</li>
</ul>
<div class="buttons">
<v-btn outlined rounded
>ADD TO CART<v-icon right>mdi-cart-plus</v-icon></v-btn
>
<router-link to="/shop">
<v-btn text outlined rounded> BACK TO SHOP</v-btn>
</router-link>
</div>
</div>
</v-card>
</div>
</template>
<script>
export default {
name: "Product",
props: ["APIurl"],
data: () => ({
title: "",
details: "",
price: "",
variations: [],
selected: 0,
}),
created() {
fetch(this.APIurl + "/products/" + this.$route.params.id)
.then((response) => response.json())
.then((data) => {
//console.log(data);
this.title = data.title;
this.details = data.details.toLowerCase();
this.price = data.price;
data.variations.forEach((element) => {
let imagesArray = element.photos.map(
(image) => this.APIurl + image.url
);
this.variations.push({
color: element.title,
images: imagesArray,
qty: element.qty,
productId: element.productId,
});
});
});
},
computed: {
displayImages() {
return this.variations[this.selected].images;
},
},
methods: {
updateProduct: function (index) {
this.selected = index;
console.log(index);
}
},
};
</script>
To properly expand on my comment, the reason why you are running into an error is because when the computed is being accessed in the template, this.variations is an empty array. It is only being populated asynchronously, so chances are, it is empty when VueJS attempts to use it when rendering the virtual DOM.
For that reason, accessing an item within it by index (given as this.selected) will return undefined. Therefore, attempting to access a property called images in the undefined object will return an error.
To fix this problem, all you need is to introduce a guard clause in your computed as such:
computed: {
displayImages() {
const variation = this.variations[this.selected];
// GUARD: If variation is falsy, return empty array
if (!variation) {
return [];
}
return variation.images;
},
}
Bonus tip: if you one day would consider using TypeScript, you can even simplify it as such... but that's a discussion for another day ;) for now, optional chaining and the nullish coalescing operator is only supported by bleeding edge versions of evergreen browsers.
computed: {
displayImages() {
return this.variations[this.selected]?.images ?? [];
},
}
For avoid this kind of error, you must to use the safe navigation property.
Remember, it's useful just when the app is loading.
Try something like that:
<script>
export default {
name: 'Product',
computed: {
displayImages() {
if (this.variations[this.selected]) {
return this.variations[this.selected].images;
}
return [];
},
},
};
</script>

In vue.js how can I filter products by size, color, category & by selecting each of these categories in step wise slider

In vue.js how can I filter products by size, color, category & by selecting each of these categories in step-wise slider where I have to select each filter one by one step-wise & at last I need to display the products which pass through the filter.
Please share code examples and ask specific questions about where you are stuck. This question is very broad and is difficult to answer.
I'll try to provide an answer as best as I can.
You might have a ProductList component that may look something like this:
<template>
<!-- your template -->
</template>
<script>
import RangeSlider from "./RangeSlider";
export default {
name: "ProductList",
components: {
RangeSlider
},
data() {
return {
products: [],
filters: {
color: null,
size: null,
category: null
},
options: {
color: ["Red", "Yellow", "Green"],
size: ["Small", "Medium", "Large"],
category: ["Men", "Women", "Boy", "Girl"]
}
};
}
};
</script>
The RangeSlider can use the native <input type="range" /> control to build the functionality you want.
In this example, it takes a label and an array of options to build the control, and will emit a change event when the user changes the value of the control by sliding, which is captured in ProductList to set the filters. You can then use the filters object and make an API call to fetch your products.
<template>
<div class="range-slider">
<label>
<strong>{{ label }}</strong>
</label>
<div>
<input type="range" min="0" :max="max" step="1" #change="onInputChange" :value="rangeValue">
</div>
<div>{{ options[rangeValue] }}</div>
</div>
</template>
<script>
export default {
name: "RangeSlider",
props: ["label", "options"],
data() {
return {
rangeValue: 0
};
},
mounted() {
this.onChange(0);
},
methods: {
onInputChange(e) {
this.onChange(e.target.value);
},
onChange(rangeValue) {
this.rangeValue = rangeValue;
let selectedOption = this.options[this.rangeValue];
this.$emit("change", selectedOption);
}
},
computed: {
max() {
return this.options.length - 1;
}
}
};
</script>
<style>
.range-slider {
padding: 1rem;
border-bottom: 1px solid;
}
</style>
Here is the working example on CodeSandbox: https://codesandbox.io/s/rangesliderwithoptionsexample-6pe10

how to run function when everytime props change in ant-design-vue modal

i have a vue app containing image uploader with crops function. it's succeed to upload and crop the picture and get back the cropped image URL. but the problem is when the user need to re upload their image, the canvas still display the old image. it is also happen when user cancel and reupload the image.
<template>
<div>
<a-row :gutter="16">
<a-col :span="12">
<img ref="image" :src="initialImage">
</a-col>
<a-col :span="12" align="center">
<p><strong>Preview</strong></p>
<img :src="updatedImage" class="preview-image">
</a-col>
</a-row><br />
<a-row :gutter="16">
<a-button type="primary" style="float: right;" #click="crop">Crop</a-button>
<a-button style="float: right; margin-right: 5px;" #click="cancel">Cancel</a-button>
</a-row>
</div>
</template>
<script>
import Cropper from 'cropperjs';
export default {
name: 'PostCropper',
props: {
uploadedImage: String,
},
data() {
return {
cropper: {},
updatedImage: {},
image: {},
initialImage: this.uploadedImage,
};
},
methods: {
crop() {
this.$emit('update-image', this.updatedImage);
},
cancel() {
this.$emit('cancel-upload');
},
},
mounted() {
this.image = this.$refs.image;
this.cropper = new Cropper(this.image, {
zoomable: false,
scalable: false,
aspectRatio: 1,
crop: () => {
const canvas = this.cropper.getCroppedCanvas();
this.updatedImage = canvas.toDataURL('image/png');
},
});
},
};
</script>
<style scoped>
.preview-image {
border-radius: 100px;
width:150px;
height:150px;
}
</style>
i have tried set initialImage and updatedImage with empty string but it's still not work. what should i initialized back after cancel or continue crop the image? or there is another way i could overcome this problem? i've also using vue watcher but it's still not work. hmm.