VeeValidate 3.0.0 : use ValidationProvider in parent component not working - vue.js

im using VeeValidate 3.0.0 and nuxt 2.15.8 and i wonder what i am missing out.
Parent Component:
<template>
<ValidationProvider
v-slot="{errors}"
:rules="rules"
<form-checkbox-item
:option="option"
:is-special="isSpecial"
:is-required="sectionChildContent.label.mandatory"
:name="sectionChildContent.name"
:title="sectionChildContent.label.title" />
<form-errors v-if="showErrors" :errors="errors" />
</div>
</ValidationProvider>
</template>
Child Component:
<template>
<div>
<input
v-model="selected"
:value="option.value"
:name="name"
:required="isRequired"
:checked="isChecked"
type="checkbox">
</div>
</template>
(simplified code)
Is this possible? When i use ValidationProvider in my child component everything works fine.
Inject as proposed here in v 2. is not working:
https://vee-validate.logaretm.com/v2/concepts/injections.html#injecting-parent-validator

Related

Nuxt2/Vue2, checking a $slots sometime fails on rerender

I have a strange issue on a Nuxt2/Vue2 project.
I have a component with a slot named "image" conditionally displayed. The condition only checks "$slots.image". It works properly on the first render of the component, but if it have to rerender, sometimes the condition fail.
I fixed this issue by modifying the condition to checks also "$scopedSlots.image". But I don't understand what is happening.
Does someone can explain to me what is happening here ?
Here is the parent where my component is imported:
<template>
<div>
<nav-pane>
<template #subheader>
<nav-pane-sub-header>
<template #image>
<avatar />
</template>
</nav-pane-sub-header>
</template>
</nav-pane>
</div>
</template>
Here is my component 'NavPaneSubHeader' with the buggy condition:
<template>
<div>
<div>
<i v-if="icon" />
<slot v-else-if="$slots.image"/>
</div>
<slot />
</div>
</template>
Works fine with :
<slot v-else-if="$scopedSlots.image || $slots.image" />

Does using slots rather inline declarations make a component more flexible?

I am trying to make a grid component that is not too opinionated. I have 2 different types of components that have different cover images, DVD and Cassette.
I am assuming the best way to do this is by not using v-ifs like I am below:
Parent.vue
<MyUniversalComponent
:items="items"
>
</MyUniversalComponent>
MyUniversalComponent.vue
import DVD from '#/components/DVD.vue';
import Cassette from '#/components/Cassette.vue';
<template>
<div class="grid">
<div v-for="item in items">
<div v-if="item.type === 'dvd'">
<DVD :data="item" />
</div>
<div v-else-if="item.type === 'cassette'">
<Cassette :data="item" />
</div>
</div>
</div>
</template>
Is there a more flexible way to do this using slots? I sort of want it to be a "shell" grid that can be used in different ways so I assume I'd want to take out the logic of having these components living in here. Can I translate this to use slots?
In this case, the built-in <component> would be more appropriate than slots. It takes an is prop that sets the component type, and any bindings are passed through the resolved component:
<script>
import DVD from '#/components/DVD.vue';
import Cassette from '#/components/Cassette.vue';
export default {
components: {
DVD,
Cassette,
}
}
</script>
<template>
<div class="grid">
<div v-for="item in items">
<component :is="item.type" :data="item" />
</div>
</div>
</template>
demo

My dynamic component (layout) doesn't work with named slots in vuejs

