VueJs 2 fetching data from json to make navigation - vuejs2

I have fetched the following json into my navigation for loop.
This works, but when i paste {{item.navitems.navitem.name}} into to="" i am getting this error.
How can i achieve this working?
Error:
Loop:
<ul class="nav navbar-nav">
<li v-for="item in items">
<router-link to="" tag="li" active-class="active"><a>{{item.navitems.navitem.name}}</a></router-link>
</li>
</ul>
Json:
[
{
"navitems": {
"navitem": {
"name": "Home",
"url": ""
}
}
},
{
"navitems": {
"navitem": {
"name": "About",
"url": "/about"
}
}
}
]

It seems that you need a null check, like following:
<ul class="nav navbar-nav">
<li v-for="item in items">
<router-link v-if="item.navitems && item.navitems.navitem" :to="item.navitems.navitem.name" tag="li" active-class="active">
<a>{{item.navitems.navitem.name}}</a>
</router-link>
</li>
</ul>
Edit: as par suggestion, making it more concise:
<ul class="nav navbar-nav">
<router-link v-for="item in items" v-if="item.navitems && item.navitems.navitem" :to="item.navitems.navitem.url" tag="li" active-class="active" exact>
<a>{{item.navitems.navitem.name}}</a>
</router-link>
</ul>

Related

How to change a css style each page in vue-cli

This is App.vue File
<template>
<div id="app">
<main-header/>
<router-view/>
<main-footer/>
</div>
</template>
<script>
import MainHeader from './components/layouts/MainHeader'
import MainFooter from './components/layouts/MainFooter'
export default {
name: 'App',
components: {
'main-header': MainHeader,
'main-footer': MainFooter
}
}
</script>
Header.vue file is
<ul class="nav nav-pills" id="mainNav">
<li class="dropdown">
<a class="active" href="/">
Home
</a>
</li>
<li>
<a class="dropdown-item dropdown-toggle" href="/test1">
test1
</a>
</li>
<li>
<a class="dropdown-item dropdown-toggle" href="/test2">
test2
</a>
</li>
</ul>
I want to activate "active class" differently for each page.
I don't know how to do
if I click a "test2", active class have to maintain only with the tag with "test2"
How can I resolve this issue?
You can consider using Vue-router with active-class prop
<ul class="nav nav-pills" id="mainNav">
<router-link to="/" tag="li" class="dropdown" active-class="active">Home</router-link>
<router-link to="/test1" tag="li" class="dropdown-item dropdown-toggle" active-class="active">Test1</router-link>
<router-link to="/test2" tag="li" class="dropdown-item dropdown-toggle" active-class="active">Test2</router-link>
</ul>
Another naive solution is checking window.location.href, but it's not simple to cover all possible cases.
<ul class="nav nav-pills" id="mainNav">
<li class="dropdown">
<a class="active" href="/">
Home
</a>
</li>
<li>
<a class="dropdown-item dropdown-toggle" href="/test1" :class="getClass('/test1')">
test1
</a>
</li>
<li>
<a class="dropdown-item dropdown-toggle" href="/test2" :class="getClass('/test2')">
test2
</a>
</li>
</ul>
<script>
export default {
methods: {
getClass(url) {
if (window.location.href.includes(url)) {
return "active"
}
return ""
}
}
}
</script>

Add router-link-exact-active to parent component when child component is in view

I have character component where I have a listing of characters. Each characters have a router-link which is redirecting to characters detail component. I want to add router-link-exact-active in Nvabra's character when character detail is in view.
{
path: '/characters',
name: 'characterList',
component: () => import('../components/characters/characterList.vue')
},
{
path: '/characters/:id',
name: 'characterDetail',
component: () => import('../components/characters/charactersDetail.vue')
},
Navbar
<ul class="navbar-nav m-auto custom-nav">
<li class="nav-item">
<router-link to="/">home</router-link>
</li>
<li class="nav-item">
<router-link to="/about-Us">about us</router-link>
</li>
<li class="nav-item">
<router-link to="/characters">characters</router-link>
</li>
<li class="nav-item">
<router-link to="/our-comics">comics</router-link>
</li>
</ul>

