Nuxt Menu links from API...How can i detect if its a Nuxt link :to or :href? - api

Im building my main menu from an API :
<b-navbar-nav>
<div v-for="result in resultsmenu" :key="result.id">
<span class="hoverlink">
<nuxt-link :to="result.slug">{{result.title}}</nuxt-link>
</span>
</div>
</b-navbar-nav>
Everythings works well but one problem is in the menu from the API ..One link is external with an "href" like : https://ww.instagram.com so nuxt-link process it like a internal route i end up with :
http://localhost:3000/https://ww.instagram.com
I was wondering if there is a way to tell Nuxt that if a link is an "href" type: "link" to handle it like an external link instead of an nuxt-link to: ?
Thank you

One way to achieve this would be to use a computed property.
Assuming your resultsmenu data looks something like...
resultsmenu: [
{slug: 'http://www.website.com', title: 'link to website.com'},
{slug: '/test', title: 'link to a website.com'},
{slug: '/yay', title: 'link to b website.com'},
{slug: 'http://www.website.com', title: 'link to c website.com'}
]
You can do...
computed: {
menuLinks() {
let links = []
this.resultsmenu.forEach(link => {
let menuItem = {}
menuItem.isHttp = !!link.slug.includes('http');
menuItem.target = link.slug
menuItem.title = link.title
links.push(menuItem)
})
return links
}
}
Then in your template:
<div v-for="(result, index) in menuLinks" :key="index">
<span>
<nuxt-link v-if="!result.isHttp" :to="result.target">{{result.title}}</nuxt-link>
<a v-else :href="result.target">{{result.title}}</a>
</span>
</div>

Related

How can I modify template variables declared with :set?

I have the following code (vue v3.2.33)
<template v-for="route of routes" :key="route.name">
<li :set="open = false">
<div class="collapse"
#click="open = !open"
:class="[open ? 'collapse-is-open' : '']">
</div>
</li>
</template>
Basically each li has a collapse and opens with the class collapse-is-open.
I tried to use a variable with the :set attribute and modify that variable but it doesn't seem to work.
If that can't be working, what would be the other way of doing this ? An object based on the value I set in :key that keeps track of all the states?
:set is syntax for the v-bind directive. It binds a property/attribute on the element, but there is no such set property for <li>. I think you're trying to create an ad-hoc variable named open for each <li>, but that feature doesn't exist in Vue (perhaps you're thinking of petite-vue's v-scope).
One solution is to create a separate data structure that contains the open-state of each <li>, and use that to update the class binding:
export default {
data() {
return {
routes: [⋯],
open: {} // stores open-state of each route item
}
}
}
<template v-for="route of routes" :key="route.name">
<li>
<div class="collapse"
#click="open[route.name] = !open[route.name]"
:class="[open[route.name] ? 'collapse-is-open' : '']">
</div>
</li>
</template>
demo 1
Alternatively, you could add that property to each array item in routes:
export default {
data() {
return {
routes: [
{
name: 'Route 1',
open: false,
},
{
name: 'Route 2',
open: false,
},
{
name: 'Route 3',
open: false,
},
],
}
},
}
<template v-for="route of routes" :key="route.name">
<li>
<div class="collapse"
#click="route.open = !route.open"
:class="[route.open ? 'collapse-is-open' : '']">
</div>
</li>
</template>
demo 2

VueJS - How to highlight the sidebar button even when displaying the router-view content

