I am trying to build a sidebar that render menu dynamically depends on menu data provided.
Here is my TheSideBarMenu.vue
<template>
<ul class="nav flex-column pt-3 pt-md-0">
<li class="nav-item" v-for="m in menu" :key="m.path">
<router-link v-if="m.children.length===0" class="nav-link" :to="m.to">
<span class="sidebar-icon"><span :class="m.icon"></span></span>
<span class="sidebar-text">{{ m.label }}</span>
</router-link>
<span v-if="m.children.length > 0" class="nav-link collapsed d-flex justify-content-between align-items-center" data-bs-toggle="collapse" data-bs-target="#submenu-app">
<span>
<span class="sidebar-icon"><span class="fas fa-user"></span></span>
<span class="sidebar-text">Users</span>
</span>
<span class="link-arrow"><span class="fas fa-chevron-right"></span></span>
</span>
<div v-if="m.children.length > 0" class="multi-level collapse" role="list" id="submenu-app" aria-expanded="false">
<the-side-bar-menu :menu="m.children" />
</div>
</li>
</ul>
</template>
<script>
export default {
name: "TheSideBarMenu",
props: {
menu : Array
},
setup(props) {
console.log('sidebarmenu',props.menu)
},
}
</script>
and menu would like something like this
[
{
to : '/',
label : 'Dashboard',
icon : 'fas fa-chart-pie'
},
{
to : '#',
label : 'User',
icon : 'fas fa-user',
children : [
{
to : '/user',
label : 'User List',
icon : 'fas fa-chart-pie'
},
{
to : '/user/create',
label : 'Create User',
icon : 'fas fa-chart-pie'
}
]
},
{
to : '/search',
label : 'Search',
icon : 'fas fa-chart-pie'
}
]
The problem is when I put in a component template it call component's setup infinitely. Is my recursive component wrong or what?
Related
I'm doing a questions list and every question that I added have 3 options delete, Undo and Set Color. So when I try to change the color of one question to orange it changes all the background color for all questions.
Template :
<template>
<div class="container" id="app">
<h1>Vragenlijst</h1>
<div class="pol">
<input type="text" v-model="que" :class="inputCls" autofocus>
<span class="addBtn">
<button #click="inputCls='inputbox extend'"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-full"
v-if="!showIcon">Add question</button>
<button #click="addqueS"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-full"
v-else>Add</button>
</span>
</div>
<div class="section">
<ul class="queS" v-if="queS.length > 0">
<transition-group name="list" tag="p">
<li :style="{ 'background-color': cueCardColor }" v-for="(item, i) in queS" :key="item" :class="+ cueCardColor">
<span :class="{complete: item.completed}">{{ item.task }}</span>
<span>
<button #click="undoComplete(i)"
class="bg-yellow-500 hover:bg-yellow-700 text-white font-bold py-2 px-4 rounded-full"
v-if="item.completed">undo</button>
<button #click="undoComplete(i)"
class="bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded-full"
v-else>check</button>
<button #click="deleteque(i)"
class="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded-full">Delete</button>
<select class="bg-gray-500" v-model="selected">
<option v-for="option in options" v-bind:value="option.value">
{{ option.text }}
</option>
</select>
</span>
</li>
</transition-group>
</ul>
<h3 v-else>No question to display. Add one.</h3>
</div>
</div>
</div>
</template>
I think the problem is that not every question has an id. cueCardColor() checks for the color that is selected.
export default {
name: "App",
data() {
return {
selected: 'this',
options: [
{ text: 'Select color'},
{ text: 'Gray', value: 'gray' },
{ text: 'Orange', value: 'orange' },
{ text: 'Green', value: 'green' }
],
que: '',
queS: [],
showIcon: false,
inputCls: 'inputbox',
};
},
watch: {
que(value) {
if(value.length > 2) {
this.showIcon = true;
}
else {
this.showIcon = false;
}
}
},
methods: {
addqueS() {
this.inputCls = 'inputbox';
this.queS.unshift(
{
task: this.que,
completed: false
}
);
this.que = '';
setTimeout(() => {
this.showIcon = false;
}, 1000);
},
undoComplete(index) {
this.queS[index].completed = !this.queS[index].completed;
},
deleteque(index) {
this.queS.splice(index, 1);
},
},
computed: {
cueCardColor() {
if(this.selected!='Select color'){
return this.selected
}
return 'transparent'
},
}
I need to do {{item.icon}} pull as a html data not string but ı don't know how to do that, is there are anyway to do that please help me out
I have this code:
<div class="box my-5" v-for="(item, index) in items" :key="index" >
<div class="innerBox">
<router-link to="/ninethPage">
<div class="card Fcard d-flex flex-row justify-content-center align-items-center" style="padding: 1rem 2rem !important">
<span v-html="icon"> </span>
<p>{{item.title}}</p>
</div>
</router-link>
<router-view></router-view>
</div>
</div>
</div>
export default {
el: '#app',
data() {
return {
items: [
{title: 'Android', icon: <i class="fab fa-android mx-3 img-fluid" style="font-size: 1.5rem;" ></i>},
{title: 'IOS', icon: <i class="fab fa-apple mx-3 img-fluid" style="font-size: 1.5rem;" ></i>}
]
}
},
components:{
Header
}
}
`
icon: <i class=... is JSX syntax that creates an element and needs to be used with render function instead of a template. It should be a string, icon: '<i class=...'.
There is no icon property, it should be <span v-html="item.icon">.
It's impractical to specify the whole icon element. If only <i> classes differ, it can be icon: 'fa-android', and be used with:
<i class="class="fab mx-3 img-fluid" style="font-size: 1.5rem" :class="item.icon"/>
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;
})
}
My vuejs application has a navigation bar and its code as bellow.
The problem is navigation bar display with its color but no links are available at the navigation bar .also console display a error like this click here to view the error . when I searched through the stackoverflow i found that some answers related to my problem and on was changing auth: '' to auth: {} . But it dosent also worked .So could any please help me to fix this.Thanks
<template>
<nav class="navbar navbar-expan-lg navbar-dar bg-primary rounded">
<button class="navbar-toggler"
type="button"
data-toggle="collapse"
data-target="#navbar1"
aria-controls="navbar1"
aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse justify-content-md-center" id="navbar1">
<ul class="navbar-ul">
<li class="nav-item">
<router-link class="nav-link" to="/home">Home</router-link>
</li>
<li v-if="auth == ''" class="nav-item">
<router-link class="nav-link" to="/login">Login</router-link>
</li>
<li v-if="auth == ''" class="nav-item">
<router-link class="nav-link" to="/register">Register</router-link>
</li>
<li v-if="auth == 'loggedin'" class="nav-item">
<router-link class="nav-link" to="/profile">Profile</router-link>
</li>
<li v-if="auth == 'loggedin'" class="nav-item">
<router-link class="nav-link" to="/logout">Logout</router-link>
</li>
</ul>
</div>
</nav>
</template>
<script>
import EventBus from './EventBus'
export default {
data () {
return {
auth: '',
user: ''
}
},
methods: {
logout () {
localStorage.removeItem('usertoken')
}
},
mounted () {
EventBus.$on('logged-in', status => {
this.auth = status
})
}
}
This is the index.js of Router folder
import Vue from 'vue'
import Router from 'vue-router'
import Home from '#/components/Home'
import Login from '#/components/Login'
import Register from '#/components/Register'
import Profile from '#/components/Profile'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/login',
name: 'Login',
component: Login
},
{
path: '/register',
name: 'Register',
component: Register
},
{
path: '/profile',
name: 'Profile',
component: Profile
}
]
})
You are missing a to in your last link. Replace the href with to and you error should dissappear.
<li v-if="auth == 'loggedin'" class="nav-item">
<router-link class="nav-link" to="">Logout</router-link>
</li>
The error about name on undefined should be gone too. This is probably because it cannot find the a route with the key undefined as defined by the router-link
If this doesn't solve it, you may need to post more code.
Like the title says, Im trying to append a vue-component to an existing v-model
The only thing i found on the internet was the v-for solution to add more elements, but that is not what I want to do since this v-model could have multiple components
chat.vue
The V-model im trying to append to
<template>
<ul class="chat-ul" v-model="messages">
<chat-entry :name="name" :message="text" direction="left"></chat-entry>
</ul>
</template>
Here is where im stuck, how i can add the components
<script>
Vue.component('chat-entry', require('./entry.vue'));
module.exports = {
data: function () {
return {
name: 'Default name',
message: 'Default message',
messages: null,
}
},
mounted: function () {
// This is the what i want to achive
this.messages.append('chat-entry', {direction: 'left', message: 'hello', name: 'HeatON'});
this.$http.get('/etc/yelp.xi').then(response => {
for (let q in response.body.arr) {
this.messages.append('chat-entry', {direction: 'left', message: q.message, name: q.name});
}
});
}
}
entry.vue
<template>
<div v-if="direction == 'left'">
<li>
<div class="message-data">
<span class="message-data-name"><i class="fa fa-circle you"></i> {{ name }}</span>
</div>
<div class="message you-message"> {{ message }} </div>
</li>
</div>
<div v-else>
<li class="clearfix">
<div class="message-data align-right">
<span class="message-data-name">{{ name }}</span> <i class="fa fa-circle me"></i>
</div>
<div class="message me-message float-right"> {{ message }} </div>
</li>
</div>
</li>
Is something like this even possible? if not, what is the proper approach?
Thanks in advance