Transition on class change property - vue.js

I currently have a vue component that I use to select drinks to be ordered.
You can increment or decrement the amount of drinks per type.
There are 2 buttons (Increment and decrement), the decrement button needs to be hidden when the amount of ordered drinks for that type is equal to 0. This can easily be accomplished by using :class="drink.amount > 0 ? 'visible':'invisible'" using the tailwind classes which sets visibility:hidden css property. When using this method it looks like:
Now I tried to implement css animations (sliding the button underneath the drink block to hide it and later hide from dom). I want to implement this using the vue transition component since this is heavily used throughout the application.
Now I have the problem that the vue transition component only works with a limited amount of vue functions among which v-if and v-show. v-if removes the html from the dom. v-show sets the property display:none both of these functions have the effect of shifting the buttons:
I would like to known how I can use the vue transition component and get the requested aligned buttons as I got without the animation.
<div v-for="drink in drinks" class="w-full flex">
<div class="mx-auto flex">
<transition name="transition-slide-right">
<div class="bg-white w-16 h-16 flex justify-center items-center mt-12"
v-show="drink.amount">
<p>-</p>
</div>
</transition>
<div class="bg-brand w-32 h-24 flex justify-center items-center my-8 z-30">
<p>{{drink.name}} ({{drink.amount}})</p>
</div>
<div class="bg-white w-16 h-16 flex justify-center items-center mt-12">
<p>+</p>
</div>
</div>
</div>
And accompanying script.
<script>
export default {
name: "CreateTransaction",
data: function() {
return {
drinks: [
{name: 'Cola', price: '1.0', amount: 1},
{name: 'Sinas', price: '1.0', amount: 0}
],
}
}
}
</script>
Finally the css:
.transition-slide-right-enter-active, .transition-slide-right-leave-active {
transition: transform .5s;
}
.transition-slide-right-enter, .transition-slide-right-leave-to {
transform: translateX(100%);
}
As a current workaround I removed the transition component, added the transition-slide-right-enter-active class and if the count == 0 I conditionally add the transition-slide-right-enter class. This does not hide the element but hiding it is not required since I is moved underneath the center block.

you can try the following code
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
<div class="row" id="todo-list-example">
<div v-for="(drink,index) in drinks" class="w-full flex">
<transition name="transition-slide-right"><button v-show="drink.amount" type="button" class="btn btn-danger" v-on:click="click1(index)">-</button></transition>
<a class="btn btn-success" >{{drink.name}} ({{drink.amount}})</a>
<button type="button" class="btn btn-danger" v-on:click="click2(index)">+</button>
</div>
</div>
<style>
.w-full{margin:10px 0px;}
.transition-slide-right-enter-active, .transition-slide-right-leave-active {
transition: transform .5s;
}
.transition-slide-right-enter, .transition-slide-right-leave-to {
transform: translateX(100%);
}
</style>
<script>
new Vue({
el: '#todo-list-example',
data(){
return {
drinks: [
{name: 'Cola', price: '1.0', amount: 1},
{name: 'Sinas', price: '1.0', amount: 1}
],
}
},
methods:{
click1:function(index){
this.drinks[index].amount=this.drinks[index].amount-1;
},
click2:function(index){
this.drinks[index].amount=this.drinks[index].amount+1;
}
}
})
</script>

Related

Passing props dynamically to a component inside a v-for loop

