I have a globally registered component (modal) in my NUXT app default layout which I would like to be able to trigger a method from any other component on a click event. I had it initially setup by importing the component into each page but I would rather this be a global component and not have to import explicitly on each page and just have one register call in the default layout. Is this achievable my using $root.$emit? or should I look into a vue store?
Default Layout:
<template>
<div class="layout-wrapper">
<Nuxt />
<Modal />
</div>
</template>
<script>
import Modal from "~/components/Modal.vue";
export default {
name: "DefaultLayout",
components: {
Modal
}
};
</script>
Modal:
<template>
<div>
<el-dialog :visible.sync="modal">
<div class="dialog-content">
<p>
Modal Content
</p>
</div>
</el-dialog>
</div>
</template>
<script>
import { Dialog } from "element-ui";
export default {
name: "Modal",
components: {
"el-dialog": Dialog
},
data() {
return {
modalOne: false
};
},
methods: {
openModal() {
this.modalOne = true;
}
}
};
</script>
other component to call method from:
Open
Using a store is a a good solution for your need:
1/ create a store file store/index.js
export const state = () => ({
modal: false
});
export const mutations = {
SET_MODAL (state, value) {
state.modal = value
}
};
2/ update your layout to set store data to the modal component:
<template>
<div class="layout-wrapper">
<Nuxt />
<Modal open="$store.state.modal" />
</div>
</template>
3/ update your component to read the open value:
<template>
<div>
<el-dialog :visible.sync="open">
<div class="dialog-content">
<p>
Modal Content
</p>
</div>
</el-dialog>
</div>
</template>
<script>
import { Dialog } from "element-ui";
export default {
props: {
open: Boolean // see https://v2.vuejs.org/v2/guide/components-props.html
},
name: "Modal",
components: {
"el-dialog": Dialog
}
};
</script>
4/ toggle the store value from a nuxt page (see https://nuxtjs.org/docs/2.x/directory-structure/store)
<script>
export default {
methods: {
openModal (e) {
this.$store.commit('SET_MODAL', true);
}
},
...
}
</script>
Related
I am trying to pass a function when a button is clicked, the button is clicked in a child element, then passed through a parent element to another child component, and i dont want to use the store for that, How can i do that?
components/footer/footer.vue
-- This is where the button is clicked
<template>
<div class="footer-bottom-header-menu-bar mob" #click="showMenu">
<img src="~/assets/svg/menubar.svg" alt="+" />
</div>
</template>
<script>
export default {
methods: {
showMenu() {
this.$emit("show-menu");
}
}
}
</script>
layouts/default.vue
--This is the parent component where that receives the click function and is to pass it into the app-header
<template>
<div>
<app-header />
<Nuxt />
<app-footer #show-menu="showMenu()"/>
</div>
</template>
<script>
import header from "~/components/header/header";
import footer from "~/components/footer/footer";
export default {
components: {
'app-header': header,
'app-footer': footer
},
methods: {
showMenu() {
console.log("clicked");
}
}
}
</script>
components/header/header.vue
-- I want the click function to perform an action inside this component
<script>
export default {
data() {
return {
showMenuBar: false
}
},
}
</script>
Why are you worried about passing a function around?
When you emit the show-menu event simply toggle a piece of data in your parent component like this:
<template>
<div>
<app-header :showMenuBar="showMenuBar" />
<Nuxt />
<app-footer #show-menu="showMenu"/>
</div>
</template>
<script>
import header from "~/components/header/header";
import footer from "~/components/footer/footer";
export default {
components: {
'app-header': header,
'app-footer': footer
},
data() {
return {
showMenuBar: false;
};
},
methods: {
showMenu() {
// I would make this more dynamic than always
// hardcoding it to true, but you get the idea
this.showMenuBar = true;
}
}
}
</script>
Then in your AppHeader simply take it in as a prop:
<script>
export default {
props: {
showMenuBar: {
type: Boolean,
default: false,
},
}
</script>
You can declare any attribut on your parent component:
data() {
return { toBeWatched: 0 };
}
Then pass it like a props from the parent to the header child:
<app-header :Trigger="toBeWatched" />
When you listen to the #show-menu event (comming from footer child),
make any change on your attribut:
<app-footer #show-menu="toBeWatched++" />
Finally you can watch for this change on your header child and
trigger your function.
<script>
export default {
data() {
return {
showMenuBar: false
};
},
props: ['Trigger'],
watch: {
Trigger() {
this.showMenuBar = !this.showMenuBar; // or do whatever you want
console.log('showMenuBar : ' + this.showMenuBar);
}
}
};
</script>
I've recently started to create an app with Vue 3 and I'm struggling with authentication when trying to emit an event from child to parent. Here's a sample of my code:
Child
<template>
<form class="msform">
<input #click="goToLogin" type="button" name="next" value="Login" />
</form>
</template>
<script>
import Cookies from "js-cookie";
export default {
emits:['event-login'],
methods: {
goToLogin() {
this.$emit("event-login");
},
},
};
</script>
Parent
<template>
<login v-if='loggedIn' #event-login='logIn'/>
<div class="my-page">
<router-view/>
</div>
</template>
<script>
import Cookies from "js-cookie";
import Login from '../pages/Login'
export default {
name: "MainLayout",
components:{
"login":Login
},
data() {
return {
loggedIn: false,
};
},
methods: {
logIn() {
this.loggedIn = true;
}
}
}
I don't know exactly why the event is not handled in the parent, can I get some help, please?
You're listening for the emitted event on a separate instance of login.
router-view is still just a component like any other, so try moving the handler there instead:
<template>
<login v-if='loggedIn' />
<div class="my-page">
<router-view #event-login='logIn' />
</div>
</template>
I have three components:
HugeBox.vue
Box.vue
Dog.vue
HugeBox contains a Box which in turn contains a Dog:
HugeBox -> Box -> Dog
I try to pass the Dog to the Box as a prop, however it doesn't work: all that gets displayed when I open HugeBox is
Box with
while it should be
Box with Dog
HugeBox.vue:
<template>
<Box :myComponent = "Dog"/>
</template>
<script>
import Dog from '../test/Dog.vue';
import Box from '../test/Box.vue';
export default {
components: {Dog, Box}
}
</script>
Box.vue:
<template>
<div>
<p>Box with</p>
<component :is = "myComponent"/>
</div>
</template>
<script>
export default {
props: {
myComponent: {
type: [String, Object],
}
}
}
</script>
Dog.vue:
<template>
<p>Dog</p>
</template>
What am I doing wrong?
In Vue, if you need to pass markup or other components into a child component you can use slots.
Slots allow you to nest components within other components, just as you do with HTML.
HugeBox.vue:
<template>
<Box>
<Dog/>
</Box>
</template>
<script>
import Dog from '../test/Dog.vue';
import Box from '../test/Box.vue';
export default {
components: {Dog, Box}
}
</script>
Box.vue:
<template>
<div>
<p>Box with</p>
<slot></slot>
</div>
</template>
<script>
import Box from '../test/Dog.vue';
export default {
props: {
myComponent: {
type: [String, Object],
}
},
components: { Dog }
}
</script>
The <slot> tag is used to specify where the nested content should be displayed.
EDIT:
So you can actually pass components as props, and display them using the <component :is="..." /> method.
The reason that it's not working is that your original HugeBox.vue component doesn't have access to the Dog component as a template variable. You have to assign it to a data property first:
<template>
<Box :myComponent="dog"/>
</template>
<script>
import Dog from "../test/Dog.vue";
import Box from "../test/Box.vue";
export default {
components: { Box },
data() {
return {
dog: Dog // assign the Dog component object to data, allowing it to be passed as a prop
};
}
};
</script>
You have to import your Dog component inside your Box component.
Not inside your HugeBox component.
HugeBox.vue
<template>
<Box :myComponent = "'Dog'"/>
</template>
<script>
import Dog from '../test/Box.vue';
export default {
components: {Box}
}
</script>
Box.vue
<template>
<div>
<p>Box with</p>
<component :is = "myComponent"/>
</div>
</template>
<script>
import Box from '../test/Dog.vue';
export default {
props: {
myComponent: {
type: [String, Object],
}
},
components: { Dog }
}
</script>
Dog.vue
<template>
<p>Dog</p>
</template>
I have the following app:
<template>
<component :is="layout">
<router-view :layout.sync="layout"/>
</component>
</template>
<script>
import LayoutBlank from './LayoutBlank'
export default {
name: 'app',
data() {
return {
layout: LayoutBlank,
};
},
}
</script>
<script>
import LayoutBlankTemplate from './LayoutBlankTemplate'
export default {
name: 'LayoutBlank',
created() {
this.$parent.$emit('update:layout', LayoutBlankTemplate);
},
render() {
return this.$slots.default[0];
},
}
</script>
<template>
<div>
<slot></slot>
</div>
</template>
<script>
export default {
name: 'LayoutBlankTemplate',
}
</script>
<template>
<layout-blank>
Test content
</layout-blank>
</template>
<script>
import LayoutBlank from './LayoutBlank';
export default {
name: 'BlankTest',
components: {LayoutBlank},
}
</script>
All works fine. But now I would like to add one more slot to the LayoutBlankTemplate:
<template>
<div>
<slot></slot>
<slot name="second"></slot>
</div>
</template>
<script>
export default {
name: 'LayoutBlankTemplate',
}
</script>
and use it in BlankTest:
<template>
<layout-blank>
<template #default>Test content</template>
<template #second>Second test content</template>
</layout-blank>
</template>
<script>
import LayoutBlank from './LayoutBlank';
export default {
name: 'BlankTest',
components: {LayoutBlank},
}
</script>
The code renders only the default slot content. The problem is that I'm using the return this.$slots.default[0]; in LayoutBlank component which renders only the default slot content.
I cannot find the way how to render all slots in LayoutBlank::render() method. I know that I'm able to get the slots list using this.$slots or this.$scopedSlots but I can't find the way how to pass them to the LayoutBlankTemplate to make them render.
You could put the slots into elements you create I believe. This is mentioned (briefly) in the render function documentation.
This may not be exactly the layout you want, but for example,
<script>
import LayoutBlankTemplate from './LayoutBlankTemplate'
export default {
name: 'LayoutBlank',
created() {
this.$parent.$emit('update:layout', LayoutBlankTemplate);
},
render(createElement, context) {
return createElement('div', {}, [
createElement('div', {}, this.$slots.default),
createElement('div', {}, this.$slots.second)
])
},
}
</script>
I am a beginner in Vue.js so please bear with me. I created two child components and one parent component. I am able to load both the components in the parent components. Now, one of my child component should load in the parent component by default. It will have a link, and when clicked, it should load the other child component within the same parent component, by passing and ID value from itself to the loading component. Below is my code.
Parent component (App.vue):
<template>
<div id="app">
<body>
<main>
<div class="main-body">
<component v-bind:is="currentComp">
</component>
</div>
</main>
</body>
</div>
</template>
<script>
import HomeContent from './components/HomeContent.vue';
import DetailContent from './components/DetailContent.vue';
import { changeRoute } from './main.js';
export default {
name: 'app',
data() {
return {
currentComp: 'HomeContent'
};
},
components: {
HomeContent, //this is the default child component
DetailContent, //this is the other child component which should get loaded dynamically
},
created() {
changeRoute.$on('switchComp', comp => {
this.currentComp = comp;
})
}
}
</script>
Default child component (HomeContent.vue):
<template>
<div>
This is Default child component
Click to load the other child dynamically
</div>
</template>
<script>
import { changeRoute } from '../main.js';
export default {
name: 'HomeContent',
props: {
msg: String
},
methods: {
switchComponent(comp) {
changeRoute.$emit('switchComp', comp);
}
}
}
</script>
The other child component (DetailContent):
<template>
<div>
This is the other loaded child component.
</div>
</template>
<script>
export default {
name: 'DetailContent',
props: {
msg: String
},
mounted(){
alert('mount ok');
}
}
</script>
With the above approach DetailContent get loaded on click of a link from the HomeContent. Now, how can pass some data (e.g. ID = 1) from HomeContent to DetailContent. Any help with this please?