VueJS - use sidebar component event to control navbar - vue.js

I am building a web app using Vue3 + vuetify3 running on Vite. I have a v-navigation-drawer within my Sidebar.vue component (child) and I'm emitting mouse events to have my App.vue (parent) listen to it and allow another child components (in this case Topbar.vue) to use that to move the title to the right as the sidebar opens and closes. The problem I'm seeing is that I can log the events in App.vue from the Sidebar as true/false but when I check those values in Topbar.vue they're undefined. I have tried different approaches but can't come up with a solution. If somebody can give me some pointers on how to figure this out I would really appreciate it.
For the sake of space, I'm not including all the css rules and all the items in the sidebar, just the functional sections worth looking at but again, the sidebar is not the issue but the way the topbar is not getting the value from the App.vue.
Sidebar.vue
<template>
<v-navigation-drawer v-model="drawer" class="sidepanel" expand-on-hover rail>
<v-list class="sidebarlist" density="compact" nav>
<router-link to="/">
<v-list-item
class="sidebar-item"
:style="{
backgroundColor: selectedRoute === 'dashboard' ? '#9100e9' : '',
}"
#click="selectedRoute = 'dashboard'"
prepend-icon="mdi-home"
title="Dashboard"
value="dashboard"
></v-list-item>
</router-link>
</v-list>
</v-navigation-drawer>
</template>
<script lang="ts">
export default {
data() {
return {
drawer: true,
selectedRoute: "",
};
},
methods: {
onHover() {
this.$emit("mouseenter");
},
onLeave() {
this.$emit("mouseleave");
},
},
};
</script>
<style scoped>
.sidebar-opened {
opacity: 1;
transition: 0.3s ease-out;
}
.sidebar-item:hover {
background-color: #4b247a;
transition-duration: 0.4s;
}
/* and more */
</style>
App.vue
<template>
<v-app :theme="theme">
<TopbarComp :title="$route.meta.title" :sidebarHovered="sidebarHovered"/>
<SideBarComp #mouseenter="onHover" #mouseleave="onLeave"/>
<router-view />
</v-app>
</template>
<script lang="ts">
import { ref } from "vue";
import SideBarComp from "./components/Sidebar.vue";
export default {
components: {
SideBarComp,
},
methods: {
onHover() {
// Slide Topbar to the right
this.sidebarHovered = true;
console.log('Sidebar entered')
},
onLeave() {
// Slide Topbar back in place
this.sidebarHovered = false;
}
},
setup() {
const theme = ref("dark");
const sidebarHovered = ref(false);
return {
theme,
sidebarHovered
};
}
};
</script>
Topbar.vue
<template>
<v-toolbar fixed app color="#2D2D2D">
<v-toolbar-items>
<v-toolbar-title
class="text-uppercase pa-5"
:class="{ 'slide-right': topbarSlide || sidebarHovered }"
#mouseenter="onHover"
#mouseleave="onLeave"
>
<span class="font-weight-light" style="color: #9E9E9E;">{{ firstWord + ' '}} </span>
<span>{{ restOfWords }}</span>
</v-toolbar-title>
</v-toolbar-items>
</v-toolbar>
</template>
<script lang="ts">
export default {
props: ['title', 'sidebarHovered'],
data() {
return {
toptitle: this.title || '',
topbarSlide: false,
};
},
computed: {
firstWord() {
return this.toptitle.split(" ")[0];
},
restOfWords() {
return this.toptitle.split(" ").slice(1).join(" ");
},
},
methods: {
onHover() {
this.topbarSlide = true;
console.log('The value of the Sidebar is: ',this.sidebarHovered)
console.log('The value of the Topbar is: ', this.topbarSlide)
},
onLeave() {
this.topbarSlide = false;
},
},
};
</script>
<style>
.slide-right {
transform: translateX(20%) !important;
transition: transform 0.3s ease-out !important;
}
</style>

Related

How to pass <slots> content (provided by router-view) data back to component where slot is declared

