Creating a custom element containing multiple elements
Learn how to create a container element with multiple elements.
To create a custom element that contains multiple elements, just like an ObjectElement
or GroupElement
we can copy them and extend as we want:
vue
<!-- NameElement.vue -->
<script>
import { ref, markRaw } from 'vue'
import { ObjectElement } from '@vueform/vueform'
import theme from '@vueform/vueform/dist/tailwind'
export default {
// Copy all properties of ObjectElement (eg. emits, mixins, ...)
...ObjectElement,
name: 'NameElement',
props: {
// Copy props from ObjectElement
...ObjectElement.props,
// Define child elements for our ObjectElement
schema: {
required: false,
type: Object,
default: () => ({
first_name: {
type: 'text',
placeholder: 'First name',
columns: 6,
},
last_name: {
type: 'text',
placeholder: 'Last name',
columns: 6,
},
})
}
},
setup(props, context) {
// Copy all props / methods from ObjectElement
const NameElement = ObjectElement.setup(props, context)
// Copy default classes from ObjectElement's theme
const defaultClasses = ref({
...theme.classes.ObjectElement,
})
// Copy template from ObjectElement's theme
const template = markRaw(theme.templates.ObjectElement)
return {
...NameElement,
defaultClasses,
template,
}
},
}
</script>
vue
<!-- NameElement.vue -->
<script>
import { ref, markRaw } from 'vue'
import { ObjectElement } from '@vueform/vueform'
import theme from '@vueform/vueform/dist/vueform'
export default {
// Copy all properties of ObjectElement (eg. emits, mixins, ...)
...ObjectElement,
name: 'NameElement',
props: {
// Copy props from ObjectElement
...ObjectElement.props,
// Define child elements for our ObjectElement
schema: {
required: false,
type: Object,
default: () => ({
first_name: {
type: 'text',
placeholder: 'First name',
columns: 6,
},
last_name: {
type: 'text',
placeholder: 'Last name',
columns: 6,
},
})
}
},
setup(props, context) {
// Copy all props / methods from ObjectElement
const NameElement = ObjectElement.setup(props, context)
// Copy default classes from ObjectElement's theme
const defaultClasses = ref({
...theme.templates.ObjectElement.data().defaultClasses,
})
// Copy template from ObjectElement's theme
const template = markRaw(theme.templates.ObjectElement)
return {
...NameElement,
defaultClasses,
template,
}
},
}
</script>
After, we can register the element in vueform.config.js
:
js
// vueform.config.js
import { defineConfig } from '@vueform/vueform'
import NameElement from './NameElement.vue'
export default defineConfig({
elements: [
NameElement,
],
})
If we would like to customize the <template>
part of the component as well, we can define it in our component instead of exporting template
property, using the original template of ObjectElement
as a starting point:
vue
<!-- NameElement.vue -->
<template>
<component :is="elementLayout" :multiple="true" ref="container">
<template #element>
<div :class="classes.wrapper" role="group" :aria-labelledby="labelId">
<slot>
<component :is="component(element)"
v-for="(element, name) in children"
v-bind="element"
:embed="embed"
:name="name"
:key="name"
@remove="(e) => $emit('remove', e)"
/>
</slot>
</div>
</template>
<!-- Default element slots -->
<template v-for="(component, slot) in elementSlots" #[slot]><slot :name="slot" :el$="el$"><component :is="component" :el$="el$"/></slot></template>
</component>
</template>
<script>
import { ref, markRaw } from 'vue'
import { ObjectElement } from '@vueform/vueform'
export default {
// Copy all properties of ObjectElement (eg. emits, mixins, ...)
...ObjectElement,
name: 'NameElement',
props: {
// Copy props from ObjectElement
...ObjectElement.props,
// Define child elements for our ObjectElement
schema: {
required: false,
type: Object,
default: () => ({
first_name: {
type: 'text',
placeholder: 'First name',
columns: 6,
},
last_name: {
type: 'text',
placeholder: 'Last name',
columns: 6,
},
})
}
},
setup(props, context) {
// Copy all props / methods from ObjectElement
const NameElement = ObjectElement.setup(props, context)
// ObjectElement's classes from Tailwind Theme's source
// https://github.com/vueform/vueform/blob/main/themes/tailwind/classes.js#L712
const defaultClasses = ref({
container: '',
wrapper: 'grid grid-cols-12',
wrapper_sm: 'form-gap-gutter-sm',
wrapper_md: 'form-gap-gutter',
wrapper_lg: 'form-gap-gutter-lg',
wrapper_embed: '!block',
$wrapper: (classes, { Size, embed }) => ([
classes.wrapper,
classes[`wrapper_${Size}`],
embed ? classes.wrapper_embed : null,
]),
})
return {
...NameElement,
defaultClasses,
template,
}
},
}
</script>
vue
<!-- NameElement.vue -->
<template>
<component :is="elementLayout" :multiple="true" ref="container">
<template #element>
<div :class="classes.wrapper" role="group" :aria-labelledby="labelId">
<slot>
<component :is="component(element)"
v-for="(element, name) in children"
v-bind="element"
:embed="embed"
:name="name"
:key="name"
@remove="(e) => $emit('remove', e)"
/>
</slot>
</div>
</template>
<!-- Default element slots -->
<template v-for="(component, slot) in elementSlots" #[slot]><slot :name="slot" :el$="el$"><component :is="component" :el$="el$"/></slot></template>
</component>
</template>
<script>
import { ref, markRaw } from 'vue'
import { ObjectElement } from '@vueform/vueform'
export default {
// Copy all properties of ObjectElement (eg. emits, mixins, ...)
...ObjectElement,
name: 'NameElement',
props: {
// Copy props from ObjectElement
...ObjectElement.props,
// Define child elements for our ObjectElement
schema: {
required: false,
type: Object,
default: () => ({
first_name: {
type: 'text',
placeholder: 'First name',
columns: 6,
},
last_name: {
type: 'text',
placeholder: 'Last name',
columns: 6,
},
})
}
},
setup(props, context) {
// Copy all props / methods from ObjectElement
const NameElement = ObjectElement.setup(props, context)
// ObjectElement's classes from Vueform Theme's source
// https://github.com/vueform/vueform/blob/main/themes/vueform/templates/elements/ObjectElement.vue#L10
const defaultClasses = ref({
container: '',
wrapper: 'vf-row',
wrapper_sm: 'vf-row-sm',
wrapper_md: '',
wrapper_lg: 'vf-row-lg',
wrapper_embed: 'vf-row-embed',
$wrapper: (classes, { Size, embed }) => ([
classes.wrapper,
classes[`wrapper_${Size}`],
embed ? classes.wrapper_embed : null,
]),
})
return {
...NameElement,
defaultClasses,
}
},
}
</script>
... and register it for the templates
as well in vueform.config.js
:
js
// vueform.config.js
import { defineConfig } from '@vueform/vueform'
import NameElement from './NameElement.vue'
export default defineConfig({
elements: [
NameElement,
],
templates: {
NameElement,
}
})
If we would like to have non-nested data we can replace ObjectElement
with GroupElement
in the examples above.