I am trying to compose a component inside another like this:
<prompt :users="users">
...
<dataset v-for="ds in users" :user="user"></dataset>
...
</prompt>
But apparently I'm not registering it properly:
[Vue warn]: Unknown custom element: <dataset> - did you register the component correctly? For recursive components, make sure to provide the "name" option.
(found in root instance)
Here's how I'm trying to register it:
app.js
Vue.component('prompt', {
props: ['userdata', 'users'],
template: '#prompt-template',
components: {
'dataset': {
props: ['userdataset', 'user'],
template: '#dataset-template',
}
}
});
Finally, the templates:
<template id="dataset-template">
<li>{{ user}}</li>
</template>
<template id="prompt-template">
<transition name="modal">
<div class="modal-mask">
<div class="modal-wrapper">
<div class="modal-container">
<div class="modal-header">
<slot name="header">
default header
</slot>
</div>
<div class="modal-body">
<slot name="body">
</slot>
</div>
<div class="modal-footer">
<slot name="footer">
default footer
<button class="modal-default-button" #click="$emit('close')">
OK
</button>
</slot>
</div>
</div>
</div>
</div>
</transition>
</template>
Are there any steps I'm missing? I can't figure out how the component isn't being registered.
The problem is that you use the dataset component as a slot within the prompt component. While the Vue vm tries to figure out the component tree it will recognize the dataset component which it does not know. Subcomponents are used in the component template but not within slots. You have to register the dataset component within the Vue vm like you did for the prompt component. Try this
Vue.component('promp', { ... })
Vue.component('dataset', { ... })
It also make sense to register the components on the same level since the templates of the components are also registered on the same level (next to each other).
Compare it to the example you mentioned in another answers comment: Here the subcomponent axis-label is only used within the template of the parent polygraph component. This is valid since now the component is in contract to figure out it sub components not the vue-vm.
In other words:
It should be possible to pass components into the slot of any component A which are not subcomponents of A. Therefore all components passed to slots of a component should be available to the vue vm.
Thumb rule could be
if a component does not appear within another components template, it is not a subcomponent.
Related
I'm a bit new to the component's world and trying to figure out a thing, how the parent child relationship works in components. I've seen some examples of some component libraries where they have some parent child components to be defined and those are used as the child components. For example, table and tr:
<my-table> <!-- Parent -->
<my-tr> </my-tr> <!-- Child -->
</my-table>
Now, I assume, that child works for parent via slots. So the parent should be defined something like this:
<template>
<div>
<slot></slot>
</div>
</template>
Now the parent element can have multiple <my-tr> as well. And slot should be rendering all of those. However, I am trying to a similar thing but a little more complex than that.
I am trying to create a slider with this approach. Where there is a my-slider component and my-slider-item components used to define inside my-slider component. And then I want to control the visibility of the child components defined in the parent component slot by modifying it's properties.
It should be looking like this:
<my-slider>
<my-slider-item>Item 1</my-slider-item>
<my-slider-item>Item 2</my-slider-item>
<my-slider-item>Item 3</my-slider-item>
</my-slider>
my-slider component
<template>
<div class="my-slider">
<slot></slot>
</div>
</template>
my-slider-item component
<template>
<div class="my-slider__item">
<slot></slot>
</div>
</template>
Now how can I know in the parent that how many <my-slider-item> are defined in the parent slot and based on that I want to control the visibility of the child 1 at a time as it is going to work as the slider.
I'm not sure but I missing some basic concept here which I was not getting after looking at tons of example since yesterday. If anyone can please help here? Thanks a lot!
The parent-child relationship is actually established by importing the child component into the parent component, and including the child in the parent's 'components' option.
I created an example scenario with simple Parent and Child component definitions in order to show a standard relationship implementation. Built with Vue 2 and the Vue CLI.
MySlider.vue (parent)
<template>
<div class="my-slider">
<h4>My Slider</h4>
<my-slider-item v-for="(item, index) in sliderItems" :key="index" :sliderItem="item" />
</div>
</template>
<script>
import MySliderItem from './MySliderItem.vue'
export default {
components: {
MySliderItem
},
data() {
return {
sliderItems: [
{
name: 'Slider Item 1'
},
{
name: 'Slider Item 2'
},
{
name: 'Slider Item 3'
}
]
}
}
}
</script>
MySliderItem.vue (child)
<template>
<div class="my-slider-item">
<h5>{{ sliderItem.name }}</h5>
</div>
</template>
<script>
export default {
props: {
sliderItem: {
type: Object,
required: true
}
}
}
</script>
I am trying to create a dummy vue page, were the different components used in the page like b-field ,b-table etc should be injected into the page in order to make things more dynamic. Currently the fields and components are defined in the template section of the .vue page.
<template>
<div class="container is-fluid">
<b-loading :is-full-page="true" :active.sync="this.isLoading"></b-loading>
<p class="subtitle">Business Unit</p>
<b-field label="Business Unit">
<b-input
required
:disabled="this.newRecord ? false : true"
:value="this.objectData.id"
#input="(newValue)=>{updateValue(newValue,'id')}"
></b-input>
</b-field>
<b-field label="Description">
<b-input
:value="this.objectData.description"
#input="(newValue)=>{updateValue(newValue,'description')}"
></b-input>
</b-field>
<b-field label="Short Description">
<b-input
:value="this.objectData.shortdescription"
#input="(newValue)=>{updateValue(newValue,'shortdescription')}"
></b-input>
</b-field>
<b-field label="Status">
<b-autocomplete
:value="this.objectData.status"
:open-on-focus="true"
:keep-first="true"
:data="['Active','InActive']"
#input="(newValue)=>{updateValue(newValue,'status')}"
></b-autocomplete>
</b-field>
<section>
<p class="is-size-7 has-text-danger">{{submitError}}</p>
<b-button #click="submitForm" class="is-radiusless">Submit</b-button>
<b-button type="is-text" #click="$router.go(-1)" class="is-radiusless">Return</b-button>
</section>
</div>
</template>
<script>
import { viewMixin } from "../viewMixin.js";
const ViewName = "BusinessUnitDetail";
export default {
name: "BusinessUnitDetail",
mixins: [viewMixin(ViewName)],
};
</script>
But the components mentioned in the template section should actually be stored as a string and going forward in the future this string will be retrieved from the database instead. But for the time being and as a starting point , this string can be hardcoded in the script section itself. Now i need a solution or guidance on how to achieve this and make the page to actually work..
Note: Please note that in vue js, i know that we could show or hide components based on the application state using Vue conditional structures such as v-if and v-else. But this is not what iam talking about. Instead i want the components (b-field,b-table etc) to be dynamically injected into the DOM. So in future if there will be an extra b-field or any other component, i can simply append the component tag to the string and that new component will be rendered in the frontend successfully.Plz help?
I guess the solution you look for is vue dynamic components.
So you can do this:
<template v-for="(item, index) of components" :key="index">
<component :is="item.name" :any-prop="item.anyProp">
</template>
...
<script>
// ...
data() {
return {
components: [
{ name: 'b-field', anyProp: 'status' }
]
}
}
</script>
In my project, I came across the following code:
Parent component - <ParameterModal>:
<template>
<modal-wrapper props="...">
<!-- ... other templates similar to this... -->
<template v-else-if="modalTemplate === 'bitmask_set'">
<template slot="header">
<h4 class="center-text">{{title}}</h4>
</template>
<div v-if="errorMessage" class="error-message">
{{errorMessage}}
</div>
<ModalBitmaskSet
v-bind="modalMeta"
:setErrorMessage="setErrorMessage"
:clearErrorMessage="clearErrorMessage"
/>
</template>
<!-- ... -->
<div v-else>
Warning: unmapped modal template!
</div>
<!-- ... -->
</modal-wrapper>
</template>
Ok, cool, this is using a regular slot and named slot to display a component called <ModalBitmaskSet>. So I look inside modal-wrapper to find the outlets...
Child component - <modal-wrapper>
<template>
<!-- some container and wrapper elements and then... -->
<div class="modal-header">
<slot name="header" />
</div>
<div
:class="['modal-body', 'display-flex', 'flex-direction-column', modalTemplate]"
>
<div
id="escape_message"
style="text-align: center; display: none; padding-bottom: 10px;"
>
You have unsaved changes.<br />Please click Save or Cancel to proceed.
</div>
<md-content v-if="modalContent">{{modalContent}}</md-content>
<slot />
</div>
<!-- end containers and wrappers -->
</template>
Also cool, there is where the slots are coming out... but how are props being passed to <ModalBitmaskSet>? When I look in Vue DevTools, I can see that props are somehow being passed to this component that don't exist in the parent. On top of this, when I add new components to <ParameterModal>, they sometimes don't get passed props that other components seem to be getting! This is very weird!
As you can see from the photo, this component is somehow getting passed props that aren't listed in the code! Specifically, the props colIndex, fieldSet, indexOffset, methodIndex and rowIndex in this case, although other components on this <ParameterModal> component appear to get different props.
Am I missing something? Where could these ghostly props be coming from?
This line seems the likely cause, though without seeing the code for modalMeta it's difficult to be sure:
v-bind="modalMeta"
This is using the object v-bind syntax, so whatever properties exist in the modalMeta object will be passed as props to the component.
See:
https://v2.vuejs.org/v2/guide/components-props.html#Passing-the-Properties-of-an-Object
https://v2.vuejs.org/v2/api/#v-bind
The problem I'm trying to solve is I want the child component to react when a v-expansion panel is expanded/contracted.
Normally this would be trivial, however, I'm attempting to pass a value from a prop within a vuetify component to component via a scoped slot. Because I'm rendering the child components within a loop, I can't just use Data to bind the prop.
<v-expansion-panel expand>
<v-expansion-panel-content
v-for="item in this.items"
:key="item.key">
<div slot="header">
content
</div>
<slot :items="item.children"></slot>
</v-expansion-panel-content>
</v-expansion-panel>
The v-expansion-panel-content has a prop called value. I need to bind that prop to the slot. Ideally, I'd like to achieve something like this:
<slot :items="item.children" :panelValue="value"></slot>
Any ideas would be greatly appreciated.
I've solved this, but in a less than ideal way.
<v-expansion-panel expand>
<v-expansion-panel-content
v-for="item in this.items"
:key="item.key"
v-model="item.isOpen">
<div slot="header">
content
</div>
<slot :items="item.children" :isVisible="item.isOpen"></slot>
</v-expansion-panel-content>
I have actually 2 global components one for Admin and other one for Modal. The Admin component have a child comp called Page and the Page comp have others childs. I want to pass content directly to Page comp via slots. Like this:
app.js
new Vue({
el: '#app',
components: { Admin, Modal }
})
Admin.vue
<template>
<div>
<page>
<slot></slot>
</page>
</div>
</template>
export default {
components: { Page }
}
Page.vue
<template>
<div>
<page-header>
<slot name="page-header">
<h1 class="page-title">
<slot name="page-title">
Page Title
</slot>
</h1>
</slot>
</page-header>
<page-body>
<slot>
Page Body
</slot>
</page-body>
<page-footer>
<slot name="page-footer">
Page Footer
</slot>
</page-footer>
</div>
</template>
export default {
components: {
pageHeader,
pageBody,
pageFooter
}
}
index.html
<admin>
<div slot="page-header">
Header Test
</div>
Body Test
<div slot="page-footer">
Footer Test
</div>
</admin>
I don't need to use Page as global component, Please any idea?? Hope you understand what I'm looking for...
Thanks
I don't think slots are designed to do this. If you need data to be persisted between parent and children then use either props as #Belmin Bedak suggested. When you pass a prop to a component it will be available to all its children.
If you need to persist state on front-end I strongly recommend using Vuex as source of data for all components ( only use if it becomes more complex to have data scattered across components ).