Using 'conditional' transitions with Vue - vue.js

I have a sort of simple question. Im building a carousel which rotates vertically using vue. When the user clicks the forward button, I want vue to transition the elements in/out using my 'forward' transition (moving them down). When the user clicks the backward button, I want vue to transition the elements in/out using my 'backward' transition (moving them up).
Right now, I just have one transition that performs my forward transition, whether the user is navigating forward or backward.
Is there any way to accomplish this?
Heres my component:
<template>
<div class="carousel__container">
<div class="carousel__visible">
<div class="carousel__slides">
<transition-group name="carouselNext">
<div v-for="(quote, index) in quotes" class="carousel__slide" key="index" v-show="index === currentSlide">
<blockquote>{{ quote.quote }}</blockquote>
<cite>{{ quote.cite }}</cite>
</div>
</transition-group>
</div>
</div>
<button class="btn btn--alt" #click="prev">Back</button>
<button class="btn btn--primary" #click="next">Next</button>
</div>
Thanks!

Easiest way is to change the transition name based on direction
<transition-group name="carouselNext"> can become <transition-group :name="direction">
add new direction data var and include toggling based on methods
prev(){
this.direction = "carouselPrev"
this.currentSlide = (this.currentSlide + this.quotes.length - 1)%this.quotes.length
},
next(){
this.direction = "carouselNext"
this.currentSlide = (this.currentSlide + 1)%this.quotes.length
}
and finally add the alternate carouselPrev css transition classes
here's a working example https://codepen.io/scorch/pen/NwwpxM

Related

How to properly create a popup component in Vue 3

As part of becoming a better Vue programmer, I am trying to implement a popup similar to Popper with a clean and Vueish architecture. Here is a simple schematic that I came up with:
So basically there is a target component, which is the reference for the popup's position. The popup can be positioned above, below, right and left of the target, therefore I will need to have access to the target element in my popup. Also, the target can be an arbitrary component. It can be a simple button or span, but also something much more complex.
Then there is the popup itself, which will be put into a modal at the end of the body, It contains the actual content. The content again can be an arbitrary component.
I have a working implementation of the popup, but the basic structure seems to be far from perfect. I am using two slots, one for the target element and one for the content.
Here is what I have come up with so far for the template:
<template>
<div ref="targetContainer">
<slot name="target"></slot>
</div>
<teleport to="body">
<div v-show="show" class="modal" ref="modal">
<div ref="popover" class="popover" :style="{top: popoverTop + 'px', left: popoverLeft + 'px'}">
<slot name="content"></slot>
</div>
</div>
</teleport>
</template>
There are several issues with this that I am not really happy with.
Using the popup is not very simple
When using this popup in another component, two <template> tags are rquired. This is ungly and not very intuitive. A very simple use case looks like this:
<modal :show="showPopup" #close="showPopup=false">
<template v-slot:target>
<button #click="showPopup=true"></button>
</template>
<template v-slot:content>
<div>Hello World!</div>
</template>
</modal>
The target is wrapped in another <div>
This is done to get access to the target element, that I need for the layout. In mounted() I am referencing the target element like this:
let targetElement = this.$refs.targetContainer.children[0];
Is this really the best way to do this? I would like to get rid of the wrapping <div> element, which just asks for unintended side effects.
The best solution would be to get rid of one slot and somehow reference the target element in another way because I only need its layout information, it does not have to be rendered inside the popover component.
Can someone point me in the right direction?
Here is my solution, which was inspired by a comment on my question and which I think is worth sharing.
Instead of putting the target element into a slot, I am now passing its ref as a prop, which makes things much cleaner.
The popover component's template now looks like this.
<template>
<teleport to="body">
<div v-show="show" class="modal" ref="modal">
<div ref="popover" class="popover" :style="{top: popoverTop + 'px', left: popoverLeft + 'px'}">
<slot ref="content"></slot>
</div>
</div>
</teleport>
</template>
I has a targetRefprop, so the component can be simply used like this:
<div ref="myTargetElement" #click="isPopupVisible=true">
</div>
<modal :show="isPopupVisible" #close="isPopupVisible=false" targetRef="myTargetElement">
<!-- popup content goes here -->
</modal>
And after mounting I can access the target element like this:
let targetElement = this.$parent.$refs[this.targetRef];
I like this solution a lot. However, ideas, advice or words of caution are still highly welcome.

