vueJs equivalent of jQuery slideDown/slideUp or slideToggle - vue.js

I'm experimenting with vue for the first time.
I've replaced a jquery show/hide that was using .slideDown() / .slideUp() with v-show - however I much prefer the animation of jQuery's slideup/down. Is there an easy way to do this with vue?
Simplified code here:
<nav class="bg-blue" role="navigation">
<div class="container classes here">
<div class="classes here">
<h1 class="classes here">
<a href="/" class="classes here">Site Name
</a>
</h1>
<button class="classes here" #click="isShowing ^= true">
hamburger svg here
</button>
</div>
<div class="main-nav classes here" v-show="!isShowing">
<div class="classes here">
<!-- nav items here -->
</div>
</div>
</div><!-- /.container -->
</nav>
Please advise.

You can install modules for this, or you can write a custom component. I suggest the second option.
<template>
<div>
<button #click="isShowing">
hamburger svg here
</button>
<!-- animation of appearance/disappearance.
More info: https://v2.vuejs.org/v2/guide/transitions.html -->
<!-- The attribute "name" is used to create custom classes
that are applied during animation -->
<transition name="main-nav"
#enter="transitionStep1"
#after-enter="transitionStep2"
#before-leave="transitionStep3"
#after-leave="transitionStep4">
<div class="main-nav" v-show="!active">
<div class="classes here">Some text</div>
</div>
</transition>
</div>
</template>
<script>
export default {
name: "Accordion",
data() {
return {
// set the variable that will hide/show the block
active: false
}
},
methods: {
isShowing() {
// change the value of the variable that will hide/show the block
this.active = !this.active;
},
transitionStep1(el) {
// set the block height at the moment of its appearance
el.style.height = el.scrollHeight + 'px'
},
transitionStep2(el) {
// remove inline styles from the block after animation of its appearance
el.style.height = ''
},
transitionStep3(el) {
// set the height of the block at the beginning of its disappearance animation
el.style.height = el.scrollHeight + 'px'
},
transitionStep4(el) {
// remove inline styles from the block after the animation of its disappearance
el.style.height = ''
},
},
}
</script>
<style lang="scss" scoped>
.main-nav {
overflow: hidden;
-webkit-transition: height 0.3s ease;
transition: height 0.3s ease;
}
.main-nav-enter {
height: 0;
}
.main-nav-leave-to {
height: 0 !important;
}
</style>

Related

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

Vue transition works for "enter" state but not for "leave" state

