Change Icon when nuxt-link is active? - vue.js

I have 3 links in navbar and each link contains 2 icons (lets say icon-1 and icon-2), but i want to show only 1 icon at a time.
icon-1 when nuxt-link is active and hovered
icon-2 when nuxt-link is inactive
this is my code
<ul>
<li class="nav-list-item" v-for="(link, i) in navLinks" :key="i">
<nuxt-link :to="link.path">
<img :src="link.inactive"> //this is icon-1
<img :src="link.active"> //this is icon-2
<span class="nav-title">{{link.title}}</span>
</nuxt-link>
</li>
</ul>
this is my script
data() {
return {
navLinks: [
{
title: 'Link 1',
path: '/' ,
inactive: './Icons/home-icon-1.svg',
active: './Icons/home-icon-2.svg'
},
{
title: Link 2',
path: '/about' ,
inactive: './Icons/about-icon-1.svg',
active: './Icons/about-icon-2.svg'
},
{
title: 'link 3',
path: '/contact' ,
inactive: './Icons/contact-icon-1.svg',
active: './Icons/contact-icon-2.svg'
},
]
}
},

First of all you need to set active state in every link object:
{
title: 'Link 1',
path: '/',
inactive: './Icons/home-icon-1.svg',
active: './Icons/home-icon-2.svg',
isActive: false,
},
then you must set two native event listeners on nuxt link and logic to show img:
<nuxt-link
:to="link.path"
#mouseover.native="link.isActive = true"
#mouseleave.native="link.isActive = false"
>
<img v-if="link.isActive"
:src="link.inactive">
<img v-else
:src="link.active">
<span class="nav-title">{{ link.title }}</span>
</nuxt-link>

Related

Children of NuxtLink rendered twice (hydration error?)

My hunch is that there is some hydration mismatch where the FontAwesomeIcon was not rendered on the server (only the span) and then on the client both child nodes of the NuxtLink were rendered (the svg and the span), prompting Nuxt to render the span twice.
The console does not return an error, though.
Any thoughts on how to debug this?
This is the Vue component:
<template>
<ul v-if="routes.length > 0" class="col-span-2 flex flex-col">
<li v-for="(item, i) in routes" :key="item.name">
<NuxtLink :to="item.path" target="_blank">
<FontAwesomeIcon :icon="item.icon" class="mr-3" fixed-width />
<span>{{ item.title }}</span>
</NuxtLink>
</li>
</ul>
</template>
<script lang="ts">
export default defineComponent({
props: {
links: {
type: Array,
default: () => ["instagram", "facebook", "email"],
},
},
computed: {
routes() {
return [
{
name: "instagram",
path: "https://www.instagram.com/insta.name/",
title: "Instagram",
icon: ["fab", "instagram"],
},
{
name: "facebook",
path: "https://www.facebook.com/fb.name",
title: "Facebook",
icon: ["fab", "facebook"],
},
{
name: "email",
path: "mailto:hello#example.com",
title: "Email",
icon: ["fas", "envelope"],
},
].filter((e) => this.links.includes(e.name));
},
},
});
</script>

Vue.js v3 passing data through router link

I have an object in Portfolio.vue like this:
data() {
return {
portfolio: {
front: [
{ description: '1. project ', src: require("../assets/sample.jpg"), slug: 'first'},
{ description: '2. project', src: require("../assets/sample.jpg"), slug: 'second' },
{ description: '3. project', src: require("../assets/sample.jpg"), slug: 'third' },
]
}
}
}
Portfolio.vue:
<div class="">
<div v-for="(data,index) in portfolio.front" :key="index">
<router-link :to="'/portfolio/'+data.slug"> <div class="element" :data-description="data.description">
<img :src="data.src " alt="">
</div>
</router-link>
</div>
</div>
PortfolioProduct.vue
<template>
<div>
<p>I want to take data to here. In here, i have to reach the data like this: portfolio.front.description</p>
</div>
</template>
<script>
export default {
props: {
},
}
</script>
My routes:
const routes = [
{
path: "/portfolio",
name: "Portfolio",
component: Portfolio,
},
{
path: "/portfolio/:id",
name: "PortfolioProduct",
component: PortfolioProduct,
props: true,
},
];
I want to take data from Portfolio.vue to PortfolioProduct.vue , i couldn't solve. I'm using vue js3 , if you help me i will be glad. Thank you
You could probably use the template literal, changing the router-link would be like so:
<router-link :to="`/portfolio/${data.slug}`"> <div class="element" :data-description="data.description">

