I am trying to add Auth0 to a colleagues Vue app while he's on holiday but I am struggling with creating a reusable navbar.
I followed a tutorial to get most of it setup: https://www.storyblok.com/tp/how-to-auth0-vuejs-authentication
My main.js
import Vue from 'vue'
import App from './App.vue'
import BootstrapVue from 'bootstrap-vue'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
import router from './router'
import Vuex from 'vuex'
import store from './Store'
import 'vue-awesome/icons'
import icon from 'vue-awesome/components/Icon'
import auth from './auth/auth'
import meta from 'vue-meta'
Vue.config.devtools = true;
Vue.config.productionTip = false;
Vue.config.errorHandler = function(err, vm, info) {
console.log("error: " + err);
console.log("error info: " + info);
};
Vue.use(BootstrapVue);
Vue.use(Vuex);
Vue.use(auth);
Vue.use(meta)
Vue.component('icon', icon);
new Vue({
router,
store: store,
render: h => h(App)
}).$mount('#app');
My App.vue:
<template>
<b-container>
<div id="app">
<navbar/>
<router-view/>
</div>
</b-container>
</template>
<script>
import WidgetBuilder from './components/WidgetBuilder/WidgetBuilder'
import Navbar from './components/NavBar.vue'
export default {
name: 'app',
components: {
Navbar: Navbar,
WidgetBuilder: WidgetBuilder
}
}
</script>
My Navbar:
<template>
<nav class="navbar navbar-dark bg-dark">
<a class="navbar-brand" href="/home">
<img src="https://a.storyblok.com/f/39898/1024x1024/dea4e1b62d/vue-js_logo-svg.png" width="40" height="40">
</a>
<div>
<img :src="$auth.user.picture" width="30" height="30">
<span class="text-muted font-weight-light px-2">{{$auth.name}}</span>
<button type="button" class="btn btn-outline-secondary btn-sm" #click="$auth.logout()">Logout</button>
</div>
</nav>
</template>
<script>
export default {
name: "navbar",
mounted() {
console.log(this.$router.params.auth)
},
}
</script>
My Callback:
<template>
<div class="callback"></div>
</template>
<script>
export default {
name: 'callback',
metaInfo: {
title: 'Callback',
},
mounted() {
// eslint-disable-next-line
this.$auth.handleAuthentication().then((data) => {
this.$router.push({ name: 'index' })
})
}
}
</script>
The problem is, after loggin and redirection my navbar cannot access the $auth object.
console:
error: TypeError: Cannot read property 'picture' of null
main.js?1c90:18 error info: render
main.js?1c90:17 error: TypeError: Cannot read property 'name' of null
main.js?1c90:18 error info: render
If i visit /home directly it works fine.
I'm not a really a JS dev and this ES6 stuff might as well be in Arabic, any of you vue experts out there point me in the right direction?
I've tried passing variables from the component like this:
<template>
<b-container>
<div id="app">
<navbar :auth="$auth"/>
<router-view/>
</div>
</b-container>
</template>
I've tried importing the service into my Navbar component (although that grates me)
I've tried different redirects, re-authenticating, re-mouting it in the navbar component with mounted() and data().
Im sure its something simple but im tearing my hair out, because as i said it totally alien to me at the moment.
I also followed the tutorial and had the same issue.
Since the navbar component is in my App.vue and not in my home component I found that the toolbar was being rendered before the response from auth0 was being set to localStorage (hence why a refresh on second load worked). Moving the navbar component inside the home component fixed the issue, but this was not ideal for me.
I used the event bus approach as recommended by a similar stack overflow question -
Re-render navigation bar after login on vuejs
auth.js
user: {
get: function() {
return JSON.parse(localStorage.getItem('user'))
},
set: function(user) {
localStorage.setItem('user', JSON.stringify(user))
this.$bus.$emit('logged', 'User logged')
}
}
navbar.vue
data() {
user: this.getUser()
}
created () {
this.$bus.$on('logged', () => {
this.user = this.getUser()
})
},
methods: {
getUser: function () {
var user = JSON.parse(localStorage.getItem('user'));
if (user === null) {
return ''
} else {
return user
}
}
},
Not the cleanest solution but it worked for me.
The object this.$auth is provided by a custom plugin from Auth0, in the guide you can read how to install and use it:
https://www.storyblok.com/tp/how-to-auth0-vuejs-authentication#setup-auth0--vuejs-auth-plugin
Related
For the purpose of learning Vue, I am trying to create a very simple app that will return highlighted code from the input in real-time. I went through a few Prism tutorials and examples, but can't get this to work. Any help or guidance will be appreciated, as I am just getting started with Vue and I have a feeling that I am mixing something up.
This is HelloWorld.vue:
<template>
<div>
<h1>Prism Demo</h1>
<div id="editor">
<textarea v-model="message"></textarea>
<div>{{ highlighteddMessage }}</div>
</div>
</div>
</template>
<script>
import Prism from 'vue-prism-component'
export default {
data() {
return {
message: `var myFunction = function() {
statements
}`
};
},
computed: {
highlighteddMessage: function () {
return Prism.highlight(this.message, Prism.languages.js);
}
}
}
</script>
<style scoped>
...
</style>
And my main.js:
import Vue from 'vue'
import App from './App.vue'
import "prismjs";
import "prismjs/themes/prism-funky.css";
import "prismjs/components/prism-scss.min";
import "prismjs/plugins/autolinker/prism-autolinker.min";
import "prismjs/plugins/autolinker/prism-autolinker.css";
import Prism from "vue-prism-component";
Vue.component("prism", Prism);
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
I think the problem is in how I am trying to use Prism in the computed property, but I am unable to fix it. WIll appreciate any hints on correctly using Prism in Vue.
You should add Prism to your components option components:{Prism} and then in the template wrap the code with that component and no need to create a computed property :
<div>
<h1>Prism Demo</h1>
<div id="editor">
<textarea v-model="message"></textarea>
<prism language="javascript">{{ message }}</prism>
</div>
</div>
</template>
<script>
import Prism from 'vue-prism-component'
export default {
data() {
return {
message: `var myFunction = function() {
statements
}`
};
},
components:{
Prism
}
}
</script>
I have some views/components; Home, ScrapeResults, and Search. To help y'all follow what is going on, here are some snippets of code that are relevant to my issue.
Snippet of App.vue
<template>
<v-app-bar>
<Search #selected_card="scrape"/>
</v-app-bar>
<v-content>
<v-container fluid style="width: 80%;">
<router-view :card="card"></router-view> //card prop is in ScrapeResults.vue
</v-container>
</v-content>
</template>
<script>
data() {
return {
card: {}
}
}
methods: {
scrape(card) { //card parameter value is from $emit event from Search
if(this.$router.currentRoute.name != 'scrape-results') {
this.$router.push({name: 'scrape-results', params: { card: card } });
}
else {
this.card = card;
console.log(this.card)
}
}
}
</script>
Snippet of Home.vue
<template>
<div id="home">
<h1>Home</h1>
<Search id="search" #selected_card="scrape"/>
</div>
</template>
<script>
methods: {
scrape(card) { //card parameter value is from $emit event from Search
this.$router.push({ name: 'scrape-results', params: { card: card } });
}
}
</script>
Snippet of ScrapeResults:
<template>
<div id="home">
<h1>Scrape Results</h1>
<p> {{card.name}} - {{card.set_name}} </p>
<v-img :src="card.img_url"></v-img>
</div>
</template>
<script>
...... //irrelevant stuff
props: {
card: Object
},
watch: {
card() {
console.log(this.card)
}
}
</script>
Paths within the app:
/ (Home)
/scrape-results (ScrapeResults)
So how the app work is the user will search for a card (Object) and select from a rendered list. Once the user selects a card from the rendered list, then the user is redirected to the Scrape Results view. All of that works fine when done from Home.vue.
My issue: If the user accessed Scrape Results from Home.vue, and while still on Scrape Results view, if the user were to search for another card via the Search component that is mounted (in the app bar) in App.vue, then the card prop in ScrapeResults.vue doesn't update. The weird thing is that if I manually navigate to Scrape Results by entering /scrape-results in the address bar, search for a card via the Search component in the app bar, then the card prop updates. The card prop will only update if I manually access /scrape-results and then search for a card.
I know all of that sounds confusing, so here's a clip I recorded to that demonstrates what my issue is. Link: https://www.youtube.com/watch?v=aO9qSTa9CCk&feature=youtu.be&hd=1
EDIT: Route definition below
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '#/views/Home'
import ScrapeResults from '#/views/ScrapeResults'
Vue.use(VueRouter);
const router = new VueRouter({
mode: 'history',
routes: [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/scrape-results',
name: 'scrape-results',
component: ScrapeResults,
props: true
}
]
});
export default router;
So, instead of trying to make passing a prop to router-view work, I just ended up changing the ScrapeResults path in my router.js to /scrape-results/:card_set/:card_name; original was just /scrape-results. I then just pushed extra params in $router.push
this.$router.push({name: 'scrape-results', params: { card: card, card_set: card.set_name, card_name: card.name } });
I am very new to the Vue framework, as well as Javascript, but am currently building a site using Vue and I want to have some links at the top of my site that the user can navigate to. I have tried using the Vue Router (https://router.vuejs.org/guide/#javascript) in order to make these links. At this point, I just want to make a little 'About Us' section that the user can navigate to. But, despite the URL changing accordingly to 'localhost:8080/#/about_us', the Vue component that I have associated with the link will not show up.
I have structured my code in the main.js as such:
import Vue from 'vue'
import VueRouter from 'vue-router'
import App from './App.vue'
Vue.config.productionTip = false
export const eventBus = new Vue();
Vue.use(VueRouter);
const AboutUs = {template: '<div>about_us</div>'};
const route = [{path:'/about_us', component: AboutUs}];
const router= new VueRouter({route});
new Vue({
render: h => h(App),
router
}).$mount('#app')
And then I have my app.vue designed as (note: I reduced much of the code to its essentials for brevity):
import AboutUs from './components/AboutUs.vue'
import { eventBus } from './main.js'
export default {
data(){
return {
films: []
}
},
components: {
"about-us": AboutUs
},
mounted(){
fetch('https://ghibliapi.herokuapp.com/films')
.then(res => res.json())
.then(films => this.films = films)
.catch(error=> console.log(error))
}
}
</script>
body {
background-color: deepskyblue;
}
<h1>Ghibli Fandom Extravaganza</h1>
<nav>
<li><router-link to="/about_us">About us </router-link></li>
<router-view></router-view>
</nav>
<p>List of Ghibli Movies: <films-list :films="films"/></p>
<film-detail />
At this point, my AboutUs component is only a very basic Vue that shows some information about the site in some simple HTML tags. But although the link is active and does work, the information from the Vue is not displayed, while the other Vue components continue to show, which indicates that maybe they are not connected? I have tried to follow the tutorial in the Vue Router site, but I don't think that I understand the mechanics of how the code actually works. Can anybody recommend me any corrections?
UPDATE:
Here is the code to my AboutUs.vue
<template>
<div>
<h1>This site is for examining the movies of Studio Ghibli</h1>
</div>
</template>
<script>
export default {
name: 'about-us'
}
</script>
<style scoped>
</style>
I think there is no need to import 'aboutus' component. You can just write like this <router-link to="about_us">About us </router-link>
and in the main.js declare the route like this
const route = [{path:'/about_us',name:'about_us', component: () => import("path to about us file")}];
The code samples you provided are a bit confusing, you should simply pass an imported view straight in to the component property of a router entry.
Where you have done:
const AboutUs = {template: '<div>about_us</div>'};
Replace that line with:
import AboutUs from './components/AboutUs.vue'
I can't figure out from your sample, when and what the relevance of components: {"about-us": AboutUs }, it is not needed.
Here is a sample of my setup:
router.js
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'index',
component: () => import('../components/views/welcome')
},
{
path: '/about-us',
name: 'about-us',
component: () => import('../components/views/about-us')
}
]
const router = new VueRouter({
mode: 'history',
routes
})
export default router
main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App)
}).$mount('#app')
App.vue
<template>
<v-app v-cloak>
<router-link :to="{ name: 'index' }">Welcome</router-link>
<router-link :to="{ name: 'about-us' }">About Us</router-link>
<router-view></router-view>
</v-app>
</template>
<script>
export default {
name: 'App'
}
</script>
components/views/about-us.vue
<template>
<div>This is the About Us page!</div>
</template>
<script>
export default {
name: 'about-us'
}
</script>
This sample uses History Mode
Other things to note
When routing, mounted is unreliable, instead you should place any fetch logic into it's own method when calling any :
methods: {
fetch () {
// https://github.com/axios/axios
axios.get('https://ghibliapi.herokuapp.com/films').then( ... )
}
}
Call this.fetch method in both beforeRouteUpdate and beforeRouteEnter instead of mounted, you can't even rely on created when it comes to views handled by vue-router.
Axios is suggested instead of native fetch because axios provides more functionality, features and browser compatibility.
In about-us.vue you add these Navigation Guards like so:
<template>
<div>This is the About Us page!</div>
</template>
<script>
export default {
name: 'about-us'
methods: {
fetch () {
axios.get('https://ghibliapi.herokuapp.com/films').then( ... )
}
}
// Will fire if you are already on the view but a parameter changes (dynamic routing)
beforeRouteUpdate(to, from, next) {
this.fetch()
next()
},
// Will fire when you enter the view
beforeRouteEnter(to, from, next) {
this.fetch()
next()
},
}
</script>
Both should be added, understand that they won't fire at the same time, only one of them will execute fetch once when relevant.
This will resolve any issues you would otherwise encounter with Dynamic Routing should you ever use them.
Folder Structure
src/
+ App.vue
+ main.js
+ router.js
+ vue.config.js
+ assets/
+ logo.png
+ components/
+ views/
+ welcome.vue
+ about-us.vue
Hope this clears up the setup requirement for you.
I'm trying to setup Vue router for the first time and I'm running into trouble.
router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import Services from '../components/Services'
import App from '../app'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'App',
component: App
},
{
path: '/services',
name: 'Services',
component: Services
}
]
})
app.vue
<template>
<div id='app'>
<Navigation></Navigation>
<div class="Site-content">
<router-view></router-view>
</div>
<Footer></Footer>
</div>
</template>
<script>
import Services from "../javascript/components/Services";
import Footer from "../javascript/components/Footer";
import Navigation from "../javascript/components/Navigation";
export default {
components: {
Footer,
Navigation,
Services
},
data: function () {
return {
message: "Welcome to Ping Party From Vue!"
}
}
}
</script>
Navigation.vue
<template>
<div id="navigation">
<nav v-bind:class="active" v-on:click>
Home
Projects
<router-link to="/services">Services</router-link>
Contact
</nav>
</div>
</template>
<script>
import Services from './Services'
export default {
data () {
return { active: 'home' }
},
methods: {
makeActive: function(item) {
this.active = item;
}
}
}
</script>
That vue-router option is not working in my navigation. It shows up on the page but it's not clickable and I'm getting this error in the console.
ERROR
Unknown custom element: <router-link> - did you register the component
correctly? For recursive components, make sure to provide the "name"
option.
found in
---> <Navigation> at app/javascript/components/Navigation.vue
<App> at app/javascript/app.vue
<Root>
Unknown custom element: <router-view> - did you register the component
correctly? For recursive components, make sure to provide the "name"
option.
found in
---> <App> at app/javascript/app.vue
Make sure to register your router with your Vue instance.
So in your
import router from './router'
new Vue({
el: '#some-element'
router, // This line is important
render: h => h(App)
})
I have two components and I want to display what the user enters in one on the other component. I don't really want to use a state manager like vuex because it's probably a bit overkill as it's a small application
this is my main.js:
import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router';
import { routes }from './routes';
export const EventBus = new Vue();
Vue.use(VueRouter);
const router = new VueRouter({
routes,
mode: 'history'
});
new Vue({
el: '#app',
router,
render: h => h(App)
})
Component that emits the event called addHtml.vue
<template>
<div>
<h1>Add HTML</h1>
<hr>
<button #click="navigateToHome" class="btn btn-primary">Go to Library</button>
<hr>
Title <input type="text" v-model="title">
<button #click="emitGlobalClickEvent()">Press me</button>
</div>
</template>
<script>
import { EventBus } from '../../main.js'
export default {
data: function () {
return {
title: ''
}
},
methods: {
navigateToHome() {
this.$router.push('/');
},
emitGlobalClickEvent() {
console.log(this.title);
EventBus.$emit('titleChanged', this.title);
}
}
}
</script>
the file that listens for the event thats emitted and to display what was entered on the other component:
<template>
<div>
<h1>Existing Items</h1>
<hr>
<p>{{ test }}</p>
</div>
</template>
<script>
import { EventBus } from '../main.js';
export default {
data: function () {
return {
test: ''
}
},
created() {
EventBus.$on('titleChanged', (data) => {
console.log('in here!',data);
this.test = data;
});
}
}
</script>
the console.log('in here!',data); inside the listener gets printed out to the console so I know it's picking it up however {{ test }} doesn't get updated to what the user enters when I click back onto the component to view if it was updated, it just remains blank? Any Ideas?
If you are using vue-router to display the secound component. The reason might be that you just see a new instance of that component everytime and the value of test will be reseted when the component is destroyed. You can bind it to a (global) variable to persist test: window.yourApp.test. Maybe webpack will mourn but it is possible. Even eslint has an ignore comment for such cases.
It is like Reiner said, because the component gets destroyed once you switch pages. Try wrapping your router-view inside a keep-alive:
<keep-alive>
<router-view><router-view>
</keep-alive>
Also. If you want to keep the state of just one specific component / page you can use the include tag:
<keep-alive include='name of component'>
<router-view></router-view>
</keep-alive>