I have problems to combine dynamic generated layouts with named slots.
To define my layouts I'm using "component :is"
//app.vue
<template>
<component :is="layout">
<router-view />
</component>
</template>
<script>
computed: {
layout() {
const layout = this.$route.meta.layout || 'default'
return () => import(`#/app/layouts/${layout}.vue`)
}
},
</script>
//layouts/default.vue
<template>
<div>
<div>
<slot name="header" />
</div>
<div>
<div>
<slot name="sidebar" />
</div>
<div>
<slot name="default"/>
</div>
</div>
</div>
</template>
// views/page.vue
<template>
<div>
<template #header>
<h1>Primitives</h1>
</template>
<template #sidebar>
<ul>
<li v-for="primitive in sections.sections" :key="primitive">
<router-link :to="`/primitives/${primitive}`">{{primitive}}</router-link>
</li>
</ul>
</template>
<template #default>
<router-view :key="$router.path" />
</template>
</div>
</template>
But now I get this error inside my code
'v-slot' directive must be owned by a custom element, but 'div' is not.
and console displays this error
<\template v-slot> can only appear at the root level inside the receiving component
If I remove the main div I get the error
The template root requires exactly one element.
What I'm doing wrong?
This is not easy to explain so please cope with me...
I really understand what you are trying to do but unfortunately it is not possible in Vue.
Reason for that is slots are more template compiler feature than runtime feature of Vue. What I mean by that ? When Vue template compiler sees something like <template #header>, it will take the inner content and compile it into a function returning virtual DOM elements. This function must be passed to some component which can call it and include the result in it's own virtual DOM it is generating. To do that template compiler needs to know to what component it should pass the function (that is the real meaning of 'v-slot' directive must be owned by a custom element, but 'div' is not. error message...ie compiler is "looking" for a component to pass the slot content to...)
But you are trying to use the slots as if they were "discoverable" at runtime. For your code to work the dynamic layout component must at runtime somehow discover that it's child (also dynamic thanks to <router-view />) has some slot content it can use. And this is not how slots work in Vue. You can pass the slot content your component receives from parent to a child components but do not expect that parent component (layout in this case) can "discover" slot content defined in it's child components...
Unfortunately only solution for your problem is to import the layout component in every "page" and use it as a root element in the template. You can use mixins to reduce code duplication (to define layout computed)
#/mixins/withLayout.js
export default = {
computed: {
layout() {
const layout = this.$route.meta.layout || 'default'
return () => import(`#/app/layouts/${layout}.vue`)
}
}
}
views/page.vue
<template>
<component :is="layout">
<template #header>
<h1>Primitives</h1>
</template>
<template #sidebar>
<ul>
<li v-for="primitive in sections.sections" :key="primitive">
<router-link :to="`/primitives/${primitive}`">{{primitive}}</router-link>
</li>
</ul>
</template>
<template #default>
<router-view :key="$router.path" />
</template>
</component>
</template>
<script>
import withLayout from '#/mixins/withLayout'
export default {
mixins: [withLayout]
}
</script>

Passing value from parent to child using slot probably

