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

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>

Related

VueJS - use sidebar component event to control navbar

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>

How to redirect a page in VueJS with Vue-Router

I'm tring to redirect from my Login Template
<template>
<formulario-login/>
</template>
<script>
import formularioLogin from "#/components/formularioLogin";
export default {
name: "index",
components: {
formularioLogin
},
methods: {
}
}
</script>
<style scoped>
</style>
<template>
<div class="center">
Login
<input type="text" v-model="usuario.login">
<br/>
Senha
<input type="password" v-model="usuario.password">
<br/>
<button #click="logar"> Login</button>
</div>
</template>
<script>
import axios from "axios";
export default {
name: "barraInicial",
data: () => {
return {
usuario: {
login: undefined,
password: undefined
}
}
},
methods: {
async logar() {
let formData = new FormData();
formData.append("user", this.usuario)
await axios.post('http://localhost:8080/login', this.usuario)
.then(res => {
if(res.data === true){
this.$router.push('/App')
}
})
.catch(res => console.log(res))
}
}
}
</script>
<style scoped>
.center {
margin: auto;
width: 50%;
border: 3px solid green;
padding: 10px;
}
</style>
To my App Template
<template>
<div class="container">
<div class="large-12 medium-12 small-12 cell">
<label>Select file
<input type="text" v-model="nome"/>
<input type="file" id="file" v-on:change="handleFileUpload"/>
</label>
<button v-on:click="submitFile">Send</button>
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'App',
components: {
},
data: () => ({
file: '',
nome: ''
}),
methods: {
handleFileUpload( event ){
this.file = event.target.files[0];
},
async submitFile(){
let formData = new FormData();
formData.append("image", this.file, this.file.name);
formData.append("nome", this.nome)
await axios.post( 'http://localhost:8080/image.do',
formData,
)
},
}
}
</script>
<style>
*{
}
</style>
I've added a Vue-Router into my project and my main.js
import { h } from 'vue'
import App from './App.vue'
import Index from './Index.vue'
import * as VueRouter from 'vue-router'
import * as Vue from 'vue'
const routes = [
{path: '/', component: Index, name: 'index'},
{path: '/App', component: App, name: 'program'},
]
const router = VueRouter.createRouter({
history: VueRouter.createWebHashHistory(),
routes,
})
const app = Vue.createApp({
render: ()=>h(Index)
})
app.use(router)
app.mount('#app')
I've created a Vue 3 project, and i'm trying to redirect from my Template Login to my App Template, i added Vue-Router into my project and created path's to my templates, but when my front-end receive a boolean true from my back-end, they step into if condition, the URL change, but template don't, the console don't show nothing too
Since you not including Index.vue file, I think you forgot the <router-view/>
Reference
// Index.vue
<template>
<router-view />
</template>
<script>
export default {
name: "Index",
}
</script>

how can i pass a component with props from method to dynamic components in vue?