I have 3 components: App.vue (Entry point), slotWrapper.vue (Wrapping component), About.vue (Page Content).
Inside the 'App.vue' i have the router-view setup which is wrapped with 'slotWrapper.vue' component. The 'slotWrapper' component has a <slot> where the current route will be rendered.
My question: Inside the About.vue page (which will be rendered instead of the <slot>, of the slotWrapper.vue component) I have a computed value which I somehow need to pass back to 'slotWrapper.vue' component to use. How would I achieve such a thing.
I looked into ScopedSlots but I can't figure our how to use it where the content rendered is provided by a router.
Link to: CodeSanbox
App.Vue
<template>
<div id="app">
<slotWrapper>
<router-view />
</slotWrapper>
</div>
</template>
<script>
import slotWrapper from "./components/SlotWrapper.vue";
export default {
name: "app",
components: {
slotWrapper,
},
};
</script>
SlotWrapper.vue
<template>
<div class="wrpperClass">
<slot />
</div>
</template>
<script>
export default {
name: "SlotWrapper",
};
</script>
<style scoped>
.wrpperClass {
width: 50%;
height: 50%;
color: black;
background-color: lightblue;
}
</style>
About.vue
<template>
<div id="app">
<slotWrapper>
<router-view />
</slotWrapper>
</div>
</template>
<script>
import slotWrapper from "./components/SlotWrapper.vue";
export default {
name: "app",
components: {
slotWrapper,
},
};
</script>
<style>
#app {
margin: 60px;
}
.link {
display: inline-block;
padding: 10px;
}
.router-link-active {
color: green;
}
</style>
Router: index.js
import Vue from "vue";
import Router from "vue-router";
import About from "../components/About";
Vue.use(Router);
export default new Router({
mode: "history",
routes: [
{ path: "/", redirect: "about" },
{
path: "/about",
component: About
// props: (route) => ({ name: route.query.name })
}
]
});
For those who might face this issue. I figured that you can pass props to <router-view> and listen to emits. Since you can listen to emits I simply emit from the about.vue page and listen on the app.vue, and later using props I pass the variable down to slotWrapper.vue.
App.vue
<template>
<div id="app">
<slotWrapper :propToPass="propToPass">
<router-view #comp-data="receiveFunction" />
</slotWrapper>
</div>
</template>
<script>
import slotWrapper from "./components/SlotWrapper.vue";
export default {
name: "app",
data() {
return { propToPass: null };
},
components: {
slotWrapper,
},
methods: {
receiveFunction(data) {
this.propToPass = data;
console.log("From emit: ", data);
},
},
};
</script>
About.vue
<template>
<div>
<h3>About Page</h3>
<p>
I need this data:
<span style="color: green">'ComputedData'</span> available inside
'SlotWrapper'
</p>
<p>{{ ComputedData }}</p>
</div>
</template>
<script>
export default {
name: "About",
data() {
return {
someVariable: "'someVariable'",
};
},
mounted() {
setTimeout(this.sendData, 2000);
},
computed: {
ComputedData() {
return (
this.someVariable + " How do I become accessible inside slotWrapper?"
);
},
},
methods: {
sendData() {
this.$emit("comp-data", this.ComputedData);
},
},
};
</script>

Getting the data back from child to parent element in Vue

Before last updates I used a custom component to work with sliders. Works perfectly, but after upgrading I got a problem.
This is the wayI use my custom component:
<ta-slider2 v-model="stelling1"/>
This passes the value to the custom component and received the data back.
This is my custom component
<template>
<div class="mt-3">
<div><br><br></div>
<v-slider
prop="value"
v-model="interface"
:value="value"
:color="color"
always-dirty
min="-100"
max="100"
thumb-label="always"
>
<template v-slot:append>
<v-icon color="blue">add_circle_outline</v-icon>
</template>
<template v-slot:prepend>
<v-icon color="error">remove_circle_outline</v-icon>
</template>
</v-slider>
</div>
</template>
<script>
export default {
name: "ta-slider2",
props: {
value: null
},
data() {
return {
}
},
computed: {
interface: {
get() {
return this.value
},
set(val) {
this.$emit('input', val)
}
},
color() {
if (this.value < 0) return 'red'
if (this.value > 0) return 'blue'
return 'red'
},
},
}
</script>
<style scoped>
</style>
Worked perfect but now the line with v-model="interface" gives problems. Googled for hours but can not find a solution. Who can help me out?

Error message when trying to pass data from component to page in Nuxt