I have a v-for loop that iterates through an array of meetings (meetings between sellers and potential buyers of used cars) and prints a card for each meeting, a basic display of who the meeting is with, what car it is about and the scheduled date. Now, I implemented a button that when clicked, opens a dialog with a Google Maps component that shows the marker for the agreed location of the meeting.
My problem is that no matter what card I click on, the dialog will always display the location of the LAST card, regardless of which has been clicked. I would think that since Im calling the component INSIDE the v-for loop it would pass props dynamically for each card, on each iteration, but that does not seem to be the case.
Here is the HTML:
<div
v-for="meeting in meetings"
:key="meeting.did"
class="col-12 col-md-6 col-lg-3 q-pa-md q-mx-xl"
>
<q-card class="my-card homeCard q-pa-md">
<q-dialog class="mapDialog flex column" v-model="mapDialog">
<MeetMapComponent
:key="componentKey"
:mapDiv="mapDiv"
:mapData="meeting.address"
:buyerName="meeting.name"
/>
</q-dialog>
<q-card-section
class="tipCardImage flex row justify-end"
:style="`background-image: url(${meeting.car.carImg})`"
>
<router-link
:to="`/user/meet/edit/${meeting.did}`"
style="text-decoration: none"
>
<q-icon
#click="fetchMeeting(meeting.did)"
name="fa-solid fa-pencil editNameIcon q-mb-sm q-ml-sm"
></q-icon>
</router-link>
<q-icon
name="fa-solid fa-trash editNameIcon q-mb-sm q-ml-sm"
#click="triggerDelete(meeting.did)"
></q-icon>
</q-card-section>
<q-card-section>
<div class="cardTitle">
<span>Encuentro Con</span> {{ truncateString(meeting.name, 30) }}
</div>
<div class="tipCardText">
<span>Agendado para el </span>
<p>{{ truncateString(meeting.date, 120) }}</p>
</div>
<div class="flex row justify-end">
<q-btn
#click="mapDialog = true"
class="text-white cardButton"
:class="{ cardButtonMobile: $q.screen.lt.md }"
>Ver UbicaciĆ³n</q-btn
>
</div>
</q-card-section>
</q-card>
</div>
And here is the code for the MeetMapComponent:
<template>
<div class="meetMapContainer">
<div ref="mapDiv" style="width: 100%; height: 500px" />
<h5 class="text-center text-white">{{ props.mapData.address }}</h5>
</div>
</template>
<script setup>
import { ref } from "vue";
import { useAuthStore } from "stores/auth";
import { storeToRefs } from "pinia";
import { Loader } from "#googlemaps/js-api-loader";
const props = defineProps({
mapData: Object,
buyerName: String,
});
const GOOGLE_MAPS_API_KEY = "...";
const loader = new Loader({ apiKey: GOOGLE_MAPS_API_KEY });
const mapDiv = ref(null);
async function mapRender() {
await loader.load();
const map = new google.maps.Map(mapDiv.value, {
mapTypeId: "roadmap",
center: props.mapData.coordinates,
zoom: 13,
});
console.log(map);
new google.maps.Marker({
position: props.mapData.coordinates,
map,
title: `Encuentro con ${props.buyerName}`,
});
}
mapRender();
</script>
I will help you as much as I understand. You use the mapDialog variable to open the dialogue. But even if this variable is used in v-for, its reference does not change. For this reason, when you want to open a modal, all modals may be opened and the last one may appear because it is the last one opened. Please check the dom.
I think this method can solve the problem.
in script
const meetings = [
{
did: 'some value',
address: 'some address',
name: 'some name',
// add modal flag
showMapModal: false
}
]
template
<div
v-for="meeting in meetings"
:key="meeting.did"
class="col-12 col-md-6 col-lg-3 q-pa-md q-mx-xl"
>
<q-card class="my-card homeCard q-pa-md">
<q-dialog class="mapDialog flex column" v-model="meeting.showMapModal">
<MeetMapComponent
:key="componentKey"
:mapDiv="mapDiv"
:mapData="meeting.address"
:buyerName="meeting.name"
/>
</q-dialog>
</q-card>
</div>

Vue3 is cutting properties from custom Elements used in App

i want to include an external library in my Vue3 app. But when i try to inlcude them in my app, but some properties the element uses get cutted by Vue. Maybe someone can explain me this behaviour and how to fix this.
The attribute i am pointing on is "secondary"
vite.config.ts:
...
plugins: [
vue({
template: {
compilerOptions: {
// treat all tags with a dash as custom elements
isCustomElement: (tag) => {
return tag.includes("-");
},
},
},
}),
],
...
index.html
<div class="container">
<atom-button secondary>Test</atom-button>
<my-vue-comp></my-vue-comp>
</div>
in my component:
<template>
<div ref="root">
<atom-button secondary>Test</atom-button>
</div>
</template>
output in DOM:
<div class="container">
<atom-button secondary="" role="button" tabindex="0" type="button" aria-disabled="false">Test</atom-button>
<my-vue-comp>
<div>
<atom-button role="button" tabindex="0" type="button" aria-disabled="false">Test</atom-button>
</div>
</my-vue-comp>
</div>

