As my project is growing, I've noticed a lot of repetitions. I'm starting with the navigations buttons, as they can appear in multiple places (side menu, navbar).
I'd like to centralize them and let the component import them as needed. So I've tried creating this file to hold all my menu items:
// menuItems.js
export default {
data() {
return {
items: [
{ title: 'Profile', icon: 'mdi-account-circle', reqAuth: true, hideOnAuth: false},
{ title: 'Dashboard', icon: 'mdi-view-dashboard', reqAuth: true, hideOnAuth: false },
{ title: 'Settings', icon: 'mdi-cog', reqAuth: true, hideOnAuth: false },
{ title: 'Signup', icon: 'mdi-account-circle-outline', reqAuth: false, hideOnAuth: true},
{ title: 'Login', icon: 'mdi-login', reqAuth: false, hideOnAuth: true },
{ title: 'Logout', icon: 'mdi-logout', reqAuth: true, hideOnAuth: false},
]
}
},
methods: {
menuItems: function(authenticated) {
if (!authenticated) {
// Gets items that do and don't require Auth, except for ones that should hide on Auth
return this.items.filter(o => o.reqAuth || !o.reqAuth && !o.hideOnAuth)
}
// Gets items that don't require Auth
return this.items.filter(o => !o.reqAuth)
}
}
}
Buttons can require authentication to be visible, and they can also be hidden once authenticated (eg. The login button).
Now lets assume I have a vue component for my nav bar, how do I import in the method that returns the filtered items?
// NavBarComponent.vue
<template>
<div>
<v-btn v-for="(item, i) in menuItems(authenticated)">
{{ item.title }}
</v-btn>
</div>
</template>
<script>
export default {
name: "NavBarComponent",
data() {
return {
authenticated: true,
}
},
methods: {
}
}
</script>
In this case, how do i make menuItems in the component reference the external file that will do the work of filtering?
You can create a mixin file and put your methods in that mixin and apply the mixin your component.
https://v2.vuejs.org/v2/guide/mixins.html
Your mixin would look like this:
// /mixins/menuItemsMixin.js
const menuItemsMixin= {
data() {
return {
items: [
{ title: 'Profile', icon: 'mdi-account-circle', reqAuth: true, hideOnAuth: false},
{ title: 'Dashboard', icon: 'mdi-view-dashboard', reqAuth: true, hideOnAuth: false },
{ title: 'Settings', icon: 'mdi-cog', reqAuth: true, hideOnAuth: false },
{ title: 'Signup', icon: 'mdi-account-circle-outline', reqAuth: false, hideOnAuth: true},
{ title: 'Login', icon: 'mdi-login', reqAuth: false, hideOnAuth: true },
{ title: 'Logout', icon: 'mdi-logout', reqAuth: true, hideOnAuth: false},
]
}
},
methods: {
menuItems: function(authenticated) {
if (!authenticated) {
// Gets items that do and don't require Auth, except for ones that should hide on Auth
return this.items.filter(o => o.reqAuth || !o.reqAuth && !o.hideOnAuth)
}
// Gets items that don't require Auth
return this.items.filter(o => !o.reqAuth)
}
}
};
export default menuItemsMixin
And in your component:
// NavBarComponent.vue
<script>
import menuItemsMixin from './mixins/menuItemsMixin.js'
export default {
mixins:[menuItemsMixin]
}
</script>
You can import this mixin in multiple components and you can also add more mixins in this component where the unique methods will be added.
I ended up creating a javascript file:
// views.js
export const views = [
{title: 'Dashboard'},
{title: 'Profile'},
{title: 'Login/Signup'},
]
then in my navbar component I imported it like so:
import {mapGetters} from "vuex";
import {views} from "../../common/views";
export default {
data: () => ({
items: views
}),
computed: {
...mapGetters(['isAuthenticated',])
menuItems: function (){
if (this.isAuthenticated) {
// do this
} else {
// do this
}
},
}
}
Then I did the same for the filtering function, but I could also just re-code it as needed if required in each component. I determined authentication state using Vuex's store, and retrieve it with mapgetters.
<componentA v-if='isAuthenticated'>
<navItem v-for='item in menuItems'>
</componentA>
Related
I am trying to make a component using shepperd library for my guide tour in vuejs. Here is the library: https://shepherdjs.dev/ So I created a component for it and I want to use it in my parent component:
<template>
<div></div>
</template>
<script>
export default {
name: 'Example',
props: {
element: {
type: Object,
required: true,
},
text: {
type: String,
required: true,
},
},
mounted() {
this.$nextTick(() => {
const tour = this.$shepherd({
defaultStepOptions: {
cancelIcon: {
enabled: true,
},
scrollTo: { behavior: 'smooth', block: 'center' },
},
});
tour.addStep({
attachTo: { element: this.element, on: 'top' },
text: this.text,
buttons: [
{
action() {
return this.back();
},
classes: 'shepherd-button-secondary',
text: 'Back',
},
{
action() {
return this.next();
},
text: 'Next',
},
],
});
tour.start();
});
},
};
Here is my parent component:
So in my parent component I want to add two times this component into my parent component. But I dont know how to connect with them. So when I click next, I want to trigger it and kill it then I want to trigger the next one.
Any help will be appreciated.
I'm generating some menu buttons dynamically (not sure if that's best practice)
script
items: [
{ title: 'Dashboard', icon: 'mdi-view-dashboard', route: '/' },
{ title: 'Register', icon: 'mdi-image', route: '/register' },
{ title: 'Login', icon: 'mdi-help-box', route: '/login' },
],
html
<v-list-item v-for="(item, i) in items" :key="i" link :to="{path: item.route}">
But what I want to do is hide the dashboard button until they have signed in. The user signed in value is kept in store.
$store.state.user.signedIn // true/false
How can I programmatically hide buttons depending on signed in value? I was trying to do this
{ title: 'Dashboard', icon: 'mdi-view-dashboard', route: '/', reqAuth: true }
But not sure how to get it working smoothly in the html. I'd also like to do the reverse later on the login/register buttons, if the user IS signed in then these should hide and a Logout button will come into play.
You have two options:
A) have separate menu arrays and display one or the other based on isLoggedIn. If you have any items showing in both cases, you'll need to place them in a third array and concat one of the first two with the third
B) have a boolean property on each menu item stating whether it should show when isLoggedIn or not. If you have menu items showing on both, you'll need either two props on each item (showWhenLoggedIn, showWhenLoggedOut - change them if too long) or, alternatively, you could make the show an array of booleans: show: [true, false] - first bool controlling whether it's shown when logged out, second when logged in).
Solution A) example (separate arrays):
Vue.config.devtools = false;
Vue.config.productionTip = false;
new Vue({
el: '#app',
data: () => ({
loggedInMenuItems: [
{ title: 'Dashboard', icon: 'mdi-view-dashboard', route: '/', show: [false, true] },
],
loggedOutMenuItems: [
{ title: 'Register', icon: 'mdi-image', route: '/register', show: [true, false] },
{ title: 'Login', icon: 'mdi-help-box', route: '/login', show: [true, false] },
],
permanentMenuItems: [
{ title: 'Terms and Conditions', icon: 'mdi-whatever', route: '/terms', show: [true, true] }
],
isLoggedIn: false
}),
computed: {
menuItems() {
return (this.isLoggedIn
? this.loggedInMenuItems
: this.loggedOutMenuItems
).concat(this.permanentMenuItems)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<label><input v-model="isLoggedIn" type="checkbox"> Is logged in</label>
<pre v-html="menuItems.map(m => m.title)"></pre>
</div>
Solution B) example:
Vue.config.devtools = false;
Vue.config.productionTip = false;
new Vue({
el: '#app',
data: () => ({
items: [
{ title: 'Dashboard', icon: 'mdi-view-dashboard', route: '/', show: [false, true] },
{ title: 'Register', icon: 'mdi-image', route: '/register', show: [true, false] },
{ title: 'Login', icon: 'mdi-help-box', route: '/login', show: [true, false] },
{ title: 'Terms and Conditions', icon: 'mdi-whatever', route: '/terms', show: [true, true] }
],
isLoggedIn: false
}),
computed: {
menuItems() {
return this.items.filter(item => item.show[Number(!!this.isLoggedIn)])
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<label><input v-model="isLoggedIn" type="checkbox"> Is logged in</label>
<pre v-html="menuItems.map(m => m.title)"></pre>
</div>
I personally prefer the second one, for brevity. I also find it a tad more elegant.
However, in large teams or in projects where code complexity needs to be kept to a minimum and code readability to a maximum, the first solution is often times preferred.
Lastly, second solution allows more flexibility for menu items order, although it's not a real issue (implementing an order attribute to each item would be trivial).
Note: obviously, isLoggedIn should come from state, not from component data fn. I placed it in data so you could easily test it here.
Your items property should be defined as computed property and add shown field in each item of items and use state value as its value in dashboard link :
computed :{
items(){
const signedIn=$store.state.user.signedIn;
return [
{ title: 'Dashboard', icon: 'mdi-view-dashboard', route: '/', shown:signedIn },
{ title: 'Register', icon: 'mdi-image', route: '/register',shown:!signedIn},
{ title: 'Login', icon: 'mdi-help-box', route: '/login' , shown:!signedIn},
]
}
}
in template add
<v-list-item v-for="(item, i) in items" :key="i" link v-if="item.shown" :to="{path: item.route}">
I tried to modify the routing configuration problem, but the breadcrumbs have two identical user lists, the error is displayed as 首页 / 系统设置 / 用户列表 / 用户列表. For example, when the left menu clicks on the user list, it should display correctly 首页 / 系统设置 / 用户列表.
The error is displayed as:
Displayed correctly as:
router.config.js
{
path: '/settings',
name: 'settings',
component: RouteView,
redirect: '/settings/user',
meta: { title: '设置系统', keepAlive: true, icon: bxAnaalyse },
children: [
{
path: '/settings/user',
name: 'user',
component: RouteView,
redirect: '/settings/user/list',
meta: { title: '用户列表', keepAlive: false },
hideChildrenInMenu: true,
children: [
{
path: '/settings/user/list',
name: 'user',
component: () => import('#/views/settings/user/index'),
meta: { title: '用户列表', keepAlive: false }
},
{
path: '/settings/user/add',
name: 'add',
component: () => import('#/views/settings/user/add'),
meta: { title: '新增用户', keepAlive: true, hidden: true }
}
]
},
]
},
components/tools/Breadcrumb.vue
<template>
<a-breadcrumb class="breadcrumb">
<a-breadcrumb-item v-for="(item, index) in breadList" :key="item.name">
<router-link
v-if="item.name != name && index != 1"
:to="{ path: item.path === '' ? '/' : item.path }"
>{{ item.meta.title }}
</router-link>
<span v-else>{{ item.meta.title }}</span>
</a-breadcrumb-item>
</a-breadcrumb>
</template>
<script>
export default {
data () {
return {
name: '',
breadList: []
}
},
created () {
this.getBreadcrumb()
},
methods: {
getBreadcrumb () {
this.breadList = []
// this.breadList.push({name: 'index', path: '/', meta: {title: '首页'}})
this.name = this.$route.name
this.$route.matched.forEach(item => {
// item.name !== 'index' && this.breadList.push(item)
this.breadList.push(item)
})
}
},
watch: {
$route () {
this.getBreadcrumb()
}
}
}
</script>
<style scoped>
</style>
Why would you redirect '/settings/user' to '/settings/user/list'? The extra "用户列表"
in the breadcrumb comes from the title of '/settings/user/list'.
I suggest you delete the '/settings/user/list' item and the 'redirect' attribute of '/settings/user'.
I'm using Vuex and realy like it. However I've got a weird problem. I've got a module called filters it looks like this:
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default {
namespaced: true,
state: {
filters: [{ key: 'page', value: 1 }],
criterias: [
{
name: "🔥 LIFE",
filter: "LIFE",
active: false
},
{
name: "🚻 FACILITIES",
filter: "FACILITIES",
active: false
},
{
name: "📡 WIFI",
filter: "WIFI",
active: false
},
{
name: "😀 FUN FOR KIDS",
filter: "FUN_FOR_KIDS",
active: false
},
{
name: "😀 FUN FOR ADULTS",
filter: "FUN_FOR_ADULTS",
active: false
},
{
name: "💰 COSTS",
filter: "COSTS",
active: false
},
{
name: "🌊 WATER QUALITY",
filter: "WATER_QUALITY",
active: false
},
{
name: "⛵ SAIL BOAT FRIENDLY",
filter: "SAIL_BOAT_FRIENDLY",
active: false
},
{
name: "🛥️ MOTOR BOAT FRIENDLY",
filter: "MOTOR_BOAT_FRIENDLY",
active: false
},
{
name: "🇪🇸 SPANISH SPEAKING",
filter: "SPANISH_SPEAKING",
active: false
},
{
name: "🍲 RESTAURANTS",
filter: "RESTAURANTS",
active: false
},
{
name: "✌️ PEACE",
filter: "PEACE",
active: false
},
{
name: "🅿️ PARKING SPOTS (CAR)",
filter: "PARKING_SPOTS",
active: false
},
{
name: "🏴 ENGLISH SPEAKING",
filter: "ENGLISH_SPEAKING",
active: false
}
]
}
}
(I import this module in my main.js file)
When I try to get the criterias from this module in a custom component:
<script>
export default {
data() {
return {
criterias: []
}
},
computed: {
...mapState({
criteriasVuex: state => state.filters.criterias
})
},
created() {
this.criterias = this.criteriasVuex.slice(0, 7);
}
}
</script>
Criterias is always empty! When I look in vue-devtools no criterias are visible in my component or vuex state!!! How is this possible?
The weird thing is that filters on the state is not empty! I can see that in vue-devtools.
It does work with the code you have shown (see sample CodeSandbox), so likely the problem is in the way the filter module is added to the store.
Here is a store that works
import Vue from "vue";
import Vuex from "vuex";
import filters from "./filtersModule";
Vue.use(Vuex);
const modules = {
filters
};
export default new Vuex.Store({
modules
});
<script>
import {mapState} from 'vuex'
export default {
computed: {
...mapState({
criterias
})
},
}
</script>
Should bring you further. 'state.filters.criterias' won't work because:
You're in the MapState function, thus the "state" namespace needs to be omitted.
criterias is not a subobject of filters.
how to implement closing a page through a function in vue js
by example
configured routing
{
path: '/example',
component: Layout,
redirect: '/example/list',
name: 'Example',
meta: {
title: 'example',
icon: 'example'
},
children: [
{
path: 'create',
component: () => import('#/views/example/create'),
name: 'CreateArticle',
meta: { title: 'createArticle', icon: 'edit' }
},
{
path: 'edit/:id(\\d+)',
component: () => import('#/views/example/edit'),
name: 'EditArticle',
meta: { title: 'editArticle', noCache: true },
hidden: true
},
{
path: 'list',
component: () => import('#/views/example/list'),
name: 'ArticleList',
meta: { title: 'articleList', icon: 'list' }
}]
},
when I go to ArticleList.vue, I open the list, go there to the authoring / editing country -> a new member opens (the list page remains open in the tab next to it), after submitting the form in CreateArticle.vue, the CreateArticle.vue page should close
Tried through destroy () As a result, the content of the page is cleaned, and the page itself remains (but empty), you have to close the page through X