how to make bootstrap4 collapse work in vue?

I am using bootstrap 4.3.1 and vue#2.6.10
I have this menu (is using collapse - and I don`t want to use JQuery):
<li class="nav-item">
<a class="nav-link" href="#sidebar-products" data-toggle="collapse" role="button" aria-expanded="false" aria-controls="sidebar-products">
<i class="ni ni-single-copy-04 text-primary"></i>
<span class="nav-link-text">Products</span>
</a>
<div class="collapse" id="sidebar-products">
<ul class="nav nav-sm flex-column">
<li class="nav-item">
Item List 1
</li>
<li class="nav-item">
Item List 2
</li>
</ul>
</div>
</li>
This is only a single block that contains 2 sub-items.
What I saw using JQuery, when click on "Products" the #sidebar-products receives the .show class and aria-expanded="true".
When having multiple blocks - when click on a block to close (if there are collapsed) the others blocks.
How can I make it work the collapse with vue?
UPDATE 1
I created a click event that do the job:
<a class="nav-link" href="javascript:void(0)" #click="navItemCollapse('sidebar-products', $event)" data-toggle="collapse" role="button" aria-expanded="false" aria-controls="sidebar-products">
and the event:
navItemCollapse(id, event) {
let expanded = event.target.getAttribute('aria-expanded').toLocaleLowerCase() == 'true' ? true : false;
let el = document.getElementById(id);
expanded ? el.classList.remove('show') : el.classList.add('show');
event.target.setAttribute('aria-expanded', !expanded);
}
But what if I have more blocks ? When click to open the current collapse on a block to close the others ???
This is the implementation of no jquery
new Vue({
el: '#app',
data() {
return {
menuList: [{
name: 'Products',
expand: false,
items: [{
name: 'Item List 1',
link: ''
},
{
name: 'Item List 2',
link: ''
}
]
},
{
name: 'Others',
expand: false,
items: [{
name: 'Other Item 1',
link: ''
},
{
name: 'Other Item 2',
link: ''
}
]
}
]
}
},
methods: {
navItemCollapse(index) {
this.menuList = this.menuList.map((item, i) => {
item.expand = !item.expand
if (i !== index) {
item.expand = false
}
return item
})
}
}
})
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<ul id="app">
<li v-for="(navItem,i) in menuList" :key="i" class="nav-item">
<a class="nav-link" href="javascript:;" data-toggle="collapse" role="button" :aria-expanded="navItem.expand" aria-controls="sidebar-products" #click.prevent="navItemCollapse(i)">
<i class="ni ni-single-copy-04 text-primary"></i>
<span class="nav-link-text">{{navItem.name}}</span>
</a>
<div v-if="navItem.items.length>0" class="collapse" :class="{show: navItem.expand}">
<ul class="nav nav-sm flex-column">
<li v-for="(subItem,j) in navItem.items" :key="j" class="nav-item">
{{subItem.name}}
</li>
</ul>
</div>
</li>
</ul>
I integrate the menu data into an array of objects. Each object has an expand flag to determine whether it is currently expanded. When you click on the menu, switch the expand flag of the current menu.
Note: You don't need to care about the id of the <a> tag.
No jQuery or bootstrap-vue ...
Create a function in the Component to handle the normal Bootstrap class and timing logic...
data() {
return {
classArr: ['collapse'],
styleObj: {}
};
},
methods: {
toggleCollapse(ref) {
let show = this.classArr.indexOf('show')>-1?false:'show'
this.classArr = ['collapsing']
setTimeout(() => {
if (show){
let height = this.$refs[ref].firstChild.clientHeight + 'px';
this.styleObj = { height }
}
else {
this.styleObj = {}
}
}, 10)
setTimeout(() => {
this.classArr = ['collapse', show]
}, 340)
}
}
In the component template, bind the class and style attrs to the data manipulated by the method. The ref of the specific collapse is passed in to the method...
<li class="nav-item">
<a class="nav-link" href="#sidebar-products" role="button" #click="toggleCollapse('sidebar')">
<i class="ni ni-single-copy-04 text-primary"></i>
<span class="nav-link-text">Products</span>
</a>
<div :class="classArr" :style="styleObj" id="sidebar-products" ref="sidebar">
<ul class="nav nav-sm flex-column">
<li class="nav-item">
Item List 1
</li>
<li class="nav-item">
Item List 2
</li>
</ul>
</div>
</li>
https://www.codeply.com/p/GA5CaNMzmc
EDIT: I updated the demo to make it scaleable for multiple collapses
This is a fully working version using bootstrap-vue:
<div class="accordion" role="tablist">
<b-card v-for="(value, key) in this.jobs" :key="key" no-body class="mb-1">
<b-card-header header-tag="header" class="p-1" role="tab">
<b-button block v-b-toggle="'accordion-'+key" variant="primary">{{ value.title }}</b-button>
</b-card-header>
<b-collapse :id="'accordion-'+key.toString()" accordion="my-accordion" role="tabpanel">
<b-card-body>
<b-card-text>{{ value.specs }}</b-card-text>
</b-card-body>
</b-collapse>
</b-card>
</div>
Data object:
data() {
return {
jobs: [
{
title: 'Design artist',
specs: 'Have an eye for web beauty'
},
{
title: 'Backend guru',
specs: 'Do stuff that don\'t break'
},
{
title: 'Frontend master',
specs: 'Create an UI that works'
}
]
}
}
I like the #sugars approach :)
So...the final version is this:
<li v-for="(navItem, i) in sidenavItems" class="nav-item">
<router-link v-if="!navItem.isCollapsible" class="nav-link" #click.native="navItemCollapse(i)" active-class="active" :to="{name: navItem.route}" exact>
<i :class="navItem.class"></i>
<span class="nav-link-text">{{ navItem.name }}</span>
</router-link>
<a v-if="navItem.isCollapsible" class="nav-link" href="javascript:void(0)" #click="navItemCollapse(i)" data-toggle="collapse" :aria-expanded="navItem.expanded">
<i :class="navItem.class"></i>
<span class="nav-link-text">{{ navItem.name }}</span>
</a>
<div v-if="navItem.isCollapsible" class="collapse" :class="navItem.expanded ? 'show' : ''">
<ul class="nav nav-sm flex-column">
<li v-for="subItem in navItem.items" class="nav-item">
<router-link class="nav-link" :to="{name: subItem.route}">{{ subItem.name }}</router-link>
</li>
</ul>
</div>
</li>
the sidenavItems:
sidenavItems: [
{name: 'Dashboard', isCollapsible: false, route: 'dashboard', class: 'class1'},
{name: 'Categories', isCollapsible: false, route: 'category', class: 'class2'},
{name: 'Brands', isCollapsible: false, route: 'brand', class: 'class3'},
{name: 'Products', isCollapsible: true, expanded: false, class: 'class4', items: [{name: 'List', route: 'product'}]},
{name: 'Orders', isCollapsible: false, route: 'order', class: 'class5'},
{name: 'Blog', isCollapsible: true, expanded: false, class: 'class6', items: [{name: 'List', route: ''}]},
],
and the navItemCollapse method:
navItemCollapse(index) {
this.sidenavItems = this.sidenavItems.map( (item, i) => {
item.expanded = !item.expanded;
if(i !== index) {
item.expanded = false;
}
return item;
})
}

Problem to make the drop down menu dynamic

I have been doing a project in which I need to create a dynamic drop down menu. In this project I am been using Angular 8 and bootstrap 4
The piece of code of html
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Dropdown
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<li ng-model="getOrders" ng-repeat="order in orders">
<a ng-click="getOrders()">{{orders}}</a>
//also tried {{ order.name }} - no result
</li>
<!-- <a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#">Something else here</a> -->
</div>
</li>
The above code is taken from the bootstrap site with the dropdown code:
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Dropdown
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#">Something else here</a>
</div>
</li>
and I have tried to use it with the below which I found in the Internet:
<span class="dropdown" dropdown on-toggle="toggled(open)">
<a href class="dropdown-toggle" dropdown-toggle>
Click me!
</a>
<ul class="dropdown-menu">
<li ng-repeat="choice in items">
<a ng-click="runFn(choice.fn)">{{choice.name}}</a>
</li>
</ul>
</span>
The code from component.ts from some tutorial:
form: FormGroup;
orders = [];
public someinfo:String = "";
public info:String = "";
constructor(private formBuilder: FormBuilder) {
this.form = this.formBuilder.group({
orders: ['']
});
this.orders = this.getOrders();
}
ngOnInit() {
}
getOrders() {
return [
{ id: '1', name: 'order 1' },
{ id: '2', name: 'order 2' },
{ id: '3', name: 'order 3' },
{ id: '4', name: 'order 4' }
];
}
After compiling the project and clicking the Dropdown Menu, the
result is like below:
[object Object],[object Object],[object Object],[object Object]
Could you please point me out, how should I fix it?
Thanks.
Can you try below?
Define how each order is going to look like outside your component class
interface OrderEntity
{
id : string;
name : string;
}
Inside you component class specify that, orders array will have the same structure
orders: OrderEntity[] = [];
With in getOrders try and push data into orders array
getOrders()
{
this.order.push({id : '1', name : 'order1'});
}
On the template you ca access orders as list and access each item by doing let item of orders; and obtain its attributes {{item.id}} and {{item.name}}

Vue run function from child component?

I have html like this:
<div id="nav">
<homenav v-on:navigation="switchNav(this.nav)" v-if="nav == 'homeNav'"></homenav>
<mainav v-if="nav == 'mainNav'"></mainav>
</div>
My Js is like this:
Vue.component('homenav', Navigation);
Vue.component('mainav', Navigation2);
new Vue({
el: '#nav',
data: {
nav: 'homeNav'
},
methods: {
switchNav: function (test) {
console.log(test);
console.log(this.nav);
}
}
});
Part of homenav component to show what I want to do:
<template>
<nav class="navbar navbar-expand-md navbar-dark bg-dark">
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse">
<span class="navbar-toggler-icon"></span>
</button>
<a class="navbar-brand" href="">Website Builder</a>
<div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav nav-fill w-100">
<li class="nav-item">
<a class="nav-link" href="/#/">Create</a>
</li>
<li class="nav-item">
<a v-on:click="navData" class="nav-link" href="/#/how">How</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/#/about">About</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/#/youtube">Videos</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/#/login">Go to main site</a>
</li>
</ul>
</div>
</nav>
</template>
<script>
export default {
data() {
return {
nav: ''
}
},
methods: {
navData () {
this.nav = 'mainNav';
this.$emit('navigation');
console.log(this.nav);
}
}
}
So what I am trying to accomplish here is a dynamic switch in navigation, for example if user click on Go to main site link the nav would change from homenav to mainav dynamically. However, I don't know how to accomplish it and my attempt is above. Help..
Edit so in my newest attempt I have managed so that it console log from child + parent function. it consoles homeNav and mainNav together. However I need to also pass the child this.nav to parent so parent can update it's this.nav? Because at the moment despite it outputting both, navigation doesn't change.
The best way to achieve that is using Vue Router.
But, if you want to do that without routing you should do it with Dynamic Components
<component is=""></component>
You can see an example here: https://jsfiddle.net/lucaskatayama/256Lo0xa/1/
There is an App, with a button to toggle a component inside <component></component>.
It simulates your switch, by changing the selected component.