Use event #change and #click to get data pull-and-drop in vuejs?

Template:
<div class="row">
<div class="col-lg-12 mt-2">
<draggable
class="list-group"
tag="ul"
v-model="imgList.top"
v-bind="dragOptions"
>
<transition-group type="transition">
<li
class="list-group-item" style="align-items: center;display: flex;padding: 12px 15px; font-size: 13px"
v-for="record in imgList.top"
:key="record._id"
#click="onClickValue"
<!-- #change="onChangeValue" -->
>
<div class="col-lg-12">
<img class="w-100" :src="getImageURL(record.image)" alt />
</div>
</li>
</transition-group>
</draggable>
</div>
</div>
<script>
import draggable from "vuedraggable";
export default {
components: {
draggable
},
data() {
return {
imgList: {
top: [
{_id: '12354356444433', image: '09jgg24.jpg'},
{_id: '12354356442211', image: '09jaef2.jpg'},
]
}
}
},
methods: {
onClickValue() {
console.log('ok') // No results received
},
//onChangeValue() {
// console.log('ok') // No results received
//},
}
}
</script>
I am doing drag-and-drop in vuejs. I hold the image and drag and drop it.. Now I want when I drag an image to catch the #click or #change event. I tried putting #click and #change in my code. But when I drag and drop nothing appears? Please show me to catch that event. Thank you

bootstrap-vue : how to recognize hover event over b-tab

I'm trying to change the active tab of a bootstrap-vue b-tabs when the tab title is hovered over, not only when clicked on. I'm having trouble isolating this event.
In the following Codepen example, I can isolate the event when the image is being hovered over, however I want to isolate the event when the title ('Tubes and Vials' for example) is being hovered over.
I'm fairly new to Vue so I apologize if this is a simple answer, but I haven't struggled with this for a while now and haven't been able to figure this out. Thanks!
Component File
<template>
<b-container class="px-3" fluid>
<div>
<h3>Range Of Glass Products We Inspect</h3>
<p>Anything formed from tubular glass</p>
</div>
<div>
<b-tabs content-class="mt-3" align="left" class="vial-types" vertical>
<b-tab
v-for="glassItem in productRange"
v-bind:key="glassItem.type"
v-bind:ref="glassItem"
v-bind:title="glassItem.type"
#mouseover.native="greet()"
#mouseleave.native="greet()"
>
<b-img
v-bind:src="glassItem.image"
alt="Factory Image">
</b-img>
</b-tab>
</b-tabs>
</div>
</b-container>
</template>
<script>
export default {
name: "ProductRange",
data() {
return {
productRange: [
{type: "Screw & Flanged Head", image:"https://picsum.photos/600/400/", hover: false},
{type: "Tubes and Vials", image:"https://picsum.photos/600/400/", hover: false},
{type: "Pipettes, Syringes, Filling Needles", image:"https://picsum.photos/400/400/",hover: false},
{type: "Ampoules", image:"https://picsum.photos/600/400/", hover: false},
{type: "Custom Geometries Per Customer Specification", image:"https://picsum.photos/600/400/", hover: false}
]
}
},
methods: {
greet: function () {
console.log("Hovering");
}
}
}
</script>
<style lang="sass">
</style>
You could also use the b-tab's title slot, and then add a hover/unhover listener in there:
<b-tabs content-class="mt-3" align="left" class="vial-types" vertical>
<b-tab
v-for="glassItem in productRange"
v-bind:key="glassItem.type"
v-bind:ref="glassItem"
>
<template v-slot:title>
<div
#mouseover="hovered"
#mouseleave="unHovered"
>
{{ glassItem.type }}
</div>
</template>
<b-img
v-bind:src="glassItem.image"
alt="Factory Image">
</b-img>
</b-tab>
</b-tabs>
Sadly I don't think there's a built-in way to easily do this.
However, you can still solve this by hiding the standard tabs and instead reconstruct the structure yourself using b-nav and binding to the b-tabs v-model.
You can then add your events the b-nav-item as they'll be working as your tabs.
new Vue({
el: "#app",
data: {
selectedTab: 0,
productRange: [
{
type: "Screw & Flanged Head",
image: "https://picsum.photos/600/400/"
},
{
type: "Tubes and Vials",
mage: "https://picsum.photos/640/400/"
},
{
type: "Pipettes, Syringes, Filling Needles",
image: "https://picsum.photos/400/400/"
},
{
type: "Ampoules",
image: "https://picsum.photos/600/400/"
},
{
type: "Custom Geometries Per Customer Specification",
image: "https://picsum.photos/700/400/"
}
]
},
methods: {
greet: function() {
console.log("hovering");
},
onTabHover(glassItem) {
console.log("Tab hovered", glassItem)
}
}
});
<link href="https://unpkg.com/bootstrap#4.4.1/dist/css/bootstrap.min.css" rel="stylesheet"/>
<link href="https://unpkg.com/bootstrap-vue#2.4.2/dist/bootstrap-vue.css" rel="stylesheet"/>
<script src="https://unpkg.com/vue#2.6.10/dist/vue.min.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.4.2/dist/bootstrap-vue.min.js"></script>
<b-container id="app" class="px-3"fluid>
<div>
<h3>Range Of Glass Products We Inspect</h3>
<p>Anything formed from tubular glass</p>
</div>
<div>
<b-row>
<b-col cols="auto">
<b-nav pills vertical>
<b-nav-item v-for="(glassItem, index) in productRange"
:active="selectedTab === index"
#click="selectedTab = index"
#mouseenter="onTabHover(glassItem)">
{{ glassItem.type }}
</b-nav-item>
</b-nav>
</b-col>
<b-col>
<b-tabs v-model="selectedTab"
content-class="mt-3"
class="vial-types"
nav-class="d-none">
<b-tab
v-for="glassItem in productRange"
:key="glassItem.type"
:ref="glassItem"
:title="glassItem.type"
#mouseover.native="greet()"
#mouseleave.native="greet()"
>
<b-img
:src="glassItem.image"
alt="Factory Image">
</b-img>
</b-tab>
</b-tabs>
</b-col>
</b-row>
</div>
</b-container>

