Synchronize variable between router in vue.js - javascript

I'd like to change value of a variable in a router-view by changing other variable in different routre-view synchronously. I wrote code like below to change variable isFoo in header and catch it in side bar, but it fails.
App.vue:
<template>
<v-app id="app">
<router-view name="sidebar"></router-view>
<router-view name="header"></router-view>
<router-view name="main"></router-view>
<router-view name="footer"></router-view>
</v-app>
</template>
<script>
export default {
name: 'app',
isFoo: false
}
</script>
and Sidebar.vue:
<template>
<div id="sidebar" :isOpen="isFoo"></div>
</template>
<script>
export default {
name: 'sidebar',
data () {
return {isFoo: this.$parent.$options.isFoo}
}
}
</script>
Header.vue:
<template>
<button v-on:click="foo()">Button</button>
</template>
<script>
export default {
name: 'header',
methods: {
foo: () => {
this.$parent.$options.isFoo = !this.$parent.$options.isFoo
}
}
}
</script>

Your question is essentially about how to share state across multiple components of your app, and is quite general.
Your code does not work because you have copied isFoo across your components instead of just referencing a single source of truth for that data. Also you should specify reactive data in the data property of each component, not directly within the $options of the component.
I've fixed your code to make it work:
const Header = {
template: '<button #click="$parent.isFoo = true">Click Me</button>'
}
const Sidebar = {
template: '<div>Sidebar: {{ $parent.isFoo }}</div>'
}
const router = new VueRouter({
routes: [
{
path: '/',
components: {
header: Header,
sidebar: Sidebar
}
}
]
})
new Vue({
router,
el: '#app',
data: {
isFoo: false
}
})
<script src="https://rawgit.com/vuejs/vue/dev/dist/vue.js"></script>
<script src="https://rawgit.com/vuejs/vue-router/dev/dist/vue-router.js"></script>
<div id="app">
<router-view name="header"></router-view>
<router-view name="sidebar"></router-view>
</div>
However I do not recommend this approach. You really shouldn't be accessing this.$parent because it tightly couples the components.
I'm not going to go into detail about better ways of doing this because there are lots of SO questions which cover this topic.

Related

Update props in component through router-view in vue js

I want to pass a boolean value from one of my views in a router-view up to the root App.vue and then on to one of the components in the App.vue. I was able to do it but I am facing an error:
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "drawer"
Below is my code:
Home.vue:
<template>
<div class="home" v-on:click="updateDrawer">
<img src="...">
</div>
</template>
<script>
export default {
name: "Home",
methods:{
updateDrawer:function(){
this.$emit('updateDrawer', true)
}
};
</script>
The above view is in the router-view and I am getting the value in the App.vue below:
<template>
<v-app class="">
<Navbar v-bind:drawer="drawer" />
<v-main class=" main-bg">
<main class="">
<router-view v-on:updateDrawer="changeDrawer($event)"></router-view>
</main>
</v-main>
</v-app>
</template>
<script>
import Navbar from '#/components/Navbar'
export default {
name: 'App',
components: {Navbar},
data() {
return {
drawer: false
}
},
methods:{
changeDrawer:function(drawz){
this.drawer = drawz;
}
},
};
</script>
I am sending the value of drawer by binding it in the navbar component.
Navbar.vue:
<template>
<nav>
<v-app-bar app fixed class="white">
<v-app-bar-nav-icon
class="black--text"
#click="drawer = !drawer"
></v-app-bar-nav-icon>
</v-app-bar>
<v-navigation-drawer
temporary
v-model="drawer"
>
...
</v-navigation-drawer>
</nav>
</template>
<script>
export default {
props:{
drawer:{
type: Boolean
}
},
};
</script>
This will work one time and then it will give me the above error. I appreciate if any one can explain what should I do and how to resolve this issue.
In Navbar.vue you are taking the property "drawer" and whenever someone clicks on "v-app-bar-nav-icon" you change drawer to be !drawer.
The problem here is that you are mutating (changing the value) of a property from the child side (Navbar.vue is the child).
That's not how Vue works, props are only used to pass data down from parent to child (App.vue is parent and Navbar.vue is child) and never the other way around.
The right approach here would be: every time, in Navbar.vue, that you want to change the value of "drawer" you should emit an event just like you do in Home.vue.
Then you can listen for that event in App.vue and change the drawer variable accordingly.
Example:
Navbar.vue
<v-app-bar-nav-icon
class="black--text"
#click="$emit('changedrawer', !drawer)"
></v-app-bar-nav-icon>
App.vue
<Navbar v-bind:drawer="drawer" #changedrawer="changeDrawer"/>
I originally missed the fact that you also used v-model="drawer" in Navbar.vue.
v-model also changes the value of drawer, which again is something we don't want.
We can solve this by splitting up the v-model into v-on:input and :value like so:
<v-navigation-drawer
temporary
:value="drawer"
#input="val => $emit('changedrawer', val)"
>
Some sort of central state management such as Vuex would also be great in this scenario ;)
What you could do, is to watch for changes on the drawer prop in Navbar.vue, like so:
<template>
<nav>
<v-app-bar app fixed class="white">
<v-app-bar-nav-icon
class="black--text"
#click="switchDrawer"
/>
</v-app-bar>
<v-navigation-drawer
temporary
v-model="navbarDrawer"
>
...
</v-navigation-drawer>
</nav>
</template>
<script>
export default {
data () {
return {
navbarDrawer: false
}
},
props: {
drawer: {
type: Boolean
}
},
watch: {
drawer (val) {
this.navbarDrawer = val
}
},
methods: {
switchDrawer () {
this.navbarDrawer = !this.navbarDrawer
}
}
}
</script>
Home.vue
<template>
<div class="home">
<v-btn #click="updateDrawer">Updrate drawer</v-btn>
</div>
</template>
<script>
export default {
name: 'Home',
data () {
return {
homeDrawer: false
}
},
methods: {
updateDrawer: function () {
this.homeDrawer = !this.homeDrawer
this.$emit('updateDrawer', this.homeDrawer)
}
}
}
</script>
App.vue
<template>
<v-app class="">
<Navbar :drawer="drawer" />
<v-main class="main-bg">
<main class="">
<router-view #updateDrawer="changeDrawer"></router-view>
</main>
</v-main>
</v-app>
</template>
<script>
import Navbar from '#/components/Navbar'
export default {
name: 'App',
components: { Navbar },
data () {
return {
drawer: false
}
},
methods: {
changeDrawer (newDrawer) {
this.drawer = newDrawer
}
}
}
</script>
This doesn't mutate the drawer prop, but does respond to changes.

