I have an array of nav items including subnav I have added to a sidebar component in a dashboard I am making.
I will like to add router links with different icons to each item in the array. I am still trying to wrap my head around how to do it and I really need help.
below is the code sample.
Array nav items
state: {
navigation: [
{title: "Home"},
{
title: "Posts",
poen: true,
subnav:[
{title: "Published"},
{title: "Draft"},
{title: "Trashed"},
]
},
{
title: "Users",
open: true,
subnav:[
{title: "Admins"},
{title: "Authors"},
{title: "Editors"},
{title: "Subscribers"}
]
},
]
},
getters: {
navigation: state => {
return state.navigation
}
},
Sidebar menu items
<ul id="navigation">
<li v-for="(item, index) in navigation" :key="'item'+index">
<div class="title" #click="item.open = !item.open">{{item.title}}</div>
<Dropdown v-if="item.subnav" :list="item" />
</li>
Drop Down Items
<ul v-show="list.open">
<li v-for="(item, index) in list.subnav" :key="'item' +index">
<div class="title" #click="item.open = !item.open">{{item.title}}</div>
<Dropdown v-if="item.subnav" :list="item" />
</li>
So that in the end, I want to achieve something like this:
You can do it two ways (or more)
1st, you can import and add the icon class to the array of objects to later bind it to the template.
Eg. heroicons and usage.
import { UserCircleIcon, ..otherIconsImaginary } from "#heroicons/vue/solid";
navigation: [
{title: "Home", icon: UserCircleIcon},
{title: "Posts",poen: true,icon: PostsIcon,
subnav:[
{title: "Published", icon: SomeOtherIcon},
{title: "Draft", icon: DraftsIcon},
{title: "Trashed", icon: TrashedIcon},
]
}
]
Then on your template bind it as a component
2nd, Import your icons from anywhere and then define icon classes for all objects.
navigation: [
{ title: "Home", icon_class: "home-icon" }...
]
then bind it as an image like this
<img :src="homeIcon" alt="" v-if="item.icon_class === 'home-icon'" />
PS. I think you should use the state for user information management only. This code can be finished in layouts since you can have multiple layouts for authenticated, non-authenticated, 404, and other pages as per your business logic wideness.
Related
Say I have a custom component that uses Vuetify's v-data-table within.
Within this component, there's multiple other custom components such as loaders and specific column-based components for displaying data in a certain way.
I found myself using the same code for filtering, retrieving data, loaders etc. across the project - so not very DRY.
The things that vary are:
API request url to retrieve data from (which I can pass to this generic component)
headers for v-data-table (which I pass to this generic component)
specific item slot templates!
(One file using this same code would need a column modification like the below, requiring different components sometimes too):
<template v-slot:[`item.FullName`]="{ item }">
<router-link class="black--text text-decoration-none" :to="'/users/' + item.Id">
<Avatar :string="item.FullName" />
</router-link>
</template>
Where another would have for example:
<template v-slot:[`item.serial`]="{ item }">
<copy-label :text="item.serial" />
</template>
There are many more unique "column templates" that I use obviously, this is just an example.
modifying items passed to v-data-table in a computed property (to add "actions" or run cleanups and/or modify content before displaying it - not related to actual HTML output, but value itself)
computed: {
items () {
if (!this.data || !this.data.Values) {
return []
}
return this.data.Values.map((item) => {
return {
device: this.$getItemName(item),
serial: item.SerialNumber,
hwVersion: this.$getItemHwVersion(item),
swVersion: this.$getItemSwVersion(item),
actions: [
{ to: '/devices/' + item.Id, text: this.$t('common.open') },
{ to: '/devices/' + item.Id + '/replace', text: this.$t('common.replace') }
],
...item
}
})
}
there are some unique methods that I can use on certain template slot item modifications, such as dateMoreThan24HoursAgo() below:
<template v-slot:[`item.LastLogin`]="{ item }">
<span v-if="dateMoreThan24HoursAgo(item.LastLogin)">{{ item.LastLogin | formatDate }}</span>
<span v-else>
{{ item.LastLogin | formatDateAgo }}
</span>
</template>
I can always make this global or provide them as a prop so this point should not be a big issue.
So my questions are:
What is the best way to use one component with v-data-table within but dynamically pass template slots and also allow item modification prior to passing the array to the v-data-table (as per point 3 and 4 above)
is there a better way to approach this since this seems too complex (should I just keep separate specific files)? It does not feel very DRY, that's why I'm not very fond of the current solution.
Basically I would be happy to have something like:
data: () => {
return {
apiPath: 'devices',
headers: [
{ text: 'Device', align: 'start', value: 'device', sortable: false, class: 'text-none' },
{ text: 'Serial Number', sortable: false, value: 'serial', class: 'text-none' },
{ text: 'Status', value: 'Status', class: 'text-none' },
{ text: 'Calibration', value: 'NextCalibrationDate', class: 'text-none' },
{ text: '', sortable: false, align: 'right', value: 'actions' }
],
itemsModify: (items) => {
return items.map((item) => {
return {
device: this.$getItemName(item),
serial: item.SerialNumber,
actions: [
{ to: '/devices/' + item.Id, text: this.$t('common.open') },
{ to: '/devices/' + item.Id + '/replace', text: this.$t('common.replace') }
],
...item
}
})
},
columnTemplatesPath: '/path/to/vue/file/with/templates'
}
}
And then I'd just call my dynamic component like so:
<GenericTable
:api-path="apiPath"
:headers="headers"
:items-modify="itemsModify"
:column-templates-path="columnTemplatesPath"
/>
Relevant but not exactly a solution to my question:
Is it possible to use dynamic scoped slots to override column values inside <v-data-table>?
Dynamically building a table using vuetifyJS data table
I'm trying to create a vue app for budget tracking and I have a BudgetItems component that I want to render in the /budget route. All the other components and raw HTML render but this one component does not
This is the BudgetItems component:
<template>
<div>
<BudgetItem v-for="item in Items" v-bind:key='item.id' v-bind:Item="item" />
</div>
</template>
<script>
import BudgetItem from './BudgetItem'
export default {
name: 'BudgetItems',
components: {
BudgetItem,
},
props: [
'Items'
]
}
</script>
And this is the BudgetItem component I used to render a single item:
<template>
<div class="budgetitem">
<h1>{{item.title}}</h1>
<h1>{{item.value}}</h1>
</div>
</template>
<script>
export default {
name: 'BudgetItem',
props: [
'Item'
]
}
</script>
Last of all, this is the Budget page view:
<template>
<div class="budget">
<Nav />
<h1>Budget</h1>
<BudgetItems v-bind:Items="items" />
</div>
</template>
<script>
import Nav from "../components/Nav"
import BudgetItems from "../components/BudgetItems"
export default {
name: 'Budget',
components: {
Nav,
BudgetItems,
},
data(){
return{
items: [
{
id: 1,
income: false,
title: "Item 1",
value: 200
},
{
id: 2,
income: true,
title: "Item 2",
value: 500
},
{
id: 3,
income: false,
title: "Item 3",
value: 10
},
]
}
}
}
</script>
Also, when I look in the vue dev tools tab, the component appears, it just doesn't show on the screen
You need to change the v-bind declarations to lower case. Replace each instance of Items and Item with items and item.
Vue.JS doesn't like it if you capitalise props when using binding.
Please read this for more explanation.
Essentially, browsers treat all attribute names as lowercase. As a result, it interprets "Items" as being "items".
Budget page view:
<BudgetItems v-bind:items="items" />
BudgetItems:
<BudgetItem v-for="item in items" v-bind:key='item.id' v-bind:item="item"/>
props: [
'items'
]
BudgetItem:
props: [
'item'
]
Once you make these changes, it works perfectly as seen here:
I have a detail about an object inside my object fetched from the API. I would like to add a loop inside of the modal to show the details when I click.
data: function() {
return {
fields: [
{ key: 'customerOrderDelay', label: this.$t('metricsTable.column-name34'), sortable: true, class: 'text-center' },
{ key: 'supplierOrders', label: "suppllier order", sortable: true, class: 'text-center' },
{ key: 'actions', label: 'Actions' }
]
}
}
SupplierOrders is an object and I want to print the details with a loop inside the modal:
<b-modal :id="infoModal.id" :title="infoModal.item.orderAmount" ok-only #hide="resetInfoModal">
<!-- <pre>{{ infoModal.content }}</pre> -->
<div v-for="foo in infoModal.item.supplierOrders" ><md-card md-with-hover>
<md-ripple>
<md-card-header>
<div class="md-title">{{foo.supplierOrderNumber}}</div>
<div class="md-subhead">{{foo.supplierName}}</div>
</md-card-header>
<md-card-content>
{{foo.sectionName}}
</md-card-content>
<md-card-actions>
</md-card-actions>
</md-ripple>
</md-card>
the table :
<b-table
:tbody-tr-class="rowClass"
:busy="isBusy"
responsive="true"
:sticky-header="stickyHeader"
small
:items="totalOrders"
:fields="fields"
:per-page="300"
:filterIncludedFields="filterOn"
:sort-by="sortBy"
#sort-changed="sortTable"
>
It seems that I cannot access the object inside the object used by bootstrap-vue's table: Does anyone know how to override it?
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)
I need to add classes to an element, based on categories. Here is my example data:
data() {
return {
projects: [
{
title: 'Project 1',
categories: [{ name: 'featured' }, { name: 'category-1' }],
},
{
title: 'Project 2',
categories: [{ name: 'category-2' }],
},
],
};
},
What I need is to add the categories directly as classes on the wrapper div (with the v-for), that will render:
<div class="featured category-1">
<h3>Project 1</h3>
</div>
<div class="category-2">
<h3>Project 1</h3>
</div>
I'm not sure how to do this?
<div v-for="(project, index) in projects" :class="v-for="(category, index) in project.categories???">
{{project.title}}
</div>
Should I do this differently? I can't seem to figure it out. Thanks for your help!
It's simple:
<div v-for="project in projects" :class="classExtraction(project)">
<h3>
{{project.title}}
</h3>
</div>
You need a method that extracts classes from your project's categories:
methods: {
classExtraction(item) {
return item.categories.map(cat => cat.name);
}
}
http://jsfiddle.net/eywraw8t/371192/
Also, please note that you should use :key directive with v-for binding it to a unique property, preferably object's id:
https://v2.vuejs.org/v2/style-guide/#Keyed-v-for-essential