How can I use transition on individual list elements in Vue.js?

My understanding of transitions in vue.js is that you use <transition>
to animate between individual elements and <transition-group> to animate a whole list.
It seems as though if you wanted to animate a transition within a list item you'd use <transition> within the list. e.g. something like this:
<span v-for="item in items">
<transition>
<div>
Transition from this...
</div>
<div>
...to this.
</div>
</transition>
</span>
Yet, when I make that change the animation doesn't work. Is this an expected behavior?
Update: after some tinkering, I have found that my original hypothesis was correct. I was just doing something else wrong. But it's worth noting for anyone else who comes across this problem.
You can use <transition> inside a list if you want to animate individual components of the list.
You use transition groups to transition all children in the same way.
In addition, try setting the transition group before your v-for
new Vue({
el: "#app",
data: {
items : [
{message: 'sometext', id: 1},
{message: 'sometext', id: 2},
{message: 'sometext', id: 3}
],
id : 3
},
methods: {
addItem(){
this.id++
this.items.push({message: 'sometext', id: this.id});
},
enter(){
console.log('transition enter called');
}
}
})
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div id="container">
<button #click="addItem()">Add Item</button>
<transition-group :name="'fade'" v-on:enter="enter">
<span v-for="item in items" v-bind:key="item.id">
{{item.message}}
</span>
</transition-group>
</div>
</div>
I am not exactly sure what you are trying to do with your example.
If you would like to transition a list
<transition-group name="fade" tag="span">
<div v-for="item in items" v-bind:key="item">
{{ item }}
</div>
</transition-group>
If you would like to transition between two items.
<transition name="fade">
<div v-show="whatever === true">
Transition from this...
</div>
</transition>
<transition name="fade">
<div v-show="whatever === false">
...to this.
</div>
</transition>