Accessing instance data in Vue from Controller or Components

I'm using the Vue controller to update the content on my SPA. While I can import views and controllers and display them accordingly, I'm having a hard time updating some variables defined in the main Vue instance. I also tried to define them in the main view but I still cannot update them.
Here's my Vue instance:
new Vue({
router,
render: h => h(App),
data: () => {
return {
showYesOrNo: false,
showMultiChoice: false
}
}
}).$mount('#app')
This is my main view:
<script type="text/javascript">
import ContactContainer from "./views/ContactContainer.vue"
export default {
name: "app",
components:{
ContactContainer
},
data: () => {
return { // Redefined the variables here to see if I could access them in some way
showYesOrNo: false,
showMultiChoice: false
}
}
}
</script>
<template>
<div id="app">
<div class="container">
<div id="main_container">
<transition name="fade"><router-view name="YesOrNo" v-show="!showMultiChoice"/></transition>
<transition name="fade"><router-view name="MultiChoice" v-show="!showYesOrNo"/></transition>
<transition name="fade"><ContactContainer name="Contact" v-show="!showMultiChoice && !showYesOrNo"></ContactContainer></transition>
</div>
</section>
</div>
</div>
</template>
Now this is how I try to access the ShowYesOrNo and ShowMultiChoice variables. First in the controller:
const routes = [
{
path: '/YesOrNo',
components: {
YesOrNo: YesOrNoVue,
MultiChoice: MultiChoiceContainer
},
beforeEnter: (to, from, next) => {
console.log(this); //undefined
console.log(this.$parent.showYesOrNo) //undefined
console.log(vm.showYesOrNo) //undefined
next();
}
}
]
Then in the YesOrNo vue:
<template>
<!-- Some HTML -->
</template>
<script>
export default {
data: function(){
return {
name: 'YesOrNoVue'
}
},
created: function(){
console.log(this.showYesOrNo); //undefined
console.log(vm.showYesOrNo); //undefined
}
}
</script>
I'm a little confused with the visibility of these variables so I'd like to know what's the best approach to change them when the router-view is updated (i.e. when the vue is created).
Thanks.

How to convert a mention tag text to a <router-link> ? (VUEJS)

