3

I am trying to build my own sortable component. I want to pass a list of items to it's default slot. The sortable component should then wrap all passed items with a custom v-draggable component.

<v-sortable handle=".handle">
    <template :key="index" v-for="(item, index) in items">
        <some-complex-component :item="item"></some-complex-component>
    </template>
</v-sortable>

Now withn my v-sortable component I am trying to wrap all given nodes within default slot with a custom v-draggable component. My v-sortable component looks like this:

import { h } from 'vue';

export default {
    name: 'v-sortable',
    props: {
        handle: {
            type: String,
            required: false,
            default: () => {
                return null;
            }
        },
    },
    render () {
        const draggableItems = this.$slots.default().map(slotItem =>
            h('v-draggable', { handle: this.handle }, [slotItem])
        )
        return draggableItems;
    }
}

This works as expected, except that my custom component v-draggable will not be rendered as a vue component. All items will be wrapped in html tags called <v-draggable>. How would I have to proceed to actually parse the v-draggable component as Vue component?

Eric Xyz
  • 452
  • 1
  • 5
  • 14

2 Answers2

4

Try to import it and register and use it directly :

import { h } from 'vue';
import VDraggable from 'path/to/v-draggable'
export default {
    name: 'v-sortable',
    props: {
        handle: {
            type: String,
            required: false,
            default: () => {
                return null;
            }
        },
    },
    render () {
        const draggableItems = this.$slots.default().map(slotItem =>
            h(VDraggable, { handle: this.handle }, [slotItem])
        )
        return draggableItems;
    }
}

It's recommended to pass items as prop and use them directly inside the render function :

<v-sortable handle=".handle" :items="items">
</v-sortable>

child component :

import { h } from 'vue';
import VDraggable from 'path/to/v-draggable'
export default {
    name: 'v-sortable',
    props: {
        items:{
         type:Array,
         default: () =>[]
        },
        handle: {
            type: String,
            required: false,
            default: () => {
                return null;
            }
        },
    },
    render () {
        const draggableItems = this.items.map(slotItem =>
            h(VDraggable, { handle: this.handle }, [item])
        )
        return draggableItems;
    }
}
Boussadjra Brahim
  • 82,684
  • 19
  • 144
  • 164
  • 1
    I'm quite sure 'v-draggable' won't work without resolveComponent, render function is low-level and works similarly to React in this regard, where string types are for HTML elements – Estus Flask Aug 17 '22 at 12:02
  • Great, it works. Only problem I still have is that the component is wrapped around the whole slot and not around each item. Maybe something wrong with the way I use the map() function. – Eric Xyz Aug 17 '22 at 12:49
  • Btw: register with `components: { VDraggable }` seems not to be neccessary in this case. – Eric Xyz Aug 17 '22 at 12:54
  • @EricXyz please check my edited answer – Boussadjra Brahim Aug 17 '22 at 12:57
  • @BoussadjraBrahim Actually in my scenario the items are not just an array of strings but a complex template with other components. I just simplyfied it for this question. So as I do not know which structure the items array has, I can not pass it as a prop. This way sortable component does not have to know anything about the slots inner content structure. – Eric Xyz Aug 17 '22 at 13:02
  • Could you reproduce this in codesandbox? – Boussadjra Brahim Aug 17 '22 at 13:08
3

The component can to be explicitly specified in render function:

h(VDraggable, ...)

Globally registered component that isn't available for import (e.g. from third-party libs) can be resolved from a name with resolveComponent.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565