Vue Overlay for specific card - vue.js

I am using Vue with Vuetify and I used a for loop to render 8 different cards. Each card has its own overlay which shows its own image when clicked, however when I click one card, all of the overlays get set off instead of the overlay for just the specific card. How should I go about doing this? Would I have to add an ID for each card?
<script>
export default {
data: () => ({
arts: [
require("#/assets/art1.jpg"),
require("#/assets/art2.jpg"),
require("#/assets/art3.jpg"),
require("#/assets/art4.jpeg"),
require("#/assets/art5.jpg"),
require("#/assets/art6.jpg"),
require("#/assets/art7.jpg"),
require("#/assets/art8.jpg")
],
absolute: true,
opacity: 1,
overlay: false,
})
};
</script>
<template>
<div style="width: 100%; height: 100%">
<v-container fluid style="height:100%; background-color:#fc8276; width:25%; float:left"></v-container>
<v-container style=" height: 100%; width: 75%; float:right" fluid>
<h1>Portfolio</h1>
<v-card
v-for="art in arts"
class="ma-5"
v-bind:key="art"
style="width: 15em; height: 15em; display:inline-flex"
>
<v-img :src="art" v-on:click="overlay=true"></v-img>
<v-overlay :absolute="absolute" :opacity="opacity" :value="overlay">
{{art}}
</v-overlay>
</v-card>
</v-container>
</div>
</template>

This is untested, but something like this should work... basically restructure your data a bit. Right now you have one overlay data boolean. Whenever you are setting this to true, you are setting it to true for everything that is using that same boolean... in your case all of your overlayables are looking at that same boolean.
Also I would recommend moving your styles into CSS classes, it will make this way more readable...
You need to update your HTML to this:
<template>
<div style="width: 100%; height: 100%">
<v-container fluid style="height:100%; background-color:#fc8276; width:25%; float:left"></v-container>
<v-container style=" height: 100%; width: 75%; float:right" fluid>
<h1>Portfolio</h1>
<v-card
v-for="(art, index) in arts"
class="ma-5"
v-bind:key="index"
style="width: 15em; height: 15em; display:inline-flex"
>
<v-img :src="art" v-on:click="showOverlay(art, index)"></v-img>
<v-overlay :absolute="absolute" :opacity="opacity" :value="art.overlay">
{{art.asset}}
</v-overlay>
</v-card>
</v-container>
</div>
</template>
Then change your data a bit:
data: () => ({
arts: [
{ asset: require("#/assets/art1.jpg"), overlay: false },
{ asset: require("#/assets/art2.jpg"), overlay: false },
{ asset: require("#/assets/art3.jpg"), overlay: false },
{ asset: require("#/assets/art4.jpeg"), overlay: false },
{ asset: require("#/assets/art5.jpg"), overlay: false },
{ asset: require("#/assets/art6.jpg"), overlay: false },
{ asset: require("#/assets/art7.jpg"), overlay: false },
{ asset: require("#/assets/art8.jpg"), overlay: false }
],
absolute: true,
opacity: 1,
})
Then in your methods:
methods: {
showOverlay(art, index) {
this.arts.map((a) => a.overlay = false);
this.arts[index].overlay = true;
}
}

I would make a new component which holds the v-card, so each one would have its own overlay data property. But having it all in one component with arts.length overlay variables works as well

Related

VueDraggable multiDrag and selected-class props not work in Nuxt.js