I probably don't understand how it should be done. I spent a few hours to achieve this functionality but with no luck. Here is what I have:
Child
<template>
<div>
Data from dialog: {{aaa}}
</div>
</template>
<script>
export default {
name: 'frm',
props: [
'aaa'
]
}
</script>
Parent:
<template>
<div>
<slot :aaa="some"></slot>
</div>
</template>
<script>
export default {
name: 'dlg',
data: () => ({
some: 'data from dialog'
})
}
</script>
View:
<template>
<div>
<dlg>
<frm></frm>
</dlg>
</div>
</template>
<script>
import Dialog from '#/components/dialog.vue'
import Frm from '#/components/frm.vue'
export default {
name: "View",
components: {
'dlg': Dialog,
'frm': Frm
}
};
</script>
Edit: Real code
dialog-template:
<template>
<v-dialog
v-model="internal.dialogOpened"
>
<!-- ... -->
<slot :aaa="'dsada'"></slot>
</v-dialog>
</template>
details-task-dialog:
<template>
<dlg-template large position='right' :onclose="close" :load="loadRetry">
<task-details-form /> <!-- just regular component in which I want to get value passed through slot in dialog-template -->
</dlg-template>
</template>
<script>
import DlgTemplate from '#/components/site/dialogs/dialog-template.vue'
export default {
// ...
components: {
'dlg-template': DlgTemplate,
'task-details-form': DetailsForm,
},
I want to avoid passing prop in View but I don't know how :/ I've read about 'slot-scope' unfortunately with no success. How to achieve such functionality?
Edit: real code
Based on your real world code, you were only missing the attachment of the scope, see below.
dialog-template:
<template>
<v-dialog v-model="internal.dialogOpened">
<!-- ... -->
<slot :aaa="'dsada'"></slot>
</v-dialog>
</template>
details-task-dialog:
<template>
<dlg-template large position='right' :onclose="close" :load="loadRetry">
<task-details-form v-slot="{ aaa }">
<!-- you can use the var `aaa` here -->
</task-details-form>
</dlg-template>
</template>
I'd still wager if you want to use aaa inside task-details-form component you have to pass it down as a prop. But it looks wierd to me, because I'm unsure of the execution order right now (v-slot vs v-bind), but try it like this:
<template>
<dlg-template large position='right' :onclose="close" :load="loadRetry">
<task-details-form v-slot="{ aaa }" :your-a-prop-name="aaa" />
</dlg-template>
</template>
Edit 2: after testing
v-bind shorthand is not working on <slot>:
dialog-template:
<template>
<v-dialog v-model="internal.dialogOpened">
<!-- ... -->
<slot v-bind:aaa="'dsada'"></slot>
</v-dialog>
</template>
details-task-dialog:
<template>
<dlg-template large position='right' :onclose="close" :load="loadRetry">
<template v-slot="{ aaa }"> <!-- has to preceed v-bind -->
<task-details-form :propertyOnComponent="aaa" /> <!-- now you cand bind it -->
</template>
</dlg-template>
</template>
Original:
I think you misunderstood slots. Check the docs:
That slot has access to the same instance properties (i.e. the same “scope”) as the rest of the template. The slot does not have access to child’s scope.
Slots
So you could do that, but then your code becomes:
<template>
<div>
<dlg>
<frm>
<child :aaa=“dialogData” />
</frm>
</dlg>
</div>
</template>
Frm:
<template>
<div>
From component
<slot></slot>
</div>
</template>
If you would define a slot on frm as you are suggesting, you would be able to fill that slot with a specific template and you can receive(!) the scope for that slot.
The deprecated slot-scope which you mentioned would provide you with a bound context from the child to be exposed in your overriding slot in the parent, that’s the opposite of what you want.
Just out of curiosity, why not send down the dialog data to the form as a property? That’s exactly what it’s for.

Vue listen and handle from Emits from elements inside slots

Basically I have Parent, child, child-of-child component while child component have 2 named slots and child-of-child component have 1 slot so template ends up like this:
Structure of the components
<parent>
<child>
<child-of-child></child-of-child>
</child>
</parent>
Parent tempalte:
<template>
<div>
<labeled-menu>
<label slot="label">
Open Menu
</label>
<ul slot="menu">
<li v-for="i in [1,2,3,4,5,6,7]" #click="$emit('test', `${i} li is clicked`)">List {{i}}</li>
</ul>
</labeled-menu>
</div>
</template>
Child Template with 2 named slots while the 'menu' slot is passed as slot of the child-of-child component:
<template>
<div>
<div #click="show">
<slot name="label">
</slot>
</div>
<v-dialog :class="getMenuSize" :is-active="isActive" #hide="hide" :position="position">
<slot name="menu">
</slot>
</v-dialog>
</div>
</template>
child-of-child component
<template>
<div>
<div class="dialog-container">
<div class="dialog-header">
</div>
<div class="dialog-body">
<slot>
//here the ul and lis are basically displayed
</slot>
</div>
</div>
</div>
</template>
I didn't get your problem but if you want a global emitter handler a common pattern is to create a singleton of a vue instance and import it when you need it.
src/bus/index.js
import Vue from 'vue'
const bus = new Vue()
export default bus
src/components/Whatever.vue
import bus from 'src/bus'
// And then in your functions
bus.$on(...)
bus.$emit(...)
bus.$off(...)
If you're looking for a more structured event system, you should take a look at the store pattern with vuex.