vue transition groups with vue2-animate not working - vue.js

I've been struggling with making vue transitions work.
I'm using vue2-animate along with vue to create some simple transition effect for when list items appear/disappear.
Here's the block:
<transition-group class="mt-2"
name="fadeLeft"
v-if="type == selectedType"
tag="ul">
<li class="mb-2" v-bind:key="category.name"
v-for="(category, index) in getCategoriesForType(type)"
#click="selectCategory(category)">
{{ category.name }}
</li>
</transition-group>
It doesn't add any effects when I try it. It simply shows/ hides the lists.
I've tested/confirmed that the animation is working in different component, but cannot figure out why the above code doesn't do any animations.
Here's the block that was working:
<ul class="list-group notes" v-if="notes.length > 0">
<transition-group name="fadeLeft">
<li v-for="(note, index) in notes" v-bind:key="note" class="list-group-item" v-on:remove="notes.splice(index, 1)">
<button class="close" #click="removeNote(index)"><span class="fa fa-times"></span></button>
{{ note.text }}
</li>
</transition-group>
</ul>

Related

Vue JS: How to make two Vue Instances to work together on the same page?

I created a recipe website, you can find it here https://hungry-vegetarian.com/.
The tab on the index page is a vue instance. I'm trying to create a search bar that is also a vue instance.
The problem occurs when I try to display searched recipes on the index page. It just shows the search result on top of the search bar.
Ideally, only searched recipes should appear on the tab just like they are now but without any indications on top of the tab like Breakfast, Salad, Bakery, etc.
I don't know how to make two objects work together. For now, they work separately without communicating with each other.
SEARCH
<div id="search">
<div class="main-search">
<h1 class="search-question">What are you in the mood for?</h1>
<div class="search-box">
<input type="text" v-model="searchQuery" class="input-search" placeholder="Bread">
<button class="btn-search"><i class="fa fa-search"></i></button>
</div>
</div>
<div class="container">
<div class="recipe" v-for="post in filteredList">
<div v-if="searchQuery">
<a v-bind:href="post.url" class="recipe-card__card-link" target="_blank">
<img v-bind:src="post.image" alt="" class="recipe-card__image"/>
<div class="recipe-card__text-wrapper">
<h2 class="recipe-card__title">{{post.name}}</h2>
<div class="recipe-card__details-wrapper">
<p class="recipe-card__excerpt">{{post.body}}</p>
<a v-bind:href="post.url" class="recipe-card__read-more">Read more <i class="fa fa-arrow-right"></i></a>
</div>
</div>
</a>
</div>
</div>
</div>
</div>
</div>
tab
<main id="tab">
<header>
<nav>
<ul>
<div id="arrow">
<span></span>
<span></span>
<span></span>
</div>
<li v-for="(tab, tabName) in tabs" :key="tabName">
<button class="tab" #click="setTabActive(tabName)" :class="{'active': tabName === activeTab}">
<span class="tab-copy">{{ tabName }}</span>
<span class="tab-background">
</span>
</button>
</li>
</ul>
</nav>
</header>
<article>
<div class="container">
<transition name="fade" mode="out-in" appear :duration="500">
<tab-content v-for="(tabContent, t) in tabs" :data="tabContent" :key="'content'+t" v-if="t === activeTab" inline-template>
<div class="content-wrapper">
<div class="recipes" v-for="(recipe, i) in data.recipes" :key="i">
<a v-bind:href="recipe.url" class="recipe-card__card-link"></a>
<img :src="recipe.image" alt="" class="recipe-card__image">
<div class="recipe-card__text-wrapper">
<h2 class="recipe-card__title">{{recipe.name}}</h2>
<div class="recipe-card__details-wrapper">
<p class="recipe-card__excerpt">{{recipe.body}}</p>
<a v-bind:href="recipe.url" class="recipe-card__read-more">Read more <i class="fa fa-arrow-right"></i></a>
</div>
</div>
</div>
</div>
</tab-content>
</transition>
</div>
</article>
</main>
I tried to combine instances search and tab into just one instance tab, but it always gives me a Vue warning:
[Vue warn]: Property or method "searchQuery" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
Also, I feel like I can have just the wrong approach to this problem I am trying to solve. Please, any suggestions are welcome. I am stuck.

Tailwind & Vue-Router - dropdown menu doesn't close when clicking on menu items