I have a modal rendered on top of a semi-transparent backdrop. Both elements have a v-if controlled by the same variable.
Although the enter transition animation works fine, the `leave`` transition animation is ignored (it should fade out smoothly, instead it disappears instantly). Why?
Codepen
Markup:
<div id="app">
<button #click="showModal = !showModal">Toggle Modal</button>
<div v-if="showModal" class="modalBackdrop">
<transition name="content" appear>
<div v-if="showModal" class="modalContent">
Modal
</div>
</transition>
</div>
</div>
CSS:
.content-enter-active {
animation: slide-up .75s;
}
.content-leave-active {
animation: fade-out .75s;
}
#keyframes slide-up {
0% {
transform: translateY(100%);
}
100% {
transform: translateY(0);
}
}
#keyframes fade-out {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
It seems that the div with modalBackdrop class is disappearing before the nested div with class modalContent does its transition, so try to wrap modal Backdrop by a transition component with name backdrop which also takes the fade-out animation :
.backdrop-leave-active,.content-leave-active { /*.backdrop-leave-active is sufficient since the parent opacity is applied on children*/
animation: fade-out .75s;
}
template :
<div id="app">
<button #click="showModal = !showModal">Toggle Modal</button>
<transition name="backdrop" appear>
<div v-if="showModal" class="modalBackdrop">
<transition name="content" appear>
<div v-if="showModal" class="modalContent">
Modal
</div>
</transition>
</div>
</transition>
</div>
DEMO
When showModal is false, the transition element is destroyed immediately. If the only reason you use v-if="showModal" in transition parent is to disable modalBackdrop, then you can assign this class dynamically.
This is working as expected:
<div :class="{ modalBackdrop: showModal }">
<transition name="content" appear>
<div v-if="showModal" class="modalContent">
Modal
</div>
</transition>
</div>

Why does this Vue3 transition break data binding?

I have this issue I've been hitting for hours now; I can't understand why it doesn't work as expected.
I pasted an example code below. The issue is that when editing the name, {{name}} is not updated. However, if I remove either of the <transition> element or the v-if="show" condition, then data binding works as expected. Same if the {{name}} is placed outside the transition.
So it seems the transition blocks data binding? However I don't find anything about it in the docs or elsewere. I tested this code in a Vue2 playground, and it works as expected (data binding works). So the behavior seems to depend on Vue3.
Is there something I'm missing? Is it a bug in Vue3?
Thanks in advance for any input or idea.
<template>
<div id="demo">
<button v-on:click="show = !show">
Toggle
</button>
<transition name="fade">
<div v-if="show">
<p>hello, {{name}}</p>
<input v-model="name" type="text" />
</div>
</transition>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
data() {
return {
name: "",
show: true,
}
}
});
</script>
<style scoped>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.8s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>
It works just fine in plain JS...
So try to focus on the differences:
TypeScript (i cannot use it here on SO) - I really doubt its the cause but you can try
Scoped CSS - did you tried to remove scoped ? There are some issues with scoped CSS and <transition>. Check this issue in Vue-loader. My example is not build with Webpack so Vue-loader is not used but it's for sure used in your project...
const app = Vue.createApp({
data() {
return {
name: "",
show: true,
}
},
template: `
<div id="demo">
<button v-on:click="show = !show">
Toggle
</button>
<transition name="fade">
<div v-if="show">
<p>hello, {{name}}</p>
<input v-model="name" type="text" />
</div>
</transition>
</div>
`
}).mount("#app");
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.8s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.0.0/vue.global.js"></script>
<div id="app"></div>
I meet same question, you can try to set the initial value of 'show' to false and at the lifeCycle to modify 'show' for true.

How to extend component and use slot

Goal: I want to be able to have a modal template that I can extend in other pages in my Vue.js (Nuxt.js) application
ModalTemplate.vue:
<!-- Base Modal Component -->
<template>
<!-- Modal -->
<div class="modal opacity-0 pointer-events-none fixed w-full h-full top-0 left-0 flex items-center justify-center">
<div class="modal-overlay absolute w-full h-full bg-gray-900 opacity-50"></div>
<!-- Modal Container -->
<div class="modal-container bg-gray-300 w-5/12 mx-auto rounded shadow-lg z-50 overflow-y-auto">
<!-- Top Right escape button (needs to be within the container for z-index purposes) -->
<div class="modal-close absolute top-0 right-0 cursor-pointer flex flex-col items-center mt-4 mr-4 text-white text-sm z-50">
<fa icon="times" class="fa-2x"></fa>
<span class="text-sm">(Esc)</span>
</div>
<div class="modal-content">
<!-- Title of Modal -->
<div class="modal-title-container">
<slot name="modal-header"></slot>
</div>
<!-- Body of Modal -->
<div class="modal-body-container">
<slot></slot>
</div>
<!-- Footer of Modal -->
<div class="modal-footer-container">
<slot name="modal-footer"></slot>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'ModalTemplate',
data() {
return {
}
},
methods: {
toggleModal: function() {
var body = document.querySelector('body');
var modal = document.querySelector('.modal');
modal.classList.toggle('opacity-0');
modal.classList.toggle('pointer-events-none');
body.classList.toggle('modal-active');
},
modalHidden: function () {
this.toggleModal();
this.messageBus.$emit('closing')
},
keyDownPressed: function(keyPressed) {
var isEscape = false
if (keyPressed.key === "Escape" || keyPressed.key === "Esc") {
isEscape = true
} else {
isEscape = (keyPressed.keyCode === 27)
}
if (isEscape && document.querySelector('body').classList.contains('modal-active')) {
this.modalHidden();
}
}
},
created: function() {
window.addEventListener('keydown', this.keyDownPressed)
},
destroyed: function() {
window.removeEventListener('keydown', this.keyDownPressed)
},
mounted: function() {
this.toggleModal();
var closeModalSelector = document.querySelectorAll('.modal-close')
for (var i = 0; i < closeModalSelector.length; i++) {
closeModalSelector[i].addEventListener('click', this.modalHidden)
}
const overlay = document.querySelector('.modal-overlay')
overlay.addEventListener('click', this.modalHidden);
}
}
</script>
<style lang="postcss">
.modal-page {
#apply pointer-events-none;
#apply fixed;
#apply w-full;
#apply h-full;
#apply top-0;
#apply left-0;
#apply flex;
#apply items-center;
#apply justify-center;
}
.modal-overlay {
#apply absolute;
#apply w-full;
#apply h-full;
#apply bg-gray-900;
}
.modal-container {
#apply bg-gray-300;
#apply mx-auto;
#apply rounded;
#apply shadow-lg;
#apply z-50;
#apply overflow-y-auto;
}
.modal-content {
#apply py-4;
}
.modal-title-container {
#apply flex;
#apply justify-between;
#apply items-center;
#apply border-b;
#apply border-gray-400;
#apply px-4;
#apply pb-3;
}
.modal-body-container {
#apply py-2;
#apply px-4;
}
.modal-footer-container {
#apply flex;
#apply border-t;
#apply border-gray-400;
#apply px-4;
#apply pt-2;
}
</style>
CertificateDetailsModal.vue:
<template>
<ModalTemplate ref="modal">
<template v-slot:modal-header>
This is a header
</template>
</ModalTemplate>
</template>
<script>
import ModalTemplate from '~/components/Modals/ModalTemplate'
export default {
name: 'DetailsModal',
components: {
ModalTemplate
},
model: {
prop: 'certificate',
event: 'input'
},
props: {
certificate: {
type: Object,
default: null
}
},
mounted() {
},
methods: {
closeModal: function() {
alert('closing modal!')
this.$store.dispatch('certificates/loadCertificates')
this.$emit('input', null);
}
}
}
</script>
<style scoped>
</style>
Attempts:
I looked at extending the modal, but it gave me quite a few errors when I tried to dismiss the modal (I can provide the code if needed).
Question:
How can I extend the Modal (getting all the functionality of the functions) while adding additional functionality in the CertificateDetailsModal (such as functions, methods, and html)?
You could re-declare the slots in your wrapper component's template. For instance, the following template declares a modal-footer slot and a default slot (unnamed assumed to have a name of default):
<!-- CertificateDetailsModal.vue -->
<template>
<ModalTemplate>
<template v-slot:modal-header>
My header
</template>
<template v-slot:modal-footer> <!-- pass `modal-footer` slot to ModalTemplate -->
<slot name="modal-footer"></slot>
</template>
<slot /> <!-- pass `default` slot to ModalTemplate -->
</ModalTemplate>
</template>
Then your app could use the CertificateDetailsModal like this:
<!-- App.vue -->
<template>
<CertificateDetailsModal>
<template v-slot:modal-footer>
<footer>My footer</footer>
</template>
<span>My default</span>
</CertificateDetailsModal>
</template>
demo

Paper-badge appears in wrong position when used with app-layout

I'm using app-layout with app-drawer / app-drawer-layout and app-header / app-header-layout (see MWE below). The paper-badge is attached to an element inside the app-header. The first time the page is loaded, it briefly appears in the correct position (issue #12), than disappears off the right of the screen, exactly 256px too far to the right, which is the exact size of the app-drawer.
When the screen is resized, it recalculates the position to the correct place. I suspect that the position is calculated before the drawer is fully loaded.
This does not happen when the app-drawer is retracted and not visible.
I tried running updatePosition() at ready but that did not help.
Here is my MWE:
<template>
<style is="custom-style">
app-drawer {
--app-drawer-content-container: {
background-color: green;
};
}
app-header {
background-color: yellow;
}
</style>
<app-drawer-layout fullbleed>
<app-drawer slot="drawer">
<div id="drawerbox">Navigation Drawer</div>
</app-drawer>
<div class="container">
<app-header-layout>
<app-header id="header" condenses reveals effects="waterfall" slot="header">
<app-toolbar id="toolbarheader">
<div main-title id="toolbartitlebox">Title Toolbar</div>
<paper-icon-button id="discoursebutton" icon="communication:forum"></paper-icon-button>
<paper-badge for="discoursebutton" label="20" title="20 results"></paper-badge>
</app-toolbar>
</app-header>
</app-header-layout>
</div>
</app-drawer-layout>
</template>
One hack that works is to set a setTimeouton the updatePosition():
ready: function() {
var self = this;
setTimeout(function(){
self.$$('paper-badge').updatePosition();
}, 100);
}
But is there a more elegant way of solving this?
Thanks for your help!
One solution is to put paper-badge outside/after app-drawer-layout :
<template>
<style>
app-drawer {
--app-drawer-content-container: {
background-color: green;
}
;
}
app-header {
background-color: yellow;
}
paper-badge {
margin-top: 10px;
}
</style>
<app-drawer-layout fullbleed>
<app-drawer id="drawer" slot="drawer">
<div id="drawerbox">Navigation Drawer</div>
</app-drawer>
<app-header-layout>
<app-header id="header" condenses reveals effects="waterfall" slot="header">
<app-toolbar id="toolbarheader">
<paper-icon-button id="toggle" on-tap="_onToggle" icon="menu"></paper-icon-button>
<div main-title id="toolbartitlebox">Title Toolbar</div>
<paper-icon-button id="discoursebutton" icon="inbox"></paper-icon-button>
<!-- <paper-badge for="discoursebutton" label="20" title="20 results"></paper-badge>-->
</app-toolbar>
</app-header>
</app-header-layout>
<div class="container">
container
</div>
</app-drawer-layout>
<paper-badge for="discoursebutton" id="pd" label="20" title="20 results"></paper-badge>
</template>
Plnkr: http://plnkr.co/edit/V7zGBpqqzRonbAYyudea?p=preview
I avoided this same issue by putting the <paper-icon-button> and <paper-badge> in a container with position: relative, and stopped using the for attribute.
<div style="position: relative">
<paper-icon-button id="notification-icon" icon="social:notifications" onclick="[[showNotifications]]">
</paper-icon-button>
<template is="dom-if" if="[[messages.length]]">
<paper-badge label="[[messages.length]]" class="red"></paper-badge>
</template>
</div>