How to set router-link for a component except some parts

I've created a card using VueJS & tailwind. There are some buttons on the card like edit & delete. I want to add a router-link on this Card Component, so that when a user clicks on the card, it opens up, but then the edit and delete buttons don't work any more and clicking on them, redirects the page too.
How can I solve this problem?
Here is my code:
<router-link to="somewhere">
<card-component :data="products"></card-component>
</router-link>
Card Component:
<div :key="id" v-for="(row, id) in data">
<div>
<img alt="Product Image"
v-lazy="row.img_url"
class="vs-images-hover-zoom rounded-lg p-0 h-24 w-32 object-center object-cover"/>
</div>
<div>
<p class="product-name font-medium">{{ row.name }}</p>
</div>
<div class="whitespace-no-wrap">
<feather-icon icon="EditIcon" svgClasses="w-5 h-5 hover:text-primary stroke-current" #click="editPopup = true"/>
<feather-icon icon="TrashIcon" svgClasses="w-5 h-5 hover:text-danger stroke-current" class="ml-2" #click="deletePopup = true"/>
</div>
</div>
Seems like you're having trouble with event handling because button's clicks are being caught by router-link. You can do one of them:
Use #click.stop in your buttons inside the Card component (it will stop event propagation);
Don't use router-link wrapping the whole card and put it just on image and title elements, for example;
Don't use router-link at all and get the #click event in right peaces of card, using this.$router.push to handle it.

Bootstrap Fixed Navbar.. how to adjust the content padding-top dynamically?

This question is for Bootstrap v3.3.7 and upwards (prior to v4).
I am using navbar-fixed-top; when the small screen break-point kicks in, and the navbar height expands when opened, i want to push down the content to be below the navbar (in much the same way that the navbar-static-top works).
How to do it? my approach would be to write a specific CSS rule for it. But how can I know at exactly what value the small screen break-point kicks in?
<div class="navbar navbar-default navbar-fixed-top">
<div class="container">
<div class="navbar-header pull-right">
<!-- this is the hamburger, shown on smaller width screens -->
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li class="active">My Home</li>
<li>Menu 1 </li>
<li>Menu 2</li>
<li>Menu 3</li>
</ul>
</div>
</div>
</div>
<section id="content">
<h3>using Fixed-navbar</h3>
<p>This uses <strong>FIXED NAVBAR (navbar-fixed-top)</strong>, which means navbar DOES NOT scroll out of view... i.e. it remains VISIBLE at all times.<br/>
However, it does NOT push the content down (the pink section) when the navbar needs to expands via the Hamburger button
</p>
<p>Sed dignissim blah etc...</p>
</section>
Codepen here:
Here is my own answer:
At first i tried playing with the transitionend event, but it proved to be unreliable to solve my problem; not because in itself it is unreliable, but because the nav height was not updating soon enough after transition ends.
So i resorted to polling, and other logic which works reliably
let navContainer$ = $("div.navbar > div.container");
let collapsedDiv$ = $("div.navbar > div.container > div.collapse");
let intervalHandle;
//on hamburger click...
$("button.navbar-toggle").click(() => {
let heightBeforeTrx = navContainer$.height();
console.error( 'heightBeforeTrx:', heightBeforeTrx ) //= 50
if (heightBeforeTrx === 50) {
//we are moving from collapsed to Expanded...
intervalHandle = setInterval( () => {
console.log('Yo keep polling...')
//we expect...a class 'collapse.in' to exist once expanded
let test1 = collapsedDiv$.hasClass( "in" );
if (test1) {
//STOP THE POLLING...
clearInterval(intervalHandle);
console.log('polling ended.')
$("body").css('padding-top', '176px')
}
}, 100 )
} else {
//we are moving from Expanded to Collapsed...
intervalHandle = setInterval( () => {
console.log('Yo keep polling...')
//we expect...a class 'collapse.in' to NOT exist once collapsed
let test2 = collapsedDiv$.hasClass( "in" );
if (!test2) {
//STOP THE POLLING...
clearInterval(intervalHandle);
console.log('polling ended.')
$("body").css('padding-top', '50px')
}
}, 100 )
}
})
Good enough for now, the 176px hardcoding should be adjusted accordingly; Only caters for 2 use-cases, there are other heights possible when the width gets even smaller, but i'll ignore those; also need additional jQuery for pushing content back up if re-sizing window manually, after it has been expanded from hamburger click.
Here is a codepen where you can see css only changes to push the content down when navbar is expanded. The only thing I have added is a new css class called mycss (not named correctly, i agree :)). You can change the media query based on your exact requirements.
Also, it might be worth looking at bootstrap-4.3, as it looks like they already have something like this out of the box.

