Vue - Component that accepts a nested component - vue.js

I would like to have a component that can have another component placed into it. I'm struggling to find how this can be achieved.
If I pass a component in it doesn't get displayed. I'm assuming I need to specify it in the component however I can't find how in the documentation.
E.g.
<component-that-allows-nesting>
<nested-component/>
</component-that-allows-nesting>
What needs to be added to my component to allow it to accept a nested component?

Vuejs support nested component like Base Component and you can use it. if you want send data from parent component to child component you can use props.
for example
//parent-component
<form>
<base-input />
<base-button>
add form
</base-button>
</form>
//child-component
//BaseInput.vue
<input type="text" placeholder="userName" />
//BaseButton.vue
<button #click="submitForm" >
<slot></slot>
</button>

Here is an example for vue3 carousel.
You should import nested component
You should add this component in components
Use it as nested component
<template>
<carousel :items-to-show="1.5">
<slide v-for="slide in 10" :key="slide">
{{ slide }}
</slide>
</carousel>
</template>
<script>
import { Carousel, Slide } from 'vue3-carousel';
export default {
name: 'App',
components: {
Carousel,
Slide,
},
};
</script>

Related

<MenuItems /> is missing a parent <Menu /> component error (with MenuItems in slot)

I am creating a component in Vue3 called <Dropdown>.
The Dropdown component uses other components in turn: Menu, MenuItems, MenuItem and MenuButton from the headlessui/vue library.
My idea is that you can put any content in the Dropdown, that's why I created a slot called #contentdropdown.
The problem is that when I pass this slot content to the Dropdown component, Vue gives me the following error:
< MenuItems /> is missing a parent < Menu /> component
This is my Dropdown componente code:
<template>
<Menu as="div" class="relative inline-block text-left">
<div>
<MenuButton class="btn inline-flex justify-center w-full text-sm" :class="'btn-'+variant">
Options
<ChevronDownIcon class="-mr-1 ml-2 h-5 w-5" aria-hidden="true" />
</MenuButton>
</div>
<transition enter-active-class="transition ease-out duration-100" enter-from-class="transform opacity-0 scale-95" enter-to-class="transform opacity-100 scale-100" leave-active-class="transition ease-in duration-75" leave-from-class="transform opacity-100 scale-100" leave-to-class="transform opacity-0 scale-95">
<slot name="contentdropdown"></slot>
</transition>
</Menu>
</template>
<script>
import { Menu, MenuButton } from '#headlessui/vue'
import { ChevronDownIcon } from '#heroicons/vue/solid'
import { vVariantProp } from '../../../../constants'
import { reactive, computed } from 'vue';
export default {
name: 'dropdown',
props: {
...vVariantProp,
},
setup(props) {
props = reactive(props);
return {
}
},
};
</script>
Why does it need the parent component called Menu?, if in fact I am already painting the slot inside the component and also importing it inside the Dropdown component.
This is how I pass to the Dropdown component the content through its #contentdropdown slot:
<Dropdown v-bind="{'variant':'primary'}">
<template #contentdropdown>
<MenuItems class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none">
<div class="py-1">
<MenuItem>
Subitem1
</MenuItem>
<MenuItem>
Subitem2
</MenuItem>
</div>
</MenuItems>
</template>
</Dropdown>
The error <MenuItems /> is missing a parent <Menu /> component is not a Vue specific error. It is an error thrown by headlessui/vue - source
MenuItems component (as well as MenuButon etc - see doc) is designed to be used inside Menu component. It is using inject to tap into state provideded by the Menu component. There is nothing you can do about it - it is designed that way
Problem is that slot content (everything inside <template #contentdropdown> in the last code example) in Vue is always rendered in parent scope
Everything in the parent template is compiled in parent scope; everything in the child template is compiled in the child scope.
This means that MenuItems rendered as slot content has no access to data provideded by the Menu component rendered inside your Dropdown component
I don't see any way to overcome this limitation. You'll need to change your design (or describe your use case to headlessui/vue maintainers and ask them to implement alternative approach to share MenuContext with child components - for example using slot props)

How to pass an instantiated Vue component as a prop in Vue.js?

I want to pass an instantiated Vue Component as a prop to antoher component and then render it, like so:
<Button :icon=<IconComponent size="25" /> :link="http://wikipedia.org">Click here to visit Wikipedia</Button>
This is at least how I would do this in React. How can I achieve the same with Vue?
You need to use named slots in the child component :
<template>
<button>
<slot name="icon"></slot>
<slot></slot>
</button>
</template>
in parent component:
<Button link="http://wikipedia.org">
<template #icon>
<IconComponent size="25" />
</template>
Click here to visit Wikipedia</Button>

How to use Vue I18n translation in component attribute/property

How do I translate the passed-in attribute/property of a component? For instance, I have a card component with a title and description prop defined like this.
<!-- my-card component -->
<template>
<div>
<h2>{{title}}</h2>
<span>{{description}}</span>
</div>
</template>
<script>
export default {
props: {
title: String,
descritpion: String
}
}
</script>
Then using the my-card component in another page/component like this
<template>
<div>
<header>Page header</header>
<my-card :title="the best card title" :description="the best description" />
<footer>Page footer</footer>
</div>
</template>
How do I us vue I18n to translate the component props?
<template>
<div>
<header>Page header</header>
<my-card :title="{{ $t('myCard.title')}}" :description="{{$t('myCard.description')}}" />
<footer>Page footer</footer>
</div>
</template>
I can't seem to get the translation to work with passed-in props.
PS: I know I could add the translation in the place I defined my-card component but the issue here is that the components are third-party components from NPM library.
I know some packages in React.js has this feature.
Just bind the translation without using {{}} :
<my-card :title="$t('myCard.title')" :description="$t('myCard.description')" />
You can use I18n translation in component props like this.
<my-card
:title="$t('myCard.title')"
:description="$t('myCard.description')"
/>

How to emit event to another component that is not a parent in vue.js?

So the first component in the example below simply just imports other components and then renders them in the template. Among the components being imported, I have a Stages component and a StageExecutionTimes component. What I'm trying to do is when a user clicks on an icon in the Stage component, I want to then hide the StageExecutionTimes component. My understanding is that I will need to use $emit from the Stages component to send the event to the parent which will then hide the StageExecutionsComponent. I am trying to set showgraph as a boolean which is then used as a prop between the components. I am then trying to change the value of showgraph via $emit.
The code below does not seems to be affecting or changing the value of showgraph in the main component
main component that imports and renders subcomponents
<template>
<div id="vue-main">
<NavBar></NavBar>
<transition name="fade">
<div :v-bind="showgraph"><StageExecutionTimes></StageExecutionTimes></div>
</transition>
<transition name="fade">
<Stages></Stages>
</transition>
<transition name="fade">
<Overview></Overview>
</transition>
<Footer></Footer>
</div>
</template>
<script>
import NavBar from "../components/NavBar.vue";
import Stages from "../components/execution_details/Stages.vue";
import Binaries from "../components/execution_details/Binaries.vue";
import StageExecutionTimes from "../components/graphs/StageExecutionTimes.vue";
import Overview from "../components/execution_details/Overview.vue";
export default {
name: "execution_details",
data() {
return {
loading: true,
showgraph: true
};
},
components: {
NavBar,
Stages,
StageExecutionTimes,
Overview,
},
../components/execution_details/Stages.vue";
<template>
<div class="stages">
<template v-if="job_execs.length > 0">
<h3>Stages</h3>
<img src="#/assets/charticon.png">
<transition name="fade" appear mode="out-in">
<table>
<tbody>
<template v-for="item in job_execs">
<tr>
<td
v-for="stage_execution in item.stage_execution"
:class="stage_execution.status.name"
:title="stage_execution.exec_node.name"
:key="stage_execution.stage.name"
>
<br />
{{ stage_execution.duration | durationReadable }}
<br />
{{ stage_execution.status.name }}
</td>
</tr>
</template>
</tbody>
</table>
</transition>
</template>
</div>
</template>
<script>
import { calculateDuration } from "../../helpers/time.js";
import { liveDuration } from "../../helpers/time.js";
import moment from "moment";
export default {
name: "Stages",
props: ["showgraph"],
data() {
return {
job_execs: []
};
},
How can I hide StageExecutionTimes component when charticon is clicked in the Stages component?
Also, can I accomplish this WITHOUT even touching StageExecutionTimes component? It seems like I could just pass the event from Stages to the main component and then from there just hide StageExecutionTimes.
if you have many components to share datas,you need a vuex store.
you click the then you change the datas in the store, and in your you use v-if="isShow"
#click="hideOtherComponent"
hideOtherComponent(){
this.store.commit('HIDE_COMPONENT');
}
computed: {
isShow() {
return store.state.StageExecutionTimesShow
}
}
<component v-if="isSHow">
`
vuex
Have the components a common high level parent? If the answer is yes, you can $emit the change to the parent, and pass to the other child the data trought a Prop.

Vue.js dynamic layout renderless component layout with multiple slots

I am trying to build a dynamic layout for my application. I have two different layouts, one being DefaultLayout.vue:
<template>
<div>
<main>
<slot/>
</main>
</div>
</template>
and a second one being LayoutWithFooter.vue, with two slots:
<template>
<div>
<main>
<slot/>
</main>
<footer>
<slot name="footer"/>
</footer>
</div>
</template>
My renderless component to handle the dynamic layout looks like this:
<script>
import Vue from 'vue';
import DefaultLayout from './DefaultLayout';
import LayoutWithFooter from './LayoutWithFooter';
export default {
props: {
name: {
type: String,
required: true
}
},
created(){
this.registerComponent("DefaultLayout", DefaultLayout);
this.registerComponent("LayoutWithFooter", LayoutWithFooter);
this.$parent.$emit('update:layout', this.name);
},
methods: {
registerComponent(name, component) {
if(!Vue.options.components[name]) {
Vue.component(name, component);
}
}
},
render() {
return this.$slots.default[0];
},
}
</script>
All of this works fine for the DefaultLayout.vue but when I want to use the LayoutWithFooter.vue, it cannot handle the two slots inside it. Here's an example usage:
<template>
<layout name="LayoutWithFooter">
<div>
<div>some content</div>
<div slot="footer">content for the footer slot</div>
</div>
</layout>
</template>
Problem now is, that the "content for the footer slot" does not get rendered inside of the footer slot of the LayoutWithFooter.vue.
First of all I want you to pay an attention to defining slots level in your example. You provided this code:
<template>
<layout name="LayoutWithFooter">
<div>
<div>some content</div>
<div slot="footer">content for the footer slot</div>
</div>
</layout>
</template>
But actually your div slot="footer" does not refer to footer slot of LayoutWithFooter.vue component. It because of anyway the very first child refers to default slot. And as a result it looks like:
"You want to set content for default slot and inside this default slot you tried to set content for footer slot" - but it's two different scopes.
The right options would look like on the next example:
<template>
<layout name="LayoutWithFooter">
<!-- default slot content -->
<div>
<div>some content</div>
</div>
<!-- footer slot content -->
<div slot="footer">content for the footer slot</div>
</layout>
</template>
I prepared some example based on code and structure you've provided. There you are able to switch layouts and check out how it works and use different components slot in one layout.
Check it here:
https://codesandbox.io/s/sad-fog-zr39m
P.S. Maybe some point are not totally clear, please reply on my answer and I will try to explain more and/or provide you with more links and sources.