I have a body property in data
data(){
return{
body:'Hello im #username1 and #username2'
}
}
I want to convert each #user into a code below, where a user can click that link to go that url path.
<router-link :to="`/${username1}`">#{{username1}}</router-link>
What i tried
<span v-html='bodyReplaced'>
computed: {
bodyReplaced(){
return this.body.replace(
/#\w+/g,
(user) => '<router-link :to="`/${username1}`">#{{username1}}</router-link>'
)
}
}
What the code did:
Convert the string into router-link in the dom but not in the view
I dont know how to replace the # after the match, so i can use it in to="`/${username1}
I think you're looking for something like
<template v-for="part of body.split(/(#\w+)/g)">
<router-link v-if="part[0] == '#'" :to="`/${part.slice(1)}`">{{part}}</router-link>
<template v-else>{{part}}</template>
</template>
new Vue({
el: 'main',
data: {
body:'Hello im #username1 and #username2'
},
router: new VueRouter({
routes: []
}),
})
<main>
<template v-for="part of body.split(/(#\w+)/g)">
<router-link v-if="part[0] == '#'" :to="`/${part.slice(1)}`">{{part}}</router-link>
<template v-else>{{part}}</template>
</template>
</main>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
Create a component called mention and pass the user name as prop, i used the same approach of #Bergi, or you could replaced a computed property in which you replace the mention uder by a element which could be parsed not like router-link :
Vue.component('mention', {
template: `<router-link :to="'/'+user">#{{user}}</router-link>`,
props: ['user']
})
const Foo = {
template: '<div>im foo</div>'
}
const Bar = {
template: '<div>im bar</div>'
}
const routes = [{
path: '/foo',
component: Foo
},
{
path: '/bar',
component: Bar
}
]
const router = new VueRouter({
routes, // short for `routes: routes`,
})
// 4. Create and mount the root instance.
// Make sure to inject the router with the router option to make the
// whole app router-aware.
const app = new Vue({
router,
data() {
return {
body: 'Hello im #foo and #bar'
}
},
computed: {
tokens() {
return this.body.split(' ');
},
bodyReplaced() {
return this.body.split(' ').map(w => {
return w.startsWith('#') ? `${w}` : w;
}).join(' ')
}
}
}).$mount('#app')
<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">
<div>********The first solution********</div>
<template v-for="token in tokens">
<mention v-if="token.startsWith('#')" :user="token.slice(1)"></mention>
<template v-else> {{token}} </template>
</template>
<div>********The second solution********</div>
<span v-html='bodyReplaced'></span>
<router-view></router-view>
</div>

Vue.js call component by variable name?

Is possible in Vue.js call component by variable name?
Components are registred:
import Component1 from 'component1'
import Component2 from 'component2'
import Component3 from 'component3'
...
components: {
Component1, Component2, Component3
},
And i am searching for something like this:
created() {
var componentName = 'Component1';
this.components[componentName]
}
You can access the components property like this:
this.$options.components[componentName]
Just a basic example on how you can use dynamic component:
link to jsFiddle to play around: link here
<div id="app">
<component v-bind:is="currentPage">
<!-- component changes when currentPage changes! -->
<!-- output: Updated About -->
</component>
</div>
new Vue({
el: '#app',
data: {
currentPage: 'home'
},
components: {
home: {
template: "<p>Home</p>"
},
about: {
template: "<p>About</p>"
},
contact: {
template: "<p>Contact</p>"
}
},
mounted(){
this.$options.components['about'].template = "<p>Updated About</p>"
this.currentPage = 'about'
}
})

How to pass params from div to single page component in Vue.js?

I have such code on different pages:
<div id="contact-us" class="section md-padding bg-grey">
<div id="contact"></div>
<script src="/dist/build.js"></script>
</div>
I have main.js:
import Vue from 'vue'
import Contact from './Contact.vue'
new Vue({
el: '#contact',
render: h => h(Contact)
})
And Contact.vue with a template
I want to know from which page component was used. So I need to pass param from div like <div id="contact" page="main"></div> . How can I implement this?
How to pass params from div to single page component in Vue.js?
You can't pass params from a div since it's a html tag and a not custom component, you should define your own component that accepts the properties you want to pass.
So first you should define your component and define the property is allow to receive, then you use your component, take a look to the below example, and you may find more information about passing props here.
Vue.component('your-component', {
props: ['property'],
template: '<h3>{{ property }}</h3>'
})
new Vue({
el: '#app'
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
<div id="app">
<your-component :property="'Hello props'" />
</div>
Example using Single File Component structure.
Parent component:
<template>
<ChildComponent :property="propValue" />
</template>
<script>
import childComponent from './childComponent.vue';
export default {
components: {
ChildComponent: childComponent
},
data() {
return {
propValue: 'Hello prop'
}
}
}
</script>
Children component:
<template>
<h3>{{ property }}</h3>
</template>
<script>
export default {
props: ['property'] // You can add more properties separeted by commas
}
</script>

Categories

Resources