How to make a recursive menu using Quasar QexpansionItem

I want create a component that it can to scale with a nested object structure using the QExpansionItem from Quasar Framework.
I made a recursive component to try achieve this but doesn't shows like i hope. The items are repeated in a wrong way and I don't know why.
I am using Quasar V1.0.5, the component that i used QexpansionItem
Here the menu object
[
{
name: '1',
icon: 'settings',
permission: 'configuration',
description: '1',
url: '',
children: [
{
name: '1.1',
permission: 'configuration',
url: '/insuranceTypes',
icon: 'add',
description: '1.1'
},
{
name: '1.2',
permission: 'configuration',
url: '/insuranceTypes2',
icon: 'phone',
description: '1.2'
}
]
}, {
name: '2',
icon: 'person',
permission: 'configuration',
url: 'contacts',
description: '2'
}
]
MenuComponent.vue where i call side-tree-menu component
<q-list
bordered
class="rounded-borders q-pt-md"
>
<side-tree-menu :menu="menu"></side-tree-menu>
</q-list>
SideTreeMenuComponent.vue
<template>
<div>
<q-expansion-item
expand-separator
:icon="item.icon"
:label="item.name"
:caption="item.description"
header-class="text-primary"
:key="item.name"
:to="item.url"
v-for="(item) in menu"
>
<template>
<side
v-for="(subitem) in item.children"
:key="subitem.name"
:menu="item.children"
>
</side>
</template>
</q-expansion-item>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'side',
props: ['menu', 'children'],
data () {
return {
isOpen: false,
algo: 0
}
},
mounted () {
console.log('menu', this.menu)
},
computed: {
...mapGetters('generals', ['can'])
}
}
</script>
The elements 1.1 and 1.2 are repeated and I don't know fix it
I got stuck at the same problem and did not find any solution online. I managed to get it working with the below approach. This could be helpful for someone in the future :)
I am adding here the 2 most important code files that will get this working. Rest of my setup is nothing more than what is created by the quasar create [project-name] CLI command.
When you create the project with the above command, you get the MainLayout.vue and EssentialLink.vue file. I have modified those to achieve the required result.
**My MainLayout.vue file - the template **
EssentialLink below is the component that renders the menu recursively using q-expansion-item inside the drawer on the main layout page.
<template>
<q-layout view="hHh Lpr lFf">
<q-header elevated>
<q-toolbar>
<q-btn flat dense round icon="menu" aria-label="Menu"
#click="leftDrawerOpen = !leftDrawerOpen" />
<q-toolbar-title>
{{appTitle}}
</q-toolbar-title>
<div>Release {{ appVersion }}</div>
</q-toolbar>
</q-header>
<q-drawer
v-model="leftDrawerOpen" show-if-above bordered
content-class="bg-grey-1">
<q-list>
<q-item-label
header
class="text-grey-8">
Essential Links
</q-item-label>
<EssentialLink
v-for="link in essentialLinks"
:key="link.title"
v-bind="link">
</EssentialLink>
</q-list>
</q-drawer>
<q-page-container>
<router-view />
</q-page-container>
</q-layout>
</template>
script section of MainLayout.vue file. Key properties to note - children and level.
<script>
import EssentialLink from 'components/EssentialLink.vue'
export default {
name: 'MainLayout',
components: {
EssentialLink
},
data () {
return {
appTitle: 'Project Name',appVersion: 'v0.1',leftDrawerOpen: false,
essentialLinks: [
{
title: 'Search', caption: 'quasar.dev', icon: 'school',
link: 'https://quasar.dev',
level: 0,
children: [{
title: 'Documents', caption: 'quasar.dev',icon: 'school',
link: 'https://quasar.dev',
level: 1,
children: [{
title: 'Search (level 3)',
caption: 'quasar.dev',
icon: 'school',
link: 'https://quasar.dev',
level: 2,
children: []
}]
}]
},
{
title: 'Github',caption: 'github.com/quasarframework',
icon: 'code',link: 'https://github.com/quasarframework',
level: 0,
children: [{
title: 'Github Level 2',caption: 'quasar.dev',icon: 'school',
link: 'https://quasar.dev',level: 1,
children: []
}]
},
{
title: 'Forum',caption: 'forum.quasar.dev',
icon: 'record_voice_over',link: 'https://forum.quasar.dev',
level: 0,
children: [{
title: 'Forum Level 2',caption: 'quasar.dev',icon: 'school',
link: 'https://quasar.dev',
level: 1,
children: []
}]
}
]
}
}
}
</script>
Finally the EssentialLink.vue component
The code below recursively calls itself when it encounters more than 1 item in its children property. The level property is used to indent the menus as you drill down.
<template>
<div>
<div v-if="children.length == 0">
<q-item clickable v-ripple :inset-level="level">
<q-item-section>{{title}}</q-item-section>
</q-item>
</div>
<div v-else>
<div v-if="children.length > 0">
<!-- {{children}} -->
<q-expansion-item
expand-separator
icon="mail"
:label="title"
:caption="caption"
:header-inset-level="level"
default-closed>
<EssentialLink
v-for="child in children"
:key="child"
v-bind="child">
</EssentialLink>
</q-expansion-item>
</div>
<div v-else>
<q-item clickable v-ripple :inset-level="level">
<q-item-section>{{title}}</q-item-section>
</q-item>
</div>
</div>
</div>
</template>
*script section of the EssentialLink.vue component
<script>
export default {
name: 'EssentialLink',
props: {
title: {
type: String,
required: true
},
caption: {
type: String,
default: ''
},
link: {
type: String,
default: '#'
},
icon: {
type: String,
default: ''
},
level: {
type: String,
default: ''
},
children: []
}
}
</script>
Final output looks like this (image)