I've created a component in Nuxt to get data from a Firestore database and would like to show that data in a page I created.
When I embed the component in a page I keep getting the error:
Property or method "provinces" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
Now, when I copy the code from the component in the page it works just fine, so I assume the problem is with passing the data between the component and the page.
Full code of the component in components/index.vue :
<template>
<section class="container">
<div class="index">
<div v-for="province in provinces" :key="province.id">
<div class="div_title">
<h2>{{ province.name_nl }}</h2>
</div>
</div>
</div>
</section>
</template>
<script>
// import { VueperSlides, VueperSlide } from 'vueperslides'
// import 'vueperslides/dist/vueperslides.css'
import firebase from 'firebase'
// import fireDb from '#/plugins/firebase.js'
export default {
name: 'Index',
components: {
// VueperSlides,
// VueperSlide
},
data: function() {
return {}
},
async asyncData() {
const moment = require('moment')
const date = moment(new Date()).format('YYYY-MM-DD')
const housesArray = []
const provincesArray = []
await firebase
.firestore()
.collection('provinces')
.orderBy('name_nl')
.get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
provincesArray.push(doc.data())
})
})
await firebase
.firestore()
.collection('houses')
.where('valid_until', '>', date)
.get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
housesArray.push(doc.data())
})
})
return {
provinces: provincesArray,
houses: housesArray
}
}
}
</script>
<style scoped>
div {
text-align: center;
}
h2 {
margin-top: 5vh;
margin-left: auto;
margin-right: auto;
width: 95vh;
}
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
p {
text-align: left;
}
li {
min-width: 100% !important;
margin-left: 0px;
text-align: left;
}
</style>
Page where I insert the component in pages/index.vue:
<template>
<v-layout column justify-center align-center>
<v-flex xs12 sm8 md6>
<div class="text-xs-center">
<logo />
<tabs />
<index />
</div>
</v-flex>
</v-layout>
</template>
<script>
import Logo from '~/components/Logo.vue'
import Tabs from '~/components/Tabs.vue'
import firebase from 'firebase'
import Index from '~/components/Index.vue'
export default {
components: {
Logo,
Tabs,
Index
},
data: function() {
return {}
}
}
</script>
I would expect the page to display the data that I retrieved when I import the component into the page but I keep getting the same error.
Should I be using the Nuxt store to transfer data between a component and a page or am I doing something else wrong?
The lifecycle hook asyncData is not know within Vue components. It's only known in Nuxt pages.
It's better to do the data request within your pages component and pass it as a property to your component:
pages/index.vue
<template>
<index :houses="houses" />
</template>
<script>
const delay = time => new Promise(resolve => setTimeout(resolve, time));
export default {
async asyncData() {
// fake data
await delay(500);
return {
houses: [...]
}
}
}
</script>
components/index.vue
<template>
<pre>{{ houses }}</pre>
</template>
<script>
export default {
props: {
houses: {
required: true,
type: Array
}
}
}
</script>

Delivery data from parent to child

After googling for hours and trying all king examples I still can't figure out how to call to a function that located in a child or pass any data to a child from the parent. I tried so many examples without success and i got really confused with the Vue, is it so complicated to pass a data to a child? Here is what I came up with from one of the examples but it does not work with me, maybe I do something wrong here? Thanks :(
parent.vue
<template>
export default {
name:'parent'
data:{
retrun:{
dataTochild:''
}
},
methods:{
callToChild:function(){
this.dataTochild = 'Hello Child'
}
}
}
</template>
<child :myprop="dataTochild" />
<input #click="this.callToChild" />
child.vue
<template>
export default {
name:'child',
props:['myprop'],
watch:{
myprop:function(){
alert('Hello Parent')
}
}
}
<template>
By the way, I am using Vue 2 version!
you have a spelling error on return (retrun), and there could be other errors on code you have omitted.
Working live demo
Here it is working on a live demo
Code
Parent
<template>
<div class="parent">
<child :myprop="dataTochild" />
<button #click="callToChild">Press me!</button>
</div>
</template>
<script>
import Child from "./Child.vue";
export default {
name: "Parent",
data() {
return {
dataTochild: "",
counter: 0
};
},
methods: {
callToChild: function() {
this.counter = this.counter + 1;
this.dataTochild = "Hello Child " + this.counter;
}
},
components: {
Child
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
button {
width: 200px;
height: 60px;
}
</style>
Child
<template>
<div class="child">
<p>{{ myprop }}</p>
</div>
</template>
<script>
export default {
name: "Child",
props: ["myprop"],
watch: {
myprop: function() {
console.log("Hello Parent");
alert("Hello Parent");
}
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
div.child {
width: 400px;
height: 100px;
margin: auto;
}
</style>

HeaderComponent with custom functionality

I would like to know how can I implement a CustomHeader with a custom functionality (other than sort, for example).
Basically what I want to know is how to communicate my HeaderComponent with my component that holds the grid. E.g.:
<template>
<div style="height: 100%" class="table-chart" ref="root">
<div class="title" ref="title">{{ objectData.title }}</div>
<div class="ag-dashboard" style="height: 100%; width: 90%; margin: 0 auto">
<ag-grid-vue
:style="{ height: tableHeight }"
:gridOptions="gridOptions"
:columnDefs="columnDefs"
:rowData="rowData"
/>
</div>
</div>
</template>
<script>
export default {
components: {
'HeaderComponent': {
template: '<span>{{this.params.displayName}} <span #click="custom">CLICK</span></span>',
methods: {
custom() {
// emmit an event here or find a way to comunnicate with the function "customEvent" below
}
}
}
},
methods: {
customEvent() {
console.log('Event from header');
}
},
beforeMount() {
// ... setup Ag-Grid and the HeaderComponent in the columns' headerComponentFramework
}
}
</script>
Apreciate any help,
The cleaner way I found of doing this was through an EventBus:
import Vue from 'vue';
const EventBus = new Vue();
export default {
//...
'HeaderComponent': {
// ...
methods: {
custom() {
EventBus.$emit('customEvent');
}
}
// ...
mounted() {
EventBus.$on('customEvent', () => {
// Do whatever...
});
}
}