Hello I am highlighting the button which ever is clicked in the sidebar.
Here i am looking to highlight the button even if the router-view content is displayed. But currently below image is displayed if i am in router view content.
I have a component called Blog.vue, and the Blog button is placed in the sidebar. This Blog.vue contains several cards in it and each card has a badge, so when clicked any of the badge it displays that respective content here route changes.
Initially when the page loads or when clicked on Blog button in sidebar it is highlighted. But as soon as the user clicks any of the badge in any of the card then the Blog button is not highlighted shown in second image.
Here is the VerticalNavMenu.vue
<vs-sidebar
class="v-nav-menu items-no-padding"
v-model="isVerticalNavMenuActive"
ref="verticalNavMenu"
default-index="-1"
:click-not-close="clickNotClose"
:reduce-not-rebound="reduceNotRebound"
:parent="parent"
:hiddenBackground="clickNotClose"
:reduce="reduce"
v-hammer:swipe="onMenuSwipe">
<div #mouseenter="mouseEnter" #mouseleave="mouseLeave">
<div #mouseenter="mouseEnter" #mouseleave="mouseLeave">
<div class="shadow-bottom" v-show="showShadowBottom" />
<!-- Menu Items -->
<component :is="scrollbarTag" ref="verticalNavMenuPs" class="scroll-area-v-nav-menu pt-2"
:settings="settings" #ps-scroll-y="psSectionScroll" #scroll="psSectionScroll" :key="$vs.rtl">
<template v-for="(item, index) in menuItemsUpdated">
<!-- Group Header -->
<span v-if="item.header && !verticalNavMenuItemsMin" class="navigation-header truncate"
:key="`header-${index}`">
{{ $t(item.i18n) || item.header }}
</span>
<!-- /Group Header -->
<template v-else-if="!item.header">
<!-- Nav-Item -->
<v-nav-menu-item
...
</v-nav-menu-item>
<!-- Nav-Group -->
<template v-else>
<v-nav-menu-group
...
/>
</template>
<!-- /Nav-Group -->
</template>
</template>
</component>
<!-- /Menu Items -->
</div>
..
showShadowBottom: false
..
psSectionScroll () {
const scroll_el = this.$refs.verticalNavMenuPs.$el || this.$refs.verticalNavMenuPs
this.showShadowBottom = scroll_el.scrollTop > 0
},
Here is the navMenuItem.js:
{
header: 'Apps',
icon: 'PackageIcon',
i18n: 'Apps',
items: [
{
url: '/apps/blog-complete',
name: 'Blog',
slug: 'blog-complete',
icon: 'CpuIcon',
i18n: 'Blog'
},
{
url: '/apps/info-tech',
name: 'Info Tech',
slug: 'info-tech',
icon: 'Icon'
i18n: 'Info Tech'
},
...
]
Here is the router.js
{
path: '/apps/blog-complete',
name: 'app-blog-complete',
component: () => import('#/views/apps/Blog.vue'),
},
{
path: '/apps/inner-blog/:apiVal_id', // Here for these router view contents i want to show the button highlighted.
name: 'Inner',
component: () => import('./views/apps/InnerBlog.vue'),
},
Here is the Blog.vue:
...
<div class="right">
<b-badge #click="$router.push({name: 'Inner', params: {id: apiVal.id.toString() }})
.catch(err => {})">Check Deep</b-badge>
</div>
...
Here is the InnerBlog.vue:
...
<div class="right">
Welcome to my route :)
</div>
...
Please do help me to highlight the Blog button in the sidebar even when it displays the router-view content.
Easiest way to do this would be to utilize .router-link-active class which is added to the <router-link> component when its target route is matched, but since you are not using that it can be done using this trick:
First, add this to your methods:
methods: {
subIsActive(input) {
const paths = Array.isArray(input) ? input : [input]
return paths.some(path => {
return this.$route.path.indexOf(path) === 0 // current path starts with this path string
})
}
}
This function checks if the current path starts with whatever you passed to the function.
Second, you need to add class binding to your span element which is rendering group headers like this:
<span v-if="item.header && !verticalNavMenuItemsMin" class="navigation-header truncate"
:key="`header-${index}`" :class="{'is-active': subIsActive(item.url)}">
{{ $t(item.i18n) || item.header }}
</span>
Third, you need to change your routes, every badge that is available when you click on the Blog button should have a route that starts with /apps/blog-complete, for example
{
path: '/apps/blog-complete/inner-blog/:apiVal_id', // Here for these router view contents i want to show the button highlighted.
name: 'Inner',
component: () => import('./views/apps/InnerBlog.vue'),
},
Fourth, you add class is-active to your style section:
.is-active{
background-color: red;
}

Vues - How to use v-for and scoped slots in render function

I have a component that renders navigation links. It's based on scopedSlots feature. The component is working fine, but I'd like to improve it. This is how it's currently used:
<horizontal-navigation :items="items" #default="{ item }">
<navigation-item :item="item"></navigation-item>
</horizontal-navigation>
Array of items looks like this:
[
{ path: '/about', title: 'About' },
{ path: '/', title: 'Home' }
]
Here's HorizontalNavigation component template:
<template>
<div class="horizontal-navigation">
<slot v-for="(item, index) in items" :key="index" :item="item"></slot>
</div>
</template> -->
Here's NavigationItem component template
<template>
<router-link class="navigation-item" :path="item.path">{{ item.title }}</router-link>
</template>
I'm trying to replace HorizontalNavigation component's template with a render function, because I want to make this component to render links with a default styling when no slot content is provided.
Here's where I'm stuck:
render () {
const options = {
class: 'horizontal-navigation'
}
let children = []
if (this.$slots.default) {
children = this.$slots.default({ items: this.items }) // NEED TO ITERATE ITEMS TO CREATE INSTANCES OF A COMPONENT PASSED TO DEFAULT SLOT
}
return h('div', options, children)
}
The closest thing I found in the docs is this:
render() {
if (this.items.length) {
return Vue.h('ul', this.items.map((item) => {
return Vue.h('li', item.name)
}))
} else {
return Vue.h('p', 'No items found.')
}
}
Any advice?
In the template try to use conditional rendering to render the slot or the fallback content :
<div class="horizontal-navigation">
<template v-for="(item, index) in items" >
<template v-if="$slots.default">
<slot :item="item"></slot>
</template>
<template v-else>
<router-link class="navigation-item" :path="item.path">{{ item.title }}</router-link>
</template>
</template>
</div>