I'm using the following dropdown menu in tailwind & vue-router https://codepen.io/huphtur/pen/ordMeN.
I need to use <router-link to="/"> inside the dropdown menu, but I found out that doing that, once you click on a menu link the dropdown doesn't close as it should do. Using normal <a> elements the dropdown closes like it should.
This is the code:
dropdown element comp
<div v-if="isOpen == false" class="dropdown inline-block relative sm:ml-20">
<button class="text-gray-700 font-normal py-2 px-4 rounded inline-flex items-center">
<span class="mr-1 cursor-default">Vita</span>
</button>
<ul class="w-32 dropdown-menu absolute hidden text-gray-700 pt-1 shadow-lg z-10">
<li > <router-link to="/biografia"> <a class="rounded-t bg-white hover:bg-gray-200 py-2 px-4 block whitespace-no-wrap text-sm"> Bio </a> </router-link> </li>
<li > <router-link to="/tour"> <a class="bg-white hover:bg-gray-200 py-2 px-4 block whitespace-no-wrap text-sm"> Tour </a> </router-link> </li>
<li > <router-link to="/foto"> <a class="bg-white hover:bg-gray-200 py-2 px-4 block whitespace-no-wrap text-sm"> Foto </a> </router-link> </li>
<li > <router-link to="/video"> <a class="rounded-b bg-white hover:bg-gray-200 py-2 px-4 block whitespace-no-wrap text-sm"> Video </a> </router-link> </li>
</ul>
</div>
}
css style in the comp
.dropdown:hover .dropdown-menu {
display: block;
}
How can I implement this functionality using <router-link to="/">?
Thanks!
router-link exposes a slot for you to enable customizations. I have used this in the past to apply styling to an outer li element when the router-link was active, but you can use the same approach for closing your dropdown as well.
<router-link
to="/"
v-slot="{ href, route, navigate, isActive, isExactActive }"
>
<a
:href="href"
#click="isOpen = false; navigate($event)"
>
Homepage
</a>
</router-link>
Source

vuejs dynamically bind text in class attribute

I am trying to dynamically change the text of an fa icon depending on what an object in the data (iterating in for loop) contains:
<ul id="example-1">
<li v-for="item in cards" :key="item.id">
<i class="fa fa-cc-{{item.brand}} fa-2x" style="color:black"></i> {{ item.brand }}
</li>
</ul>
This doesn't seem to work
You can make class as dynamic attribute as below
:class="`fa fa-cc-${item.brand}`"
I suppose that would be the correct way of doing it (string concatenation):
<ul id="example-1">
<li v-for="item in cards" :key="item.id">
<i :class="'fa fa-cc-'+item.brand+' fa-2x'" style="color:black"></i> {{ item.brand }}
</li>
</ul>
or different syntax with ECMAScript 6 template literals:
<ul id="example-1">
<li v-for="item in cards" :key="item.id">
<i :class="`fa fa-cc-${item.brand} fa-2x`" style="color:black"></i> {{ item.brand }}
</li>
</ul>
You can see that I used dynamic class binding.

All tab elements are active when using uk-tab with Vue.js

I am using getuikit's tab component ( https://getuikit.com/docs/tab ) in my Vue-App:
Now I see that every tab is active, if I use v-for to iterate through an array.
<ul class=" uk-tab-left" uk-tab>
<li v-for="test in tests" id="test">{{ test }}</li>
</ul>
In my codepen example you can see, that the class uk-active is always inserted automatically:
https://codepen.io/spqrinc/pen/Ydzbez
Is there a possibility to change this behavior?
You can add a empty li element before the loop to make sure the active class will not be added to the others.
Don't forget to add a key to the loop and bind the id.
<div id="app">
<div>
<div uk-grid>
<div class="uk-width-1-4#m">
<ul class=" uk-tab-left" data-uk-tab>
<li></li>
<li v-for="test in tests" :key="test" :id="test">
{{ test }}
</li>
</ul>
</div>
</div>
</div>
</div>
<div id="app">
<div>
<div uk-grid>
<div class="uk-width-1-4#m">
<ul class=" uk-tab-left" data-uk-tab>
<template v-for="test in tests">
<li :key="test" :id="test">
{{ test }}
</li>
</template>
</ul>
</div>
</div>
</div>
</div>

v-for and v-if not working together in vue.js

