Vue Named Slots Caveat? - javascript

When using Named Slots with Vue (utilizing the older, more verbose API for component slots), if I have a reusable component defined with a template like this:
<template>
<div v-for="field in formFields">
<slot name="`${field.key}_PREPEND`">
<span hidden></span>
</slot>
<slot name="`${field.key}_FIELD`">
<slot name="`${field.key}_LABEL`">{{ field.label }}</slot>
<slot name="`${field.key}_CONTROL`">
<input v-if="field.type === 'text'" v-model="model[field.key]"></input>
<input type="checkbox" v-else-if="field.type === 'checkbox'" v-model="model[field.key]"></input>
</slot>
</slot>
<slot name="`${field.key}_APPEND`">
<span hidden></span>
</slot>
</div>
</template>
(this is essentially a hollowed out version of an auto-form generating component I have)
I can then reuse this component like so:
<auto-form
:fields="someArray"
:model="someObject"
>
<template slot="Name_PREPEND"> This goes before the name field </template>
<template slot="Name_FIELD"> For some reason this isn't being rendered, the default slot markup is</template>
<template slot="Name_APPEND"> This goes after the name field </template>
</auto-form>
For some reason, using the above markup (<auto-form>), the slot "${field.key}_FIELD" is ignored.
If I change the inner markup of the _PREPEND field like so
<slot name="`${field.key}_PREPEND`">
<span hidden>
<slot name="`${field.key}_CONTENT`"></slot>
</span>
</slot>
I similarly cannot override the _PREPEND slot (but can override _CONTENT)
Is this simply a limitation of Vue component slots? i.e. Are nested component slots not allowed?
In this particular case, the limitation would prevent a developer using this AutoForm component from say, overriding both the control and label at once via the _FIELD slot (for my uses I wanted to add logic that made a particular field conditional based on the value of other fields in the form)

In case anyone else runs into this issue, it's a bit of a sneaky one.
It looks like if you do conditional rendering on markup that is supposed to override a slot, the default slot will render in its place when it is not conditionally rendered.
So, the simple solution is to use v-show instead of v-if when you try to override the component slot.
(Has nothing to do with nested component slots as originally suspected)

Related

How to render VueJS Slot in a another component

VueJS slot could be placed anywhere inside a parent component.
However, I have a use case where I need to show a slot in another (foreign) component outside the parent component.
For example, I have component Student:
<template>
<slot name="detail" />
<slot name="status" /> <!-- I don't want to render this slot inside this component, but somewhere else -->
</template>
I want to display the student's status in a header section outside the student component as below:
<div class="Header">
<!-- student status slot should go here-->
<student-status-slot></student-status-slot>
</div>
<div class="InputForm">
<student>
<template #detail>This the detail information about student</template>
<template #status>Grade A</template>
</student>
</div>
You may suggest to create a new "student-status-slot" component. However, the idea is to have all student related information, including "status" declared under the component, but render some of the slot somewhere else.
I have tried another approach creating a separate component and pass the status data via prop. But That is not what I wanted to achieve. I don't want to pass data, but re-use the student status component, which can be dynamic.
In a parent component you can access child's slots by adding ref attribute to child, and then render the VNode obtained. See the demo here https://codesandbox.io/s/happy-einstein-9f5rke?file=/src/components/StudentPage.vue
But actually I wouldn't recommend this way such it's not conventional and slots were not designed for this. I think you should rather consider using portals (Vue 3 portals or https://www.npmjs.com/package/portal-vue if you use Vue 2) to achieve your goal

Iterating slot content for iterated slots in child component in Vue.js

I've got a child component (that I cannot edit) where each row in an object is rendered inside a div with a specific slot, and I'd need to pass data from the parent for each of those elements. I'm trying to iterate through every element of the object in the parent component, generate a slot and pass the desired code to the child, but unfortunately I can't manage to and I can't find any material to support me.
The child component:
<div class="slotchildren" v-for="(child, childindex) in row.elementChildren" :key="childindex">
<span>element nr. {{child.id}}</span>
<slot :name="`element-child-${row[idlabel]}-${childindex}`" :row="child">
...
</slot>
</div>
The parent component (not working):
<template v-for="row in rows"> -->
<template v-slot:`element-row-${row.id}`="{row}">
//--> [vue/valid-v-slot] 'v-slot' directive doesn't support any modifier
//--> [vue/valid-v-slot] 'v-slot' directive must be owned by a custom element, but 'template' is not.
<span>{{row.name}}</span>
</template>
</template>
Is something like this feasible and how? If it's not, what could be a viable workaround, consideind that I can't edit the child component?
Thanks in advance.
I solved it with the following synthax:
<template v-for="row in rows" v-slot:[`element-row-${row.id}`]>
..
</template>

Vue: Mysterious ghost props?

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

Vuejs - Passing parent component props to child element with the template

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>

Vue component unknown despite being registered

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.

Categories

Resources