I have a bunch of components, and the users are allowed to access only certain components based on their permissions. How to disable the component?
These are the routes:
{
path: "/administration",
name: "Administration",
component: PageAdministration,
meta: {
requiresAuth: true,
neededPermissions: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
},
},
{
path: "/settings/overview",
name: "SettingsOverview",
component: PageSettingsOverview,
meta: { requiresAuth: true,
showComponent: true,
},
},
{ path: "/:notFound(.*)", name: "NotFound", component: NotFound },
I have an authorizeUser function to authorize users, and I'd like to change the showComponent from routes based on the user's permissions. But I am not sure where to change the showComponent inside the function to disable the component.
if (currentUser!== 0 and to?.meta?.neededPermissions!== 0), {
for (let i = 0; i < currentUser.teams.length; i++) {
const team = currentUser.teams[i];
console.log(to.meta.neededPermissions);
const validPermissions = team.permissions.filter((item) => {
return to.meta.neededPermissions.includes(item.permissionType);
});
const mappedValidPermissions = validPermissions.map((item) => {
return item.permissionType;
});
if (
!to.meta.neededPermissions.every((i) =>
mappedValidPermissions.includes(i)
)
) {
isAllowed = false;
to.meta.showComponent= false
next({ path: "/:notFound(.*)" });
break;
}
else to.meta.showComponent= true
}
if (isAllowed) {
next();
}
} else {
next();
}
Here is the router-link referring the router
<div class="col-12 col-md-4" v-if="$route.meta.showComponent">
<router-link class="card" to="/administration">
<div class="card-img-top">
<i class="hi hi-people" />
</div>
<div class="card-body">
<h5 class="card-title">Teams & Users</h5>
</div>
</router-link>
</div>
You can achieve this by using navigation guards
router.beforeEach(async (to, from) => {
if (
// make sure the user is authenticated
!isAuthenticated &&
// Avoid an infinite redirect
to.name !== 'Login'
) {
// redirect the user to the login page
return { name: 'Login' }
}
})
Documentation with more complex examples
Related
I have a problem. I made routing what according how many addresses have user it will display them in manner 'Addresses(addrescount)'. global variable for getting address count is '$auth.user.address_count'
Task was if user have addresses show their count in sidebar if user do not have addresses show nothing .I did that but is any chance to have another solution to this problem
Is any chance to make it different then it now is?
how it looks like in programm
see line before {{route.display}}
<template>
<div class="col-lg-3">
<div class="profile-sidebar">
<ul class="list-unstyled">
<li v-for="route in nodes" :key="route.name">
<router-link :to="route.fullPath">
<template v-if="route.display === route.meta.title && $auth.user.address_count>0">
{{ route.display }}({{$auth.user.address_count}})
</template>
<template v-else>
{{ route.display }}
</template>
</router-link>
</li>
</ul>
</div>
</div>
</template>
<script>
import { routes } from '#/routes/routes';
export default {
data() {
return {
nodes: [],
}
},
async created() {
if (!this.$auth.user)
await this.$auth.updateUserInfo();
this.loadProfileNodes();
},
methods: {
loadProfileNodes() {
let node = routes.filter(route => route.path === '/profile').pop();
let hasLocalAccount = this.$auth.hasLocalAccount;
this.nodes = [];
node.children.forEach((route) => {
route['fullPath'] = node.path + '/' + route.path;
if ((!hasLocalAccount && route.path !== 'change-password') || (hasLocalAccount && route.path !== 'set-password')) {
this.nodes.push(route);
}
});
}
},
}
</script>
my routes.js see /profile children address-list route.
const routeOptions = [
{ path: '/', name: 'default', view: 'home', display: 'Home', meta: { showInMenu: true } },
{ path: '/401-forbidden', name: 'forbidden', view: 'errors/401-forbidden', display: '401 Forbidden' },
{ path: '/404-not-found', name: 'not-found', view: 'errors/404-not-found', display: '404 Page Not Found' },
{ path: '/login', name: 'login', view: 'auth/login' },
{ path: '/register', name: 'register', view: 'auth/register' },
{ path: '/auth/forgot-password', view: 'auth/forgot-password' },
{ path: '/auth/reset-password', view: 'auth/reset-password', props: (route) => ({ code: route.query.code }) },
{ path: '/auth/confirm-email', view: 'auth/confirm-email', props: (route) => ({ userId: route.query.userId, token: route.query.token }) },
{ path: '/admin/user-list', view: 'admin/users/user-list', display: 'Users', meta: { showInMenu: true, auth: { roles: 'Admin' } } },
{ path: '/admin/company-list', view: 'admin/companies/company-list', display: 'Companies', meta: { showInMenu: true, auth: { roles: 'Admin' } } },
{
path: '/profile',
view: 'profile/profile',
display: 'Edit profile',
meta: { auth: true },
children: [
{
path: '',
display: 'My Profile',
view: 'profile/edit-profile',
meta: { auth: true }
},
{
path: 'manage-logins',
display: 'External Logins',
view: 'profile/manage-logins',
meta: { auth: true }
},
{
path: 'address-list',
display: 'Addresses',
view: 'profile/addresses/address-list',
meta: { auth: true, title: 'Addresses' }
},
{
path: 'change-password',
display: 'Change Password',
view: 'profile/change-password',
meta: { auth: true }
},
{
path: 'set-password',
display: 'Set Password',
view: 'profile/set-password',
meta: { auth: true }
}
]
},
{ path: '*', redirect: { name: 'not-found' } }
];
function addDynamicImport(route) {
if (!route.view)
return route;
if (route.children && route.children.length) {
route.children = route.children.map(child => {
return addDynamicImport(child);
});
}
return {
...route,
component: () => import(/* webpackChunkName: "[request]" */ `#/components/views/${route.view}`)
}
}
const routes = routeOptions.map(route => {
return addDynamicImport(route);
})
export { routes }
router.js
import Vue from 'vue';
import VueRouter from 'vue-router';
import { routes } from './routes';
Vue.use(VueRouter);
let router = new VueRouter({
mode: 'history',
routes
});
export default router;
Answer is simple. You can add flag into '/profile/addres-list' route meta tag and check it if it is true/ True means we found the needed page
{
path: 'address-list',
display: 'Addresses',
view: 'profile/addresses/address-list',
meta: { auth: true, title: 'Addresses' }
},
into this (difference in the last line)
{
path: 'address-list',
display: 'Addresses',
view: 'profile/addresses/address-list',
meta: { auth: true, isAddressList: true }
}
and change
<template v-if="route.display === route.meta.title &&$auth.user.address_count>0">
to this
<template v-if="route.meta.isAddressList && $auth.user.address_count>0">
I'm making chat app by Vuejs and want to handle if messages in loop belong to new user for styling color/background-color of user's message.
<template v-for="msg in allMsgs">
<li :key=msg.id> //I want to add some class to handle if next message belong to new user.
<span class="chatname">{{msg.user.name}}</span>
{{msg.content}}
</li>
</template>
https://prnt.sc/114ynuq
Thank you so much
You can use a computed property to determine the position of each message according to the sequence, and then, use class-binding as follows:
new Vue({
el:"#app",
data: () => ({
allMsgs: [
{ id:1, user: { name:'B' }, content:'contentB' },
{ id:2, user: { name:'A' }, content:'contentA' },
{ id:3, user: { name:'A' }, content:'contentA' },
{ id:4, user: { name:'B' }, content:'contentB' },
{ id:5, user: { name:'B' }, content:'contentB' },
{ id:6, user: { name:'A' }, content:'contentA' },
{ id:7, user: { name:'A' }, content:'contentA' }
]
}),
computed: {
messages: function() {
let pos = 1, prev = null;
return this.allMsgs.map((msg, index) => {
// if msg is not the first, and it belongs to a new user, opposite pos
if(index !== 0 && msg.user.name !== prev.user.name) pos *= -1;
msg.position = pos;
prev = msg;
return msg;
});
}
}
});
.chatname { font-weight:bold; }
.left { text-align:left; }
.right { text-align:right; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<template v-for="(msg, index) in messages">
<li
:key=msg.id
:class="{ 'right': msg.position === 1, 'left': msg.position === -1 }"
>
<span class="chatname">
{{msg.user.name}}
</span>
{{msg.content}}
</li>
</template>
</div>
I am new to Vue JS and having a mind blank when I've been creating my first Auto complete comp with VueCLI.
Here is the working code with an array:
https://pastebin.com/a8AL8MkD
filterStates() {
this.filteredStates = this.states.filter(state => {
return state.toLowerCase().startsWith(this.state.toLowerCase())
})
},
I am now trying to get it to work with JSON so I can use axios to get the data.
In the filterStates method I understand I need to get the name of the item and do the lowercase on that, but it keeps erroring out when I try this:
https://pastebin.com/HPYyr9QH
filterStates() {
this.filteredStates = this.states.filter(state => {
return state.name.toLowerCase().startsWith(this.state.name.toLowerCase())
})
},
Vue is erroring this:
[Vue warn]: Error in v-on handler: "TypeError: state.toLowerCase is not a function"
Do I need to pass in a key or something to identify each record?
Let's take your second pastebin :
<script>
import PageBanner from '#/components/PageBanner.vue'
export default {
components: {
PageBanner
},
data() {
return {
state: '',
modal: false,
states: [
{
id: 1,
name: 'Alaska'
},
{
id: 2,
name: 'Alabama'
},
{
id: 3,
name: 'Florida'
}
],
filteredStates: []
}
},
methods: {
filterStates() {
this.filteredStates = this.states.filter(state => {
return state.name.toLowerCase().startsWith(this.state.name.toLowerCase())
})
},
setState(state) {
this.state = state
this.modal = false
}
}
}
</script>
You are calling : this.state.name.toLowerCase().
But this.state returns '' initially. So this.state.name is undefined.
You should initialize this.state with an object :
data() {
return {
state: {
name: ''
}
...
EDIT 17/03/2020
Here is another working solution :
What I did :
state is a string again. so I check this.state.toLowerCase()
In the setState function, I just pass the name : this.state = state.name
And to fix another error I changed this line : :key="filteredState.id" because a key should not be an object
<template>
<div>
<div class="AboutUs">
<PageBanner>
<template slot="title">Search</template>
</PageBanner>
<div class="container-fluid tarms-conditions">
<div class="row">
<div class="container">
<input
id
v-model="state"
type="text"
name
autocomplete="off"
class="form-control z-10"
placeholder="Search for a state..."
#input="filterStates"
#focus="modal = true"
>
<div
v-if="filteredStates && modal"
class="results z-10"
>
<ul class="list">
<li
v-for="filteredState in filteredStates"
:key="filteredState.id"
#click="setState(filteredState)"
>
{{ filteredState }}
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import PageBanner from '#/components/PageBanner.vue'
export default {
components: {
PageBanner
},
data () {
return {
state: '',
modal: false,
states: [
{
id: 1,
name: 'Alaska'
},
{
id: 2,
name: 'Alabama'
},
{
id: 3,
name: 'Florida'
}
],
filteredStates: []
}
},
methods: {
filterStates () {
this.filteredStates = this.states.filter(state => {
return state.name.toLowerCase().startsWith(this.state.toLowerCase())
})
},
setState (state) {
this.state = state.name
this.modal = false
}
}
}
</script>
Here's an example of a component:
<script>
export default {
name: 'my-form',
computed: {
myModules() {
return this.$store.state.myModules;
}
}
</script>
<template>
<form>
<p v-for="module in myModules">
<input type="checkbox" :value="module.id" />
<label>module.name</label>
</p>
<button type="submit">Submit</button>
</form>
</template>
The associated store:
state: {
myModules: []
},
mutations: {
setModules(state, modules) {
state.myModules = modules;
}
},
actions: {
getModules({commit}) {
return axios.get('modules')
.then((response) => {
commit('setModules', response.data.modules);
});
}
}
And finally, an example of return of the API "getModules":
modules : [
{
id: 1,
name: 'Module 1',
isActive: false
},
{
id: 2,
name: 'Module 2',
isActive: false
},
{
id: 3,
name: 'Module 3',
isActive: false
}
]
My question: what's the best way to change the "isActive" property of each module to "true" when I check the checkbox corresponding to the associated module, directly in the store?
I know that Vuex's documentation recommends to use "Two-way Computed Property" to manage the forms, but here I don't know the number of modules that the API can potentially return, and I don't know their name.
Thank you in advance!
This is a little bit wicked approach, but it works. You can create an accessor object for every item you access in a loop:
const store = new Vuex.Store({
mutations: {
setActive (state, {index, value}) {
state.modules[index].isActive = value
}
},
state: {
modules : [
{
id: 1,
name: 'Module 1',
isActive: false
},
{
id: 2,
name: 'Module 2',
isActive: false
},
{
id: 3,
name: 'Module 3',
isActive: false
}
]
}
});
const app = new Vue({
el: '#target',
store,
methods: {
model (id) {
const store = this.$store;
// here i return an object with value property that is bound to
// specific module and - thanks to Vue - retains reactivity
return Object.defineProperty({}, 'value', {
get () {
return store.state.modules[id].isActive
},
set (value) {
store.commit('setActive', {index: id, value});
}
});
}
}
})
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script src="https://unpkg.com/vuex/dist/vuex.min.js"></script>
<div id="target">
<div v-for="(item, id) in $store.state.modules">
Module #{{ item.id }} state: {{ item.isActive }}
</div>
<div v-for="(item, id) in $store.state.modules">
<label>
Module #{{ item.id }}
<input type="checkbox" v-model="model(id).value"/>
</label>
</div>
</div>
This is still quite a messy approach, but at least you don't have to commit mutations directly in template. With a little help of Vue.set() you can use this approach even to overcome standard reactivity caveats.
I have an alternative solution for you. You could make a child component for the checkboxes to clean up the code a bit.
UPD: I just realised that everything that I and #etki proposed is an overkill. I left the old version of my code below in case you still want to take a look. Here is a new one:
const modules = [{
id: 1,
name: 'Module 1',
isActive: true,
},
{
id: 2,
name: 'Module 2',
isActive: false,
},
{
id: 3,
name: 'Module 3',
isActive: false,
},
];
const store = new Vuex.Store({
state: {
myModules: [],
},
mutations: {
SET_MODULES(state, modules) {
state.myModules = modules;
},
TOGGLE_MODULE(state, id) {
state.myModules.some((el) => {
if (el.id === id) {
el.isActive = !el.isActive;
return true;
}
})
}
},
actions: {
getModules({
commit
}) {
return new Promise((fulfill) => {
setTimeout(() => {
commit('SET_MODULES', modules);
fulfill(modules);
}, 500)
});
}
}
});
const app = new Vue({
el: "#app",
store,
data: {},
methods: {
toggle(id) {
console.log(id);
this.$store.commit('TOGGLE_MODULE', id);
}
},
computed: {
myModules() {
return this.$store.state.myModules;
},
output() {
return JSON.stringify(this.myModules, null, 2);
},
},
mounted() {
this.$store.dispatch('getModules').then(() => console.log(this.myModules));
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.0.1/vuex.js"></script>
<script src="https://unpkg.com/vue"></script>
<div id="app">
<form>
<div v-for="data in myModules">
<label :for="data.id">{{ data.name }}: {{data.isActive}}</label>
<input type="checkbox" :id="data.id" :name="'checkbox-' + data.id" :checked="data.isActive" #change="toggle(data.id)">
</div>
</form>
<h3>Vuex state:</h3>
<pre v-text="output"></pre>
</div>
As you can see above you could just call a function on input change and pass an id as a parameter to a method that fires vuex action.
The old version of my code.
A new one on jsfiddle
I am completely new to both vue.js and Javascript. How do I dynamically create nav links from an Axios request?
I am wanting to follow what's being done in the item section which is currently static information, but i want to dynamically return links based on whats returned in the json request.
import * as types from '../../mutation-types'
import lazyLoading from './lazyLoading'
import charts from './charts'
// gathering items from API
const url = 'http://localhost:8080/items/'
data: {
items: []
},
mounted() {
axios.get(url).then(response => {
this.results = items.data
})
}
// Sidebar links are statically created here
const state = {
items: [
{
name: 'Dashboard',
path: '/dashboard',
meta: {
icon: 'fa-tachometer',
link: 'dashboard/index.vue'
},
component: lazyLoading('dashboard', true)
},
{
name: 'Axios',
path: '/axiosDemo',
meta: {
auth: true,
icon: 'fa-rocket',
link: 'axios/index.vue'
},
component: lazyLoading('axios', true)
},
charts,
]
}
const mutations = {
[types.EXPAND_MENU] (state, menuItem) {
if (menuItem.index > -1) {
if (state.items[menuItem.index] && state.items[menuItem.index].meta) {
state.items[menuItem.index].meta.expanded = menuItem.expanded
}
} else if (menuItem.item && 'expanded' in menuItem.item.meta) {
menuItem.item.meta.expanded = menuItem.expanded
}
}
}
export default {
state,
mutations
}
I think what I am wanting to do is something like this (python example):
items:
for i in items_payload:
{
name: i.name,
path: i.url,
meta: {
icon: 'fa-tachometer',
link: i.name+'/index.vue'
},
},
How do I best accomplish this in vue.js? Any help would be appreciated. Thanks.
If you are making the api request from a component, you can create a list of links like follows:
// for demo purposes, let's say links are returned as an array of objects
[
{
href: '/path/to/page1',
linkText: 'Page 1'
},
{
href: '/path/to/page2',
linkText: 'Page 2'
}
]
// MyComponent.vue
<template>
<div class="sidebar">
<ul>
<li v-for="(item, index) in items" :key="index">
<a :href="item.href">{{ item.linkText }}</a>
</li>
</ul>
</div>
</template>
export default {
data () {
return {
links: []
}
},
mounted() {
axios.get(url).then(response => {
this.links = items.data
})
}
}