When I log out I delete the token. Here is how I check if the token is deleted:
const isLoggedOut = () => {
let token = window.localStorage.getItem('token')
if (token === undefined || token === null || token.length === 0) {
console.log("user is logged out")
return true
}
else {
console.log("user is logged in")
return false
}
}
Inside the navbar I want the Login button to be hidden if the user is logged in.
This is how the navbar looks like:
<nav class="navbar navbar-expand-sm bg-dark navbar-dark">
<router-link class="navbar-brand" :to="{ name: 'Home' }">Home</router-link>
<ul class="navbar-nav">
<li class="nav-item">
<router-link class="text-white" :to="{ name: 'Login' }" v-if="isLoggedOut">Login</router-link>
</li>
<li class="nav-item">
<router-link class="text-white ml-2" :to="{ name: 'Register' }">Register</router-link>
</li>
<li class="nav-item">
<router-link class="text-white ml-2" :to="{ name: 'Dashboard' }">Dashboard</router-link>
</li>
<li class="nav-item">
<button class="text-white ml-2" #click="logOut">Logout</button>
</li>
</ul>
</nav>
Later edit, this is the App.vue file without the styles. I log in and Login button still appears in the navbar.
<script setup>
import { reactive,ref, computed } from 'vue'
import { useRouter } from "vue-router"
const router = useRouter()
const token = ref(null);
const setToken = (token) => {
token.value = token;
window.localStorage.setItem('token', token)
}
const clearToken = () => {
token.value = null;
window.localStorage.removeItem('token')
}
const isLoggedOut = computed(() => token.value === null)
const logOut = () => {
clearToken()
router.push({name:'Login'})
}
</script>
<template>
<nav class="navbar navbar-expand-sm bg-dark navbar-dark">
<router-link class="navbar-brand" :to="{ name: 'Home' }">Home</router-link>
<ul class="navbar-nav">
<!-- Show Login button only if user is logged out -->
<li class="nav-item">
<router-link class="text-white" :to="{ name: 'Login' }" v-if="isLoggedOut">Login</router-link>
</li>
<li class="nav-item">
<router-link class="text-white ml-2" :to="{ name: 'Register' }">Register</router-link>
</li>
<li class="nav-item">
<router-link class="text-white ml-2" :to="{ name: 'Dashboard' }">Dashboard</router-link>
</li>
<li class="nav-item">
<button class="text-white ml-2" #click="logOut">Logout</button>
</li>
</ul>
</nav>
<router-view/>
</template>
window.localStorage.getItem('token') is not reactive. If the value changes, you will not be notified.
Instead of getting logged in state from localStorage, you can manage it through internal state which will then manage the local storage.
for example:
const token = ref(null);
const setToken = (value) => {
token.value = value;
window.localStorage.setItem('token', token)
}
const clearToken = () => {
token.value = null;
window.localStorage.removeItem('token')
}
const isLoggedIn = computed(()=>token.value !== null)
then you can use isLoggedIn as a reactive (computed) value.
You can store this in a separate auth.js and export the methods and token.
example
The button is still visible because the component (App.vue in your case) is not reloaded, meaning the check whether you have a token or not is not performed, respectively the computed is not going to be updated because as mentioned above window.localStorage is not reactive.
One way to overcome this is to use a store (pinia/vuex/composable that will hold the value all the time) and router hooks, like beforeRouteEnter or the global one - beforeEach.
You can also try to make localStoragereactive like in this example here
Which might be overkill.
Just make sure you moved token functions and operations in a separated file (or even as a composable)
Related
I use this code to check if user token is correct, and put user into req.locals
exports.isLoggedIn = async (req, res, next) => {
if (req.cookies.jwt) {
try {
const decoded = await promisify(jwt.verify)(
req.cookies.jwt,
process.env.JWT_SECRET
);
const currentUser = await User.findById(decoded.id);
if (!currentUser) {
return next();
}
if (currentUser.changedPasswordAfter(decoded.iat)) {
return next();
}
res.locals.user = currentUser;
return next();
} catch (err) {
return next();
}
}
next();
};
After that, I render the view. This is my header, name of the user should be displayed here, but it seems to be empty. There is a user, and it is not empty, but for some reason i cannot access directly his name using hbs
<nav class="navbar navbar-dark navbar-expand p-0 bg-dark">
<div class="container">
{{#if user}}
<ul class="navbar-nav d-none d-md-flex mr-auto">
<li class="nav-item"><a class="nav-link logout" href="/users/logout" data-abc="true">Log Out</a></li>
</ul>
<ul class="navbar-nav d-flex align-items-center">
<li class="nav-item">
<div class="d-flex flex-row"><img src="https://i.imgur.com/EYFtR83.jpg" class="rounded-circle"
width="30"></div>
</li>
<li class="nav-item"><a href="#" class="nav-link d-flex align-items-center" data-abc="true">
<span>{{user.name}}</span>
<i class='bx bxs-chevron-down'></i></a>
</li>
</ul>
{{else}}
<ul class="navbar-nav d-none d-md-flex mr-auto">
<li class="nav-item"><a class="nav-link" href="/users/login" data-abc="true">Log In</a></li>
<li class="nav-item"><a class="nav-link" href="/users/signup" data-abc="true">Sign Up</a></li>
</ul>
{{/if}}
</div>
Any ideas?
UPD
Added hbs configuration and controller function that renders page
app.set('view engine', 'hbs');
hbs.registerPartials(__dirname + "/views/partials");
app.engine("hbs", engine ({
layoutsDir: "views/layouts",
defaultLayout: "layout",
extname: "hbs"
}
));
Also, here is controller method, that renders page
exports.getHomePage = async (req, res, next) => {
res.render('home');
};
Views in Express don't have access to the request object. They only have access to the data in the object passed as the locals parameter:
const locals = { put: "some data here" };
res.render('home', locals);
You aren't passing any data at all.
When I click sub menu in the following template, how do I add an active class to the <a> of a sibling <router-link> (of menu1 or menu2)?
<ul class="depth1">
<li class="m1">
<router-link to="/introduce/Introduce" #click="selected = 1" :class="{ active: selected == 1 }"><span>menu1</span></router-link>
<ul class="depth2 sm1">
<li><router-link to="" #click="selected = 1">sub menu</router-link></li>
<li><router-link to="" #click="selected = 1">sub menu</router-link></li>
<li><router-link to="" #click="selected = 1">sub menu</router-link></li>
</ul>
</li>
<li class="m2">
<router-link to="/introduce/Introduce" #click="selected = 2" :class="{ active: selected == 2 }"><span>menu2</span></router-link>
<ul class="depth2 sm1">
<li><router-link to="" #click="selected = 2">sub menu</router-link></li>
<li><router-link to="" #click="selected = 2">sub menu</router-link></li>
<li><router-link to="" #click="selected = 2">sub menu</router-link></li>
</ul>
</li>
</ul>
<script>
export default {
data: function () {
return {
selected: false,
};
},
methods: {
},
};
</script>
Your attempt is close to working, but the click handler isn't called because it's not applied correctly.
To add a click handler to the root element of <router-link> (i.e., the <a> tag), use #click.native:
<router-link to="" #click.native="selected = 1">sub menu</router-link>
^^^^^^^
but, why not use vue-router's default behaviors (link automatically get active class when the target route is active) like the below demo shows
const Home = {
template: '<div><h2>Home</h2></div>'
}
const About = {
template: '<div><h2>About</h2></div>'
}
const Users = {
template: `
<div>
<h2>Users</h2>
<router-view></router-view>
</div>
`
}
const User = {
template: '<div>{{ $route.params.username }}</div>'
}
const router = new VueRouter({
mode: 'history',
routes: [{
path: '/',
component: Home
},
{
path: '/about',
component: About
},
{
path: '/users',
component: Users,
children: [{
path: ':username',
name: 'user',
component: User
}]
}
]
})
new Vue({
router,
}).$mount('#app')
a.router-link-active {
color: #f66;
}
li.router-link-active a {
color: #f66;
}
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<h1>Active Links</h1>
<ul>
<li>
<router-link to="/">/</router-link>
</li>
<li>
<router-link to="/" exact>/ (exact match)</router-link>
</li>
<li>
<router-link to="/users">/users</router-link>
</li>
<li>
<router-link to="/users" exact>/users (exact match)</router-link>
</li>
<li>
<router-link to="/users/evan">/users/evan</router-link>
</li>
<li>
<router-link to="/users/evan#foo">/users/evan#foo</router-link>
</li>
<li>
<router-link :to="{ path: '/users/evan', query: { foo: 'bar' }}">
/users/evan?foo=bar
</router-link>
</li>
<li>
<!-- #635 -->
<router-link :to="{ name: 'user', params: { username: 'evan' }, query: { foo: 'bar' }}" exact>
/users/evan?foo=bar (named view + exact match)
</router-link>
</li>
<li>
<router-link :to="{ path: '/users/evan', query: { foo: 'bar', baz: 'qux' }}">
/users/evan?foo=bar&baz=qux
</router-link>
</li>
<li>
<router-link to="/about">/about</router-link>
</li>
<router-link tag="li" to="/about">
<a>/about (active class on outer element)</a>
</router-link>
</ul>
<router-view class="view"></router-view>
</div>
I am new to Vue and straggling with generating sidebar list where each list element on click open its components. I am using vue router. I understand the theory how it should work, but obviously I am missing something because I can't solve it. I don't know how to dynamically change the path.
I generated sidebar list using router-link
<template>
<div class="sidebarListItems">
<router-link href="#"
:to="calcRut"
class="list-group-item list-group-item-action bg-light"
v-for="title in mapTitles"
:key="title.map"
:title="title.map"
#click="callMap">
{{title.map}}
</router-link>
</div>
</template>
<script>
import bus from "./js/bus"
import mapNames from "./json/popis_karata.json"
export default {
name: "PageSidebarList",
props: ["title"],
data(){
return {
mapTitles:mapNames,
ruth=""
}
},
methods: {
callMap(event){
bus.$emit("openMap")
},
calcRuth(){
for (var i=0; i<this.routes.length; i++){
var newruth = this.routes[i].path
this.ruth = newruth
}
}
},
computed: {
routes(){
return this.$router.options.routes
}
}
When I put my path directly as a string (:to="/zup" or :to="/reg") it's working, but I would like that those paths are dynamically generated depending on which list element I clicked.
Here's how I do it. Try extracting the v-for on the level above. If you don't want to use an actual element, try <template>
<ul class="flex flex-col w-full space-y-1">
<li v-for="item in items" :key="item.link">
<router-link class="flex items-center h-8 px-4 rounded-lg hover:bg-white" :class="{ 'bg-white': routeMatches(item) }" :to="{ name: item.link }">
<div class="text-sm text-gray-700">{{ item.name }}</div>
</router-link>
</li>
</ul>
Edit, also format your to="" correctly to be :to="{name: 'namehere'}"
how about this solution with a switch and a programmatic router change
<template>
<div id="app">
<ul class="nav">
<li class="nav-item" #click="routeChanger('home')">Home</li>
<li class="nav-item" #click="routeChanger('page1')">Page1</li>
<li class="nav-item" #click="routeChanger('page2')">Page2</li>
<li class="nav-item" #click="routeChanger('page3')">Page3</li>
<li class="nav-item" #click="routeChanger('page4')">Page4</li>
</ul>
</div>
</template>
<script>
export default {
name: "App",
methods: {
routeChanger(routeParam) {
switch (routeParam) {
case "home":
this.$router.push("/home");
break;
case "page1":
this.$router.push("/page1");
break;
//...
default:
this.$router.push("/404");
break;
}
}
}
};
</script>
<style>
</style>
Maybe my answer would be useful to someone so I am posting my solution.
I didn't found solution to loop separately navbar elements from one data file and router-lik :to path attribute from routes file.
It work if I use routes file for everything.
<router-link
v-for="route in $router.options.routes"
:key="route.path"
:to="route.path"
class="list-group-item list-group-item-action bg-light">
{{route.title}}
</router-link>
I'm trying to create a simple reactive navigation based on if a user is authenticated or not. A login method on a login view sets a token in localstorage. If set, I want to display the logout button. I've tried computed, standard methods and props to no avail.
When I login, the navigation does not update. HOWEVER, if I refresh the page (reinitialize the app), it does show the logout button.
What exactly am I doing incorrectly?
I have been trying for hours to grasp the concept of Vue JS and am on the brink of quitting. What would have taken me minutes to do server side has taken me hours client side. WHERE IS THE REACTIVITY?
Nav.vue
<template>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<a class="navbar-brand" href="#">App</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarColor01" aria-controls="navbarColor01" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarColor01">
<ul class="navbar-nav">
<li class="nav-item active">
<a class="nav-link" href="#">
<router-link to="/">Home</router-link>
<span class="sr-only">(current)</span>
</a>
</li>
<li class="nav-item">
<router-link to="/about" class="nav-link">About</router-link>
</li>
</ul>
<ul class="navbar-nav ml-auto">
<li class="nav-item" v-if="hasAuth()"><a #click="logout()" class="nav-link">Log Out</a></li>
<template v-else>
<li class="nav-item">
<router-link to="/register" class="nav-link">Register</router-link>
</li>
<li class="nav-item">
<router-link to="/login" class="nav-link">Login</router-link>
</li>
</template>
</ul>
</div>
</nav>
</template>
<script>
export default {
name: 'Nav',
data: () => {
return {
auth: false
}
},
methods: {
logout: function () {
localStorage.removeItem('user-token');
this.$router.push({ path: 'login' });
},
hasAuth: function () {
this.auth = (localStorage.getItem('user-token')) ? true : false;
return this.auth
}
},
};
</script>
App.vue
<template>
<div id="app">
<Nav></Nav>
<router-view/>
</div>
</template>
<script>
import Nav from '#/components/Nav.vue';
export default {
components: {
Nav,
},
}
</script>
While Vue.js is reactive, localStorage is not. Vue cannot possibly know if the localStorage is modified or not. There is no local change event available with local storage.
To solve this problem, use Vuex combined with Local Storage for persistent data. The point where you save the token to local storage, at that time also save a copy inside Vuex store.
Another component like Nav should read data from Vuex store which is reactive. When you refresh the page, initialize Vuex store with the data from localStorage.
This way you get a perfect reactive system.
Here is my replies.vue file. Here what i am doing is when this replies component is created, i call a fetch() method that loads some data from the server:
//Replies.vue
<template>
<div>
<div v-for="(reply, index) in replies_with_pagination.data">
<reply :attributes="reply" :key="reply.id" :current_user_id="auth_user_id" #iamdeleted="updateCollection(index)"></reply>
</div>
<pagination :current_paginated_dataSet="replies_with_pagination"></pagination>
</div>
</template>
<script>
import Reply from './Reply.vue';
export default{
components: { Reply },
props: ['all_replies','auth_user_id','thread_id'],
data() {
return {
all_reply_items: this.all_replies,
replies_with_pagination: '',
}
},
methods: {
fetch: function(){
let vm = this;
axios.get(route('replies.paginated',{ 'thread' : this.thread_id
}))
.then(function(serverResponse){
vm.replies_with_pagination = serverResponse.data;
});
},
},
created() {
this.fetch();
}
And then i am passing that to the pagination component via the current_paginated_dataSet props. Here is the code for pagination.vue
//Pagination.vue
<template>
<div>
<nav aria-label="...">
<ul class="pagination">
<li class="page-item disabled" v-show="current.prev_page_url">
<a class="page-link" href="#" tabindex="-1">Previous</a>
</li>
<li class="page-item"><a class="page-link" href="#">1</a></li>
<li class="page-item active">
<a class="page-link" href="#">2 <span class="sr-only">(current)</span></a>
</li>
<li class="page-item"><a class="page-link" href="#">3</a></li>
<li class="page-item" v-show="current.next_page_url">
<a class="page-link" href="#">Next</a>
</li>
</ul>
</nav>
</div>
</template>
<script>
export default{
props: ['current_paginated_dataSet'],
data() {
return {
current: this.current_paginated_dataSet,
}
},
}
</script>
As you can see i am using this property to initialize a data called current in the pagination component. This current_paginated_dataSet property was initialized at the replies component to the value of FALSE, which, upon fetching the data from server was then reset to some object returned by the server.
My problem is the previous and next buttons on the pagination component are not visible. It seems the current data property on the pagination component is not getting the updated value from the replies component which updates after loading data from server. It is always set to the intial value of FALSE that it received from parent root component.
What am i doing wrong here?
EDIT
it works if instead of referring to the current data property i directly refer to the current_paginated_dataSet props in the previous and next buttons, like so:
// this works:
<li class="page-item disabled" v-show="current_paginated_dataSet.prev_page_url">
<a class="page-link" href="#" tabindex="-1">Previous</a>
</li>
But i want to achieve this by referring to the data property current. How can i do that?