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

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.

Related

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

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
}
}
}
},
}

How to give a dynamically rendered element it's own data value & target it in it's parent component?

I have a BaseMenuItem component that has normal button elements as well as special news elements.
I have added a ticker effect to the news type els and want to stop the ticker on that element when it's clicked. Currently the click event stops the ticker effect on the whole group.
How can I target a single element from that group?
There are two methods, openNews one showing the specific news article that the element is linked to.
And clearItemType that clears the itemType upon recieving the emitted event from the BaseMenuItem component.
I'm just not sure which element to target to change it's itemType.
Does Vuejs have a way to make an unique data value for dynamically generated elements?
If you need anymore information please let me know!
Cheers!
BaseMenuItem
<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 v-if="menuIcon" :name="menuIcon" class="text-black mr-md" />
<div #click="$emit('stop-ticker')" v-if="itemType === 'news'" class="ellipsis _ticker">
<div class="ellipsis _ticker-item">{{ title }}</div>
</div>
<div v-else>
<div class="ellipsis">{{ title }}</div>
</div>
<slot>
<div class="ml-auto"></div>
<div class="_subtitle mr-md" v-if="subtitle">{{ subtitle }}</div>
<q-icon name="keyboard_arrow_right" class="_right-side" />
<ComingSoon v-if="comingSoonShow" />
</slot>
</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
._subtitle
margin-left: auto
opacity: 0.7
._ticker
position: absolute
font-weight: bold
margin-left: 2em
width: 82%
&-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>
MainMenuPage
<template>
<div class="eachMenuGroup" v-if="newsList.length">
<MainMenuItem
v-for="news in newsList"
:key="news.id"
#click="openNews(news)"
:title="news.title"
:itemType="itemType"
:class="{ readLink: readNewsList[news.id] }"
menuIcon="contactless"
#stop-ticker="clearItemType"
></MainMenuItem>
</div>
</template>
<style lang="sass" scoped>
.readLink
font-weight: 500
</style>
<script>
methods: {
openNews(postInfo) {
dbAuthUser().merge({ seenNewsPosts: { [postInfo.id]: true } })
Browser.open({ url: postInfo.url, presentationStyle: 'popover' })
},
clearItemType() {
this.itemType = ''
return
},
</script>
EDIT: I edited since your latest comment. See below
To target the exact element within your v-for that fired the event, you can use $refs by using an index :
<MainMenuItem v-for="(item, index) in items" :ref="`menuItem--${index}`" #stop-ticker="clearItemType(index)" />
In your method:
clearItemType(index){
console.log(this.$refs[`menuItem--${index}`])
// this is the DOM el that fired the event
}
Edited version after your comments:
If you pass the exact same prop to each el of your v-for you wont be able to modify its value just for one child in the parent context.
Instead, keep an array of distinctive values in your parent. Each child will receive one specific prop that you can change accordingly in the parent either by listening to an child emitted event or by passing index as click event as I've done below for simplicity.
See snippet below
Vue.config.productionTip = false;
const Item = {
name: 'Item',
props: ['name', 'isActive'],
template: `<div>I am clicked {{ isActive }}!</div>`,
};
const App = new Vue({
el: '#root',
components: {
Item
},
data: {
items: ["item1", "item2"],
activeItemTypes: []
},
methods: {
toggle(index) {
this.activeItemTypes = this.activeItemTypes.includes(index) ? this.activeItemTypes.filter(i => i !== index) : [...this.activeItemTypes, index]
}
},
template: `
<div>
<Item v-for="(item, i) in items" :name="item" #click.native="toggle(i)" :isActive="activeItemTypes.includes(i)"/>
</div>
`,
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="root"></div>

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",
}
}
}
}

Reload a Component