Modal ls not firing with V-Show

I have a modal that is being imported and added to a page as a component. The modal is called into the page as
<TwoFactorStatus v-show="showTwoFactoreModal"></TwoFactorStatus>
Then a button has a click event as such
<button class="btn btn-danger pull-right" #click="ShowTwoFactoreModal()" type="danger">Disable two-factor authentication</button>
Which then calls a method to
ShowTwoFactoreModal() {
this.showTwoFactoreModal = true;
}
The Modal looks like so
<template>
<div class="modal fade" id="showTwoFactoreModal" data-keyboard="false" data-backdrop="static" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header text-center">
Two Factor Switch Off
</div>
<div class="modal-body">
<p>This modal must pass</p>
</div>
</div>
</div>
</div>
</template>
Change your ShowTwoFactoreModal function to something like this:
ShowTwoFactoreModal() {
this.showTwoFactoreModal = true;
$('#showTwoFactoreModal').modal('show');
}
Either that or maybe using :class bindings and appending the active class, but this is more straight. I usually do it with an eventBus, but it might be overkill for your project... if you were to do it with an event bus, you could attach an eventListener to the modal component, and attach a showModal function callback to that listener where you don't need to set IDs you can just call it like: $(this.$el).modal('show'); ... but thats another story...
Bootstrap modal is using its own js functions to handle show and hide modal. Vue v-show can only show and hide the components which you programmed its logic. But bootstrap modal is showing and hiding by appending class to modal component.
If you want to use without bootstrap.js to toggle you can use jQuery with Vue as below. (if just want show then change method .show())
//200 is duration of fade animation.
<button #click="toggleModal"></button>
function toggleModal() {
$('#your_modal_id').fadeToggle(200)
}
If you want to use bootstrap own modal with their js then you can copy and paste modal code
here. https://getbootstrap.com/docs/4.0/components/modal/
If you want Vue v-show to handle all process then write logic to do it. If need help just ask.

Aurelia router-view inside dialog not working on reopening dialog

As per example given in aurelia documentation I am opening dialog box with viewmodel (say prompt ). This prompt has view inside in which I am adding "router-view" tag.
My routes are already configured. So when first time I open dialog it opens correct views as configured in routes and everything works well. But when I close dialog and re-opens dialog, It's not showing first route view. If I click other route link and come back to first route it works.
I have observed it's not creating instance of view model of first route( when opened dialog second time).
How to fix this issue?
Prompt html
<template><div class="row">
<left-menu ></left-menu>
<router-view></router-view>
</div></template>
<template>
and left-menu.htm
<template>
<div class="list-group">
<template repeat.for="item of items">
<a class="list-group-item ${$parent.selectedNav === item.routeName ? 'active' : ''}" route-href="route.bind: item.routeName;" click.delegate="$parent.select(item)">${item.text}</a>
</template>
</div>
Using a router inside a modal window seems off to me. The router is used to manage pages, but a modal is used to manage content within a page.
I would suggest building a modal window component, you can use <slot> tags to inject content and set it's model bindings to any data within the current view model.
Here's an example of my component that I use for this.
<template>
<div show.bind="visibility" class="modal-window">
<div>
<button class="btn btn-danger btn-sm close-button" click.delegate="close()">close</button>
</div>
<slot></slot>
</div>
<div show.bind="visibility" class="modal-window-overlay" click.delegate="close()"></div>
</template>
-
import { bindable } from 'aurelia-framework';
export class ModalContent {
#bindable visibility: boolean;
close(){
this.visibility = false;
}
}
<modal-content id.bind="'add-variant-window'">
<h4>Modal Content</h4>
<div>you can bind this to things on the current viewmodel</div>
</modal-content>