I currently use vue-draggable to make drag and drop component in my latest Nuxt project.
My package.json is like that (with the latest version of vuedraggable)
"nuxt": "^2.14.12",
"vuedraggable": "^2.24.3",
// I also use those plugins
"#nuxtjs/vuetify": "^1.11.3",
First of all, I tried to make mini-sample referred to this sample.
And I made code like below
I just changed :list to v-model in draggable tag, and use nuxt-property-decorator instead of export default. referred to latest vue-draggable
<template>
<div id="app">
<div class="flex">
<div class="flex-1">
Target Category Items: {{ targetCategoryItems.length }}
<div class="draggable-container">
<draggable
v-model="targetCategoryItems"
draggable=".element"
group="elements"
:multi-drag="true"
class="draggable"
selected-class="selected-item"
>
<div
v-for="element in targetCategoryItems"
:key="element.ItemID"
class="draggable-element"
>
<div class="item-text">
<div>[{{ element.ItemID }}]</div>
<div>{{ element.ItemName }}</div>
</div>
</div>
</draggable>
</div>
</div>
<div class="flex-1">
Source Category Items: {{ sourceCategoryItems.length }}
<div class="draggable-container">
<draggable
v-model="sourceCategoryItems"
draggable=".element"
group="elements"
:multi-drag="true"
class="draggable"
selected-class="selected-item"
#select="selectItems"
>
<div
v-for="element in sourceCategoryItems"
:key="element.ItemID"
class="draggable-element"
>
<div class="item-text">
<div>[{{ element.ItemID }}]</div>
<div>{{ element.ItemName }}</div>
</div>
</div>
</draggable>
</div>
</div>
</div>
</div>
</template>
<script>
import draggable from "vuedraggable";
export default {
components: { draggable },
data() {
return {
sourceCategoryItems: [
{ ItemID: "566GR", ItemName: "Leaf Urn", PhotoName: "" },
{ ItemID: "575GD", ItemName: "Italian Villa Planter", PhotoName: "" },
{ ItemID: "576GR", ItemName: "Palm Topiary Planter", PhotoName: "" },
],
targetCategoryItems: [
{ ItemID: "F238", ItemName: "Cuadrado Side Table", PhotoName: "" },
{ ItemID: "F239", ItemName: "Triangulo Side Table", PhotoName: "" },
{ ItemID: "F285", ItemName: "Kew Occassional Table", PhotoName: "" },
{ ItemID: "F286", ItemName: "Tuileries Coffee Table", PhotoName: "" },
{ ItemID: "F296", ItemName: "Heligan Table", PhotoName: "" },
],
};
},
methods: {
toggle(todo) {
todo.done = !todo.done;
},
selectItems(event) {
console.log(event.items);
},
},
};
</script>
<style scoped>
.flex {
display: flex;
}
.flex-1 {
flex: 1;
}
.draggable-container {
border: 1px solid black;
padding: 10px;
margin: 10px;
}
.draggable-element {
padding: 10px;
margin: 10px;
background-color: lightgrey;
}
.selected-item {
background-color: red;
opacity: 0.5;
}
</style>
The result is like that
It seems that there is no problem, but I clicked the left side table, background-color never changed and I couldn't drag and drop any items.
I also checked the same issues, "SortableJS / Vue.Draggable multi-drag option not working" and "Use with Nuxt? #525". So I tried to make drag.js in src/plugins, but it doesn't work either.
So, how can I correctly use muli-drag and selected-class props in Nuxt?
This is issues Github repository.

Dynamically insert images into 'v-data-table'