The idea is simple. To move a whole ready component with props from App to Header, but through a method.
I tried all the combination of with parenthesis/without etc.. but nothing seem to work..
is it possible?
App component:
<template>
<Header
#toggleAddTask="toggleAddTask"
:showAddTask="isShowAddTask"
title="Task Tracker"
func="func"
/>
</template>
<script>
import Header from "./components/Header.vue";
import Footer from "./components/Footer.vue";
import Button from "../src/components/Button.vue";
// import state from './router/index'
export default {
name: "App",
components: {
Header,
Footer,
},
methods: {
func() {
return <Button text="i'm a button" color="red" />;
},
},
};
</script>
Header component:
<template>
<header>
<component v-bind:is="func"></component>
</header>
</template>
<script>
import Button from "./Button.vue";
export default {
name: "Header",
props: ["title", "showAddTask", "func"],
components: {
Button,
},
};
</script>
You could instead of sending props, just decouple the data like so
<template>
<Header
#toggleAddTask="toggleAddTask"
:showAddTask="isShowAddTask"
:dynamic-component="dynamicComponentData"
title="Task Tracker"
/>
</template>
<script>
import Header from "./components/Header.vue";
import Footer from "./components/Footer.vue";
import Button from "../src/components/Button.vue";
export default {
name: "App",
components: {
Header,
Footer,
},
methods: {
dynamicComponentData() {
return {
component: Button,
props: {
text: "I'm a button",
color: "red"
}
}
},
},
};
</script>
With the header like this
<template>
<header>
<component
:is="dynamicComponent.component"
:text="dynamicComponent.props.text"
:color="dynamicComponent.props.color"
/>
</header>
</template>
<script>
export default {
name: "Header",
props: ["title", "showAddTask", "dynamic-component"],
};
</script>
But keep in mind, a better solution for this case would be using slots instead of injecting components.
<template>
<Header
#toggleAddTask="toggleAddTask"
:showAddTask="isShowAddTask"
title="Task Tracker">
<Button text="I'm a button" color="red"/>
</Header>
</template>
<script>
import Header from "./components/Header.vue";
import Footer from "./components/Footer.vue";
import Button from "../src/components/Button.vue";
export default {
name: "App",
components: {
Header,
Footer,
},
};
</script>
And you Header component should have a slot like this
<template>
<header>
<slot />
</header>
</template>
<script>
export default {
name: "Header",
};
</script>
As you asked, this could be a form of adaptor
<template>
<header>
<div class="typeButtons" v-if="componentType.button">
<component
v-text="dynamicProps.text"
:is="dynamicComponent.component"
:text="dynamicProps.textType"
:color="dynamicProps.color"
/>
</div>
<div class="typeInput" v-else-if="componentType.input">
<component
:is="dynamicComponent.component"
:label="dynamicProps.text"
:rules="dynamicProps.rules"
/>
</div>
</header>
</template>
<script>
export default {
name: "Header",
props: ["title", "showAddTask", "dynamic-component"],
computed: {
componentType() {
return {
button:
dynamicComponent.type === "typeOne" ||
dynamicComponent.type === "typeTwo",
input: dynamicComponent.type === "typeThree",
};
},
dynamicProps() {
switch (this.dynamicComponent.type) {
case "typeOne":
return {
text: "Create",
textType: false,
color: "success",
};
case "typeTwo":
return {
text: "Cancel",
textType: true,
color: "error",
};
case "typeThree":
return {
text: "Cancel",
rules: [(v) => Boolean(v)],
};
default:
return { ...this.dynamicComponent.props };
}
},
},
};
</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>

The component template do not shows up, it render to the `<!---->`

I have a forget-pwd.vue component:
<template>
<div class="page-common">
ABC
</div>
</template>
<script>
</script>
<style scoped>
</style>
in the router.js, there is my route:
export const paths = {
home: '/',
dataCenter: '/data-center',
forgetPassword: '/forget-password',
}
export const app_routes = [
......
{
path: paths.forgetPassword,
name: '忘记密码',
meta: {
title: ''
},
component: (resolve) => require(['./views/忘记密码/forget-pwd.vue'], resolve)
},
];
export const routes = [
...app_routes,
];
this is my index.vue:
<template>
<div class="index">
<i-header ></i-header>
<router-view></router-view>
<i-footer></i-footer>
</div>
</template>
<script>
import Home from './首页/home.vue'
import Header from '../components/header/header.vue'
import Footer from '../components/footer/footer.vue'
export default {
data() {
return {
}
},
methods: {
},
components: {
'home': Home,
'i-header': Header,
'i-footer': Footer,
}
};
</script>
<style scoped>
.index {
}
</style>
in a modal of my header.vue I link to the forget-pwd.vue:
<router-link class="forget-pwd" type="text" to="forgetPwdPath" #click.native="closeModal" >忘记密码?</router-link>
the route is skipped, but the forget-pwd.vue template do not shows up:
You forgot to bind the to in your router link. So bind it:
<router-link class="forget-pwd" type="text" :to="forgetPwdPath" #click.native="closeModal" >忘记密码?</router-link>
Or
<router-link class="forget-pwd" type="text" v-bind:to="forgetPwdPath" #click.native="closeModal" >忘记密码?</router-link>