A form is used to submit text and two options which tell vue which column to display the text in. When the col2 radio button is checked the submitted text should display in column 2. This is not happening, on column 1 text is displaying.
I have two radio buttons which should pass the value 'one' or 'two' to a newInfo.option On submnit a method pushed the form data to the array 'info'.
<input type="radio" id="col1" value="one" v-model="newInfo.col">
<input type="radio" id="col2" value="two" v-model="newInfo.col">
This data is being pushed to the array 'info' correctly and I can iterate through it. I know this is working because I can iterate through the array, an console.log all the data in it. All the submitted form data is there.
Next I iterate through this array twice in the template. Once for info.col==="one" and the other iteration should only display when info.col==="two". I am using a v-for and v-if together, which the vue.js documentation says is ok to do,
https://v2.vuejs.org/v2/guide/conditional.html#v-if-with-v-for
<div class="row">
<div class="col-md-6">
<ol>
<li v-for="item in info" v-if="item.col==='one'">
text: {{ item.text }}, col: {{ item.col }}
</li>
</ol>
</div>
<div class="col-md-6">
<ol>
<li v-for="item in info" v-if="!item.col==='two'">
text: {{ item.text }}, col: {{ item.col }}
</li>
</ol>
</div>
</div>
The full vue.js code is on github here
And it is running on gh-pages here
Why don't use the power of Computed Properties ?
computed: {
infoOne: function () {
return this.info.filter(i => i.col === 'one')
},
infoTwo: function () {
return this.info.filter(i => i.col === 'two')
}
}
Then on each list just iterate over its respective property without the need to check. Example
<ol>
<li v-for="item in infoOne">{{item}}</li>
</ol>
Here the working fiddle
From Vue docs:
When they exist on the same node, v-if has a higher priority than
v-for. That means the v-if condition will not have access to variables
from the scope of the v-for:
<!--
This will throw an error because property "todo"
is not defined on instance.
-->
<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo.name }}
</li>
This can be fixed by moving v-for to a wrapping tag (which
is also more explicit):
<template v-for="todo in todos">
<li v-if="!todo.isComplete">
{{ todo.name }}
</li>
</template>
If you don't mind your view continuing in the html with "display:none" you can use v-show together with v-for without any problems.
<div class="row">
<div class="col-md-6">
<ol>
<li v-for="item in info">
<template v-if="item.col==='one'">
text: {{ item.text }}, col: {{ item.col }}
<template>
</li>
</ol>
</div>
<div class="col-md-6">
<ol>
<li v-for="item in info">
<template v-if="!item.col==='two'">
text: {{ item.text }}, col: {{ item.col }}
<template>
</li>
</ol>
</div>
</div>
You could also use JavaScript in your template to filter the array elements of the v-for. Instead of v-for="item in infos" you could narrow down the info-array to v-for="item in infos.filter(info => info.col === 'one')".
I renamed your info-array to infos to improve readability of my suggestion because of the usage of info in the callbacks.
<div class="row">
<div class="col-md-6">
<ol>
<li v-for="item in infos.filter(info => info.col === 'one')">
text: {{ item.text }}, col: {{ item.col }}
</li>
</ol>
</div>
<div class="col-md-6">
<ol>
<li v-for="item in infos.filter(info => info.col === 'two')">
text: {{ item.text }}, col: {{ item.col }}
</li>
</ol>
</div>
</div>
If for some reason, filtering the list is not an option, you can convert the element with both v-for and v-if in to a component and move the v-if in to the component.
Original Example
Original Loop
<li v-for="item in info" v-if="item.col==='one'">
text: {{ item.text }}, col: {{ item.col }}
</li>
Suggested Refactor
Refactored Loop
<custom-li v-for="item in info" :visible="item.col==='one'">
text: {{ item.text }}, col: {{ item.col }}
</custom-li>
New Component
Vue.component('custom-li', {
props: ['visible'],
template: '<li v-if="visible"><slot/></li>'
})
Remove ! from second if v-if="item.col==='two'"
better you can do this way (to iterate only once):
<div class="row" v-for="item in info">
<div class="col-md-6">
<ol>
<li v-if="item.col==='one'">
text: {{ item.text }}, col: {{ item.col }}
</li>
</ol>
</div>
<div class="col-md-6">
<ol>
<li v-if="item.col==='two'">
text: {{ item.text }}, col: {{ item.col }}
</li>
</ol>
</div>
</div>
Your second check is !item.col==='two' and would only display if it does not equal 'two'.
EDIT: The ! not operator is likely binding more closely than === so that will always return false. Add brackets to control the order of application. I say likely because it may be a bit of Vue magic that I'm not familar with, rather than a pure JavaScript expression.
I think you want to remove that exclamation mark. Or to make it !(item.col==='one') to display for any value other than 'one'.
Computed
In most cases a computed property is indeed the best way to do it like DobleL said, but in my own case I'm having a v-for within another v-for so then the computed doesn't make sense for the second v-for.
V-show
So instead of using v-if which has a higher priority than v-for (as mentioned by Mithsew), an alternative would be to use v-show which doesn't have that higher priority.
It basically does the same, and works in your case.
No wrapper
Using v-show avoids having to add a useless wrapper element as I've seen in some answers, which in my case was a requirement to avoid messing up CSS selectors and html structure.
Downside of v-show
The only downside of using v-show is that the element will still be added in your HTML, which in my own case still messes up CSS :first selectors for example. So I personally actually went for the .filter() solution mentioned by inTheFlow. But in most basic cases, you can definitely use v-show to solve this problem.
<div class="row">
<div class="col-md-6">
<ol>
<li v-for="item in info" v-show="item.col==='one'">
text: {{ item.text }}, col: {{ item.col }}
</li>
</ol>
</div>
<div class="col-md-6">
<ol>
<li v-for="item in info" v-show="item.col!=='two'">
text: {{ item.text }}, col: {{ item.col }}
</li>
</ol>
</div>
</div>
If you are curious why the hell I'm using a v-for within another v-for, here's a stripped-down version of my use case:
It's a list of conversations (which is a computed property), then displaying the avatars of all the participants within each conversation, except for the avatar of the user who is currently viewing it.
<a v-for="convo in filteredConversations" class="card">
<div class="card-body">
<div class="row">
<div class="col-auto">
<div class="avatar-group">
<div class="avatar" v-for="participant in convo.participants.filter(info => !thatsMe(info))">
<img :src="userAvatarUrl(participant)" :alt="participant.name" class="avatar-img">
</div>
</div>
</div> ...