Using Slots or slot-scopes in v-for loops to access properties?

I'm having a difficult time understanding slots for some reason and why they should even be used. The only reason I can think of that would be nice for usuage is if we can reference specific properties within a v-for loop of an element and output different templates quicker perhaps...
So, am thinking, and possibly I could be wrong in thinking this, but if I have a variable like so:
<script>
const items: [
{
label: 'My Label',
url: '#',
headerTitle: 'My Header Title'
},
{
label: 'My Label 2',
url: 'https://www.myurl.com',
headerTitle: 'My Header Title 2'
},
{
label: 'My Label 3',
url: 'https://www.myurl3.com'
}
]
export default {
data () {
return {
items: items
}
}
}
</script>
And than in the template, possibly this:
<template>
<div v-for="(item, index) in items" :key="item.id">
<template slot-scope="headerTitle">
<h1>{{ item.headerTitle }}</h1>
</template>
<template slot-scope="label">
<div class="mylabel">
{{ item.label }}
</div>
</template>
<template slot-scope="url">
<a :href="item.url">{{ item.label }}</a>
</template>
</div>
</template>
I don't know if this makes sense or not, but basically using the property as a slot-scope and than for everytime that property is defined, it will output something. But this doesn't work properly. Is this not what slot-scopes are for within component v-for loops? Is this not how to use these properties of an array of objects?
This kinda makes sense to me. Anyways to do it like this perhaps?

Conditional link behavior in VueJS

Couldn't find a proper name for the title, will be glad if someone figures out a better name.
I have a component which represents a product card. The whole component is wrapped in <router-link> which leads to product page.
However I have another case, when I do not need the component to lead to a product page, but instead I need to do some other action.
The only solution I found is to pass a callback function as a prop, and based on this, do something like:
<router-link v-if="!onClickCallback">
... here goes the whole component template ...
</router-link>
<div v-if="onClickCallback" #click="onClickCallback">
... here again goes the whole component template ...
</div>
How can I do this without copy-pasting the whole component? I tried to do this (real code sample):
<router-link class="clothing-item-card-preview"
:class="classes"
:style="previewStyle"
:to="{ name: 'clothingItem', params: { id: this.clothingItem.id }}"
v-on="{ click: onClick ? onClick : null }">
However I got this: Invalid handler for event "click": got null
Plus not sure if it's possible to pass prevent modificator for click and this just looks weird, there should be a better architectural solution
Commenting on the error, you could use an empty function instead of null, in the real code snippet
<router-link class="clothing-item-card-preview"
:class="classes"
:style="previewStyle"
:to="{ name: 'clothingItem', params: { id: this.clothingItem.id }}"
v-on="{ click: onClick ? onClick : null }">
This should works (replace a for "router-link" then insert right properties)
Further infos :
https://fr.vuejs.org/v2/guide/components-dynamic-async.html
v-bind is simply an Object where each keys is a props for your component, so here, I programmatically defined an object of properties depending on the wrapper (router link or a simple div). However we cannot do this for events (of course we could create our own event listener but it's a little bit tricky) so I simply but an handle method.
new Vue({
el: "#app",
data: {
products : [{onClickCallback : () => { alert("callback"); return true;}}, {}, {}]
},
methods : {
handleClick(product, event) {
if (!product.onClickCallback) return false
product.onClickCallback()
return true
},
getMyComponentName(product) {
if (product.onClickCallback) return "div"
return "a"
},
getMyComponentProperties(product) {
if (product.onClickCallback) return {is : "div"}
return {
is : "a",
href: "!#"
}
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<component
v-for="(product, index) in products"
:key="index"
v-bind="getMyComponentProperties(product)"
#click="handleClick(product, $event)"
>
<div class="product-card">
<div class="product-card-content">
<span v-show="product.onClickCallback">I'm a callback</span>
<span v-show="!product.onClickCallback">I'm a router link</span>
</div>
</div>
</component>
</div>
Do you have to use a <router-link>? If it can safely be a <div>, you could use something like
<div #click="handleClick" ...>
<!-- component template -->
</div>
and
methods: {
handleClick (event) {
if (this.onClickCallback) {
this.onClickCallback(event)
} else {
this.$router.push({ name: 'clothingItem', ... })
}
}
}
See https://router.vuejs.org/guide/essentials/navigation.html