I have 3 components. They are dashboard.vue, datatable.vue and modalbody.vue. After login my application reach in dashboard.vue. Where I call datatable.vue to display a list with some props. I have a button in datatable.vue. If I click on that button a modal will open to add new record to add that list (datatable.vue) using modalbody.vue.
Now I need to reload that list (datatable.vue) after inserting new record through modal (modalbody.vue).
How can I do that ?
I am going to show you a simple example how to do it.Hope you will get the logic
Component which has the table:
<template>
<div>
<cmp :modal.sync="modal" #personAdded="addItemInTable"></cmp>
<button #click="addNewPerson">add person</button>
<table>
<tr v-for="row in tableRows">
<td>{{ row.name }}</td>
<td>{{ row.lastName }}</td>
</tr>
</table>
</div>
</template>
<script>
import childComponent from 'ChildComponent.vue'
export default {
components: {
"cmp": childComponent
},
data() {
return {
modal: false,
tableRows: [
{ name: "person1", lastName: "lperson1" },
{ name: "person2", lastName: "lperson2" },
{ name: "person3", lastName: "lperson3" },
{ name: "person4", lastName: "lperson4" },
]
}
},
methods: {
addNewPerson() {
this.modal = true //open the modal
},
addItemInTable(data) {
//saving the data passed from modal
this.tableRows.unshift(data)
this.modal = false
}
}
}
</script>
Modal Component:
<template>
<div id="modal" v-if="modal">
<input type="text" v-model="name">
<input type="text" v-model="">
<button #click="save">Save</button>
<button #click="cancel">Cancel</button>
</div>
</template>
<script>
export default {
props: {
modal: {
default: false
}
},
data() {
return {
name: '',
lastName: ''
}
},
methods: {
save() {
const savedData = {
name: this.name,
lastName: this.lastName
}
this.$emit('personAdded', savedData)
},
cancel() {
this.$emit('update:modal', false)
}
}
}
</script>
<style>
.modal {
position: absolute;
height: 500px;
width: 500px;
display: flex;
justify-content: center;
align-items: center;
z-index: 999;
}
</style>
I dont have run the above code but the logic is correct.Please read below to understand:
For .sync modifier read this
For emitting events ($emit) read this
For reusing components read this

VueJS: #click.native.stop = "" possible?

I have several nested components on the page with parents component having #click.native implementation. Therefore when I click on the area occupied by a child component (living inside parent), both click actions executed (parent and all nested children) for example
<products>
<product-details>
<slide-show>
<media-manager>
<modal-dialog>
<product-details>
<slide-show>
<media-manager>
<modal-dialog>
</products>
So I have a list of multiple products, and when I click on "canvas" belonging to modal dialog - I also get #click.native fired on product-details to which modal-dialog belongs. Would be nice to have something like #click.native.stop="code", is this possible?
Right now I have to do this:
#click.native="clickHandler"
and then
methods: {
clickHandler(e) {
e.stopPropagation();
console.log(e);
}
code
<template>
<div class="media-manager">
<div v-if="!getMedia">
<h1>When you're ready please upload a new image</h1>
<a href="#"
class="btn btn--diagonal btn--orange"
#click="upload=true">Upload Here</a>
</div>
<img :src="getMedia.media_url"
#click="upload=true"
v-if="getMedia">
<br>
<a class="arrow-btn"
#click="upload=true"
v-if="getMedia">Add more images</a>
<!-- use the modal component, pass in the prop -->
<ModalDialog
v-if="upload"
#click.native="clickHandler"
#close="upload=false">
<h3 slot="header">Upload Images</h3>
<p slot="body">Hello World</p>
</ModalDialog>
</div>
</template>
<script>
import ModalDialog from '#/components/common/ModalDialog';
export default {
components: {
ModalDialog,
},
props: {
files: {
default: () => [],
type: Array,
},
},
data() {
return {
upload: false,
}
},
computed: {
/**
* Obtain single image from the media array
*/
getMedia() {
const [
media,
] = this.files;
return media;
},
},
methods: {
clickHandler(e) {
e.stopPropagation();
console.log(e);
}
}
};
</script>
<style lang="scss" scoped>
.media-manager img {
max-width: 100%;
height: auto;
}
a {
cursor: pointer;
}
</style>
Did you check the manual? https://v2.vuejs.org/v2/guide/events.html
There is #click.stop="" or #click.stop.prevent=""
So you don't need to use this
methods: {
clickHandler(e) {
e.stopPropagation();
console.log(e);
}
}
In the Vue, modifiers can be chained. So, you are free to use modifiers like this:
#click.native.prevent or #click.stop.prevent
<my-component #click.native.prevent="doSomething"></my-component>
Check events
I had the same problem. I fixed the issue by using following:
<MyComponent #click.native.prevent="myFunction(params)" />