<v-data-table
:headers="headers"
:items="items"
:search="search"
hide-default-footer
class="elevation-1"
>
<template #[`item.employee_avatar`]="{ item }">
<v-img
:src="require('#/assets/img/img2.jpg')"
:alt="item.name"
style="width: 100px; height: 100px"
/>
</template>
</v-data-table>
The image with a fixed path is received, but I want to express the image with the image path of each object. I want to automatically follow the imgsrc path value of items.
export default {
data() {
return {
search: '',
loading: true,
headers: [
{
text: 'Avatar',
value: 'employee_avatar',
divider: true,
align: 'center',
sortable: false,
width: '100px',
},
{
text: 'myname',
align: 'center',
sortable: false,
value: 'name',
},
],
items: [
{
name: 'myname',
imgsrc: "#/assets/avatar.png",
},
The code below is the code I tried.
<template #[`item.employee_avatar`]="{ item }">
<v-img
:src="require(item.imgsrc)"
:alt="item.name"
style="width: 100px; height: 100px"
/>
</template>
There will be an error even if I fill out the above.
What's wrong with it? Help me...
You can use v-slot instead of #[item.employee_avatar]:
<template v-slot:item.employee_avatar="{ item }">
<v-img
:src="require(item.imgsrc)"
:alt="item.name"
style="width: 100px; height: 100px"
/>
</template>

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>

Vue.js: How to update element's position

I want to move an element while scrolling so the element is always in the screen.
But the position isn't updated.
What I am doing is this.
<v-card class="item" :style="{ top: distance }">
</v-card>
...
data() {
return {
distance: 0,
}
}
methods: {
handleScroll() {
this.distance = window.scrollY
},
},
created() {
window.addEventListener('scroll', this.handleScroll)
},
destroyed() {
window.removeEventListener('scroll', this.handleScroll)
},
}
...
.item {
position: absolute;
}
How can I do it?
Your element should have an absolute position and add the units as #Nikos mentioned :
<v-card class="item" style="position:absolute" :style="{ top: distance+'px' }">
</v-card>

Is it impossible to update data without update a component in Vue.js?

There is a template
<div class="wrap">
<p v-for="item in items">
{{ item }}
</p>
</div>
There is a data
data: {
items: [1, 2, 3]
}
Problem: after update DOM I trying to actualization data, but update a data causes to re-rendering a component.
Expected result:
Question: how to synchronisation data after update DOM?
Live demo
Below is one simple demo on the implemention of drag/drop by Vue.
Create one watch, it will watch this.dragedItem. if changed, it will re-calculate the styles which apply to the dragItems.
When drag start, get current dragging item (dragedItem=current item).
When drag end, reset dragging item (dragedItem={}),
When drop, remove item from dragItems, then push to dropItems.
In above steps, we just need to change the data, then Vue will auto render.
You can check HTML Drag And Drop API for more details.
For the transition effects, you can check Vue Guide: Enter/Leave Transition and Vue Guide: State Transtion.
app = new Vue({
el: "#app",
data: {
dragItems: ['A', 'B', 'C', 'D'],
dragedItem: {},
dropItems: [],
styles: {},
defaultStyle: {'opacity': '', 'background-color': 'yellow'}
},
watch: {
dragedItem: function () {
this.styles = this.dragItems.reduce((pre, cur) => {
pre[cur] = cur === this.dragedItem.item ? {'opacity': 0.5, 'background-color': 'blue'} : {'opacity': '', 'background-color': 'yellow'}
return pre
}, {})
}
},
methods: {
onDragStart: function (ev, item, index) {
ev.dataTransfer.setData('text/plain',null)
this.dragedItem = {item, index}
},
onDragEnd: function (ev) {
this.dragedItem = {}
},
onDrop: function (ev) {
this.dropItems.push(this.dragedItem.item)
this.dragItems.splice(this.dragedItem.index, 1)
}
}
})
.dragzone {
display:flex;
flex-direction: row;
}
.dragger {
width: 30px;
height: 30px;
text-align: center;
border: 1px solid gray;
}
.dropzone {
width: 200px;
height: 30px;
background: green;
padding: 10px;
display:flex;
flex-direction: row;
}
<script src="https://unpkg.com/vue#2.5.16/dist/vue.js"></script>
<div id="app">
<div class="dragzone">
<div class="dragger" draggable="true"
v-for="(item, index) in dragItems" :key="index"
:style="styles[item] ? styles[item] : defaultStyle"
#dragstart="onDragStart($event, item, index)"
#dragend="onDragEnd($event)"
>
{{item}}
</div>
</div>
<div class="dropzone" #drop.prevent="onDrop($event)"
#dragover.prevent=""
>
<div class="dragger" style="background-color:yellow" draggable="true" v-for="(item, index) in dropItems" :key="index">
{{item}}
</div>
</div>
</div>