VueJS Custom dropdown buttons not working independently of each other

So I have a button dropdown which is working as expected but I have a bug where I can't reuse the same component as they both dont work independently of each other but instead when one is clicked the other changes too.
Please find a JSFiddle below and my code.
Thanks
<div id="app">
<div v-on:click="menuVisible = !menuVisible" class="dropdownBtn">
<div v-bind:class="{ active : menuVisible }"class="dropdownBtn__title">
<span v-if="!btnTitle">exploring</span>
<span v-else>{{ btnTitle }}</span>
</div>
<div v-show="menuVisible" class="dropdownBtn__content">
<ul v-for="reason in reasons">
<li v-on:click="updateTitle(reason)">{{ reason.title }}</li>
</ul>
</div>
</div>
<div v-on:click="menuVisible = !menuVisible" class="dropdownBtn">
<div v-bind:class="{ active : menuVisible }"class="dropdownBtn__title">
<span v-if="!btnTitle">exploring</span>
<span v-else>{{ btnTitle }}</span>
</div>
<div v-show="menuVisible" class="dropdownBtn__content">
<ul v-for="reason in reasons">
<li v-on:click="updateTitle(reason)">{{ reason.title }}</li>
</ul>
</div>
</div>
</div>
new Vue({
el: '#app',
data() {
return {
menuVisible: false,
btnTitle: false,
reasons: [{
title: 'family fun',
value: 1
},
{
title: 'relaxing',
value: 2
},
{
title: 'dining',
value: 3
},
{
title: 'meetings & events',
value: 4
},
{
title: 'what\'s on',
value: 5
},
{
title: 'gold',
value: 6
}]
}
},
methods: {
updateTitle($event) {
this.btnTitle = $event.title
}
}
})
So, for one, you haven't actually made a reusable component for the dropdown. You need to define that using Vue.component. Then you can use the tag for the custom component in the root template.
Secondly, if you are binding to the same data, then that is what will be reflected in both templates. You'll still need to pass separate data to the two separate dropdown components.
Vue.component('dropdown', {
template: `
<div v-on:click="menuVisible = !menuVisible" class="dropdownBtn">
<div v-bind:class="{ active : menuVisible }"class="dropdownBtn__title">
<span v-if="!btnTitle">exploring</span>
<span v-else>{{ btnTitle }}</span>
</div>
<div v-show="menuVisible" class="dropdownBtn__content">
<ul v-for="reason in reasons">
<li v-on:click="updateTitle(reason)">{{ reason.title }}</li>
</ul>
</div>
</div>
`,
props: ['menuVisible', 'btnTitle', 'reasons'],
methods: {
updateTitle($event) {
this.btnTitle = $event.title
}
}
})
new Vue({
el: '#app',
data() {
return {
fooReasons: [
{ title: 'family fun', value: 1 },
{ title: 'relaxing', value: 2 },
{ title: 'dining', value: 3 },
{ title: 'meetings & events', value: 4 },
{ title: 'what\'s on', value: 5 },
{ title: 'gold', value: 6 }
],
barReasons: [
{ title: 'family bar', value: 1 },
{ title: 'bar relaxing', value: 2 },
{ title: 'bar dining', value: 3 },
{ title: 'meetings & bars', value: 4 },
{ title: 'bar\'s on', value: 5 },
{ title: 'gold bar', value: 6 }
]
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.4/vue.min.js"></script>
<div id="app">
<dropdown :menu-visible="false" :btn-title="false" :reasons="fooReasons"></dropdown>
<dropdown :menu-visible="false" :btn-title="false" :reasons="barReasons"></dropdown>
</div>

Accessing siblings elements in a v-for

New to Vuejs 2, need a bit of help to understand how this would be done.
So, I have a template that looks like the following:
<template>
<div class="wrapper">
<div class="item" v-for="item in items">
<div class="item">
<div class="title">{{ item.title }}</div>
<div class="action">
<a href="#" #click.prevent="editSettings(item, $event)">
Click Me
</a>
</div>
</div>
<div v-if="showSettings" class="settings">
<!-- Settings component for this item. -->
<settings-panel item="item"></settings-panel>
</div>
</div>
</div>
</template>
The scripts looks like this:
export default {
data() {
return {
items: [
// This data is from an API usually and only
// shown here for example purposes
{ id: 1, title: 'Title 1' },
{ id: 2, title: 'Title 2' },
{ id: 3, title: 'Title 3' },
{ id: 4, title: 'Title 4' }
],
showSettings: false
};
},
methods: {
editSettings(item, event) {
// Only show the clicked items settings, not all
this.showSettings = !showSettings;
}
}
}
What is the easiest and cleanest approach to only showing the settings panel for the clicked item? Right now, if I click the Click Me settings, all settings open up. The data listed in the code is only there for example, but is coming from an API so I don't have much to do with the manipulation of that data.
Can anyone help lead me in the right direction?
Instead of using showSettings add a selectedItem.
export default {
data() {
return {
items: [
// This data is from an API usually and only
// shown here for example purposes
{ id: 1, title: 'Title 1' },
{ id: 2, title: 'Title 2' },
{ id: 3, title: 'Title 3' },
{ id: 4, title: 'Title 4' }
],
selectedItem: null
};
},
methods:{
selectItem(item){
if (this.selectedItem === item) this.selectedItem = null
else this.selectedItem = item
}
}
}
And modify your template to set the selectedItem when the item is clicked and show the settings only if the selectedItem is the current item.
<template>
<div class="wrapper">
<div class="item" v-for="item in items">
<div class="item">
<div class="title">{{ item.title }}</div>
<div class="action">
<a href="#" #click.prevent="selectItem(item)">
Click Me
</a>
</div>
</div>
<div v-if="selectedItem === item" class="settings">
<!-- Settings component for this item. -->
<settings-panel item="item"></settings-panel>
</div>
</div>
</div>
</template>
The above will show the settings for the clicked item if the currently selected item is not the item that was clicked. If the currently selected item is the one that was clicked, it will hide the settings.