Creating Elements
Learn how to create new elements in Vueform.
Boilerplate
We can use the following boilerplate to create a new Vueform element:
<template>
<ElementLayout>
<template #element>
<!-- ADD YOUR ELEMENT TEMPLATE HERE -->
</template>
<!-- Default element slots -->
<template v-for="(component, slot) in elementSlots" #[slot]><slot :name="slot" :el$="el$"><component :is="component" :el$="el$"/></slot></template>
</ElementLayout>
</template>
<script>
import { ref } from 'vue'
import VueformElement from '@vueform/vueform/element'
export default VueformElement({
name: 'CustomElement',
}, {
setup(props, { element }) {
const defaultClasses = ref({
container: '', // added automatically to the element's outermost DOM in ElementLayout
})
return {}
}
})
</script>
Template
First we have the <template>
part where we are using ElementLayout
component as a wrapper for our element and we define the actual element template in #element
slot:
<template>
<ElementLayout>
<template #element>
<!-- ADD YOUR ELEMENT TEMPLATE HERE -->
</template>
<!-- ... -->
</ElementLayout>
</template>
Then we have a row that passes over each slot defined for the element to the ElementLayout
:
<template>
<ElementLayout>
<!-- ... -->
<template v-for="(component, slot) in elementSlots" #[slot]><slot :name="slot" :el$="el$"><component :is="component" :el$="el$"/></slot></template>
</ElementLayout>
</template>
This is how we can use inline slots for label, description, etc:
<CustomElement name="custom">
<template #label>Foo</template>
<template #description>Bar</template>
</CustomElement>
Options
The component should export a VueformElement
, where the first argument are the element options and the second contains all the regular propeties as we'd normally define for a component.
<script>
import VueformElement from '@vueform/vueform/element'
export default VueformElement({
name: 'CustomElement',
}, /* ... /* )
</script>
The name
option in the first argument must be the element name suffixed with Element
using PascalCase. Once we register this element we'll be able to use it with type: 'custom'
.
Setup
The second argument of VeuformElement
includes all the regular properties we'd normally defined for a component. This includes setup()
function, which only differentiates from the original version that it includes an element
property in context
(the second argument of setup()
):
<script>
import VueformElement from '@vueform/vueform/element'
export default VueformElement({
name: 'CustomElement',
}, {
setup(props, context) {
const element = context.element
}
})
</script>
Using Element Properties & Methods
Using the element
property we can access all the component's default properties and methods. For example if'd like to watch the element's model
we can do:
<script>
import { watch } from 'vue'
import VueformElement from '@vueform/vueform/element'
export default VueformElement({
name: 'CustomElement',
}, {
setup(props, { element }) {
watch(element.model, (newValue, oldValue) => {
console.log('model changed from', oldValue, 'to', newValue)
})
}
})
</script>
New Properties & Methods
We can also return new properties and methods that can be accessed in the component's template:
<template>
<ElementLayout>
<template #element>
{{ foo }}
</template>
<!-- ... --->
</ElementLayout>
</template>
<script>
import { ref } from 'vue'
import VueformElement from '@vueform/vueform/element'
export default VueformElement({
name: 'CustomElement',
}, {
setup(props, { element }) {
const foo = ref('bar')
return {
foo,
}
}
})
</script>
Default Classes
Elements are using a dynamic class system as described here. We can set default classes for elements using defaultClasses
:
<template>
<ElementLayout>
<template #element>
<div :class="classes.wrapper">...</div>
</template>
<!-- ... --->
</ElementLayout>
</template>
<script>
import { ref } from 'vue'
import VueformElement from '@vueform/vueform/element'
export default VueformElement({
name: 'CustomElement',
}, {
setup(props, { element }) {
const defaultClasses = ref({
container: '', // added automatically to the element's outermost DOM in ElementLayout
wrapper: 'element-wrapper',
})
return {
defaultClasses,
}
}
})
</script>
Element Properties and Methods
We can dump context.element
to see the available properties and methods for the element and consult TextElement
's API reference to see what they do:
<script>
import VueformElement from '@vueform/vueform/element'
export default VueformElement({
name: 'CustomElement',
}, {
setup(props, { element }) {
console.log(element)
}
})
</script>
Registering Elements
New elements can be registered in vueform.config.js
:
// vueform.config.js
import CustomElement from './path/to/CustomElement.vue'
export default {
elements: [
CustomElement,
],
// ...
}
Examples
Simple Example
Here's how we can create a very simple text input field that uses the element's model
and dynamic classes:
<template>
<ElementLayout>
<template #element>
<input
v-model="model"
:class="classes.input"
/>
</template>
<!-- Default element slots -->
<template v-for="(component, slot) in elementSlots" #[slot]><slot :name="slot" :el$="el$"><component :is="component" :el$="el$"/></slot></template>
</ElementLayout>
</template>
<script>
import { ref } from 'vue'
import VueformElement from '@vueform/vueform/element'
export default VueformElement({
name: 'CustomElement',
}, {
setup(props, { element }) {
const defaultClasses = ref({
container: '', // added automatically to the element's outermost DOM in ElementLayout
input: 'form-text-input',
input_danger: 'has-errors',
$input: (classes, { isDanger }) => ([
classes.input,
isDanger ? classes.input_danger : null,
])
})
return {
defaultClasses,
}
}
})
</script>
<style lang="scss">
.form-text-input {
border: 1px solid black;
outline: none;
width: 100%;
&.has-errors {
border: 1px solid red;
}
}
</style>
If a class name has a counterpart prefixed with
$
function, it will be dynamic. The function's first argument is the class list and the second is the component properties.
Now if we use it we'll have a simple input field with a red border if it has any errors:
<Vueform>
<CustomElement name="custom" rules="email" />
</Vueform>
Advanced Example
If we'd like to create a more advanced element in terms of
<template>
<ElementLayout>
<template #element>
<div :class="classes.wrapper">
<select v-model="day" :class="classes.day">
<option v-for="day, i in days" :value="day" :key="i">
{{ day }}
</option>
</select>
<select v-model="month" :class="classes.month">
<option v-for="month, i in months" :value="month[0]" :key="i">
{{ month[1] }}
</option>
</select>
<select v-model="year" :class="classes.year">
<option v-for="year, i in years" :value="year" :key="i">
{{ year }}
</option>
</select>
</div>
</template>
<template v-for="(component, slot) in elementSlots" #[slot]><slot :name="slot" :el$="el$"><component :is="component" :el$="el$"/></slot></template>
</ElementLayout>
</template>
<script>
import VueformElement from '@vueform/vueform/element'
import { ref, computed } from 'vue'
export default VueformElement({
name: 'BirthdayElement',
}, {
setup(props, { element }) {
const months = ref([
['01', 'January'],
['02', 'February'],
['03', 'March'],
['04', 'April'],
['05', 'May'],
['06', 'June'],
['07', 'July'],
['08', 'August'],
['09', 'September'],
['10', 'October'],
['11', 'November'],
['12', 'December'],
])
const days = ref([...Array(31).keys()].map(i=>i<9?'0'+(i+1):String(i+1)))
const years = ref([...Array(100).keys()].map(i=>(new Date().getFullYear())-i))
// Creating models for day, month, year from the element's model
const datePart = (part) => {
return {
get: () => {
return (element.model.value||'').split('-')[part]||null
},
set: (value) => {
let date = (element.model.value||'').split('-')
date[part] = value
element.model.value = `${date[0]||'0000'}-${date[1]||'00'}-${date[2]||'00'}`
}
}
}
const day = computed(datePart(2))
const month = computed(datePart(1))
const year = computed(datePart(0))
return {
day,
month,
year,
months,
days,
years,
}
},
data() {
return {
defaultClasses: {
container: '',
wrapper: 'form-input-wrapper',
input: 'form-text-input',
day: 'day',
month: 'month',
year: 'year',
$day: (classes) => ([
classes.input,
classes.day,
]),
$month: (classes) => ([
classes.input,
classes.month,
]),
$year: (classes) => ([
classes.input,
classes.year,
]),
},
}
},
})
</script>
<style lang="scss">
.form-input-wrapper {
width: 100%;
display: flex;
column-gap: 0.5rem;
}
.form-text-input {
border: 1px solid #000000;
padding: 0.25rem 0.5rem;
appearance: auto;
&.day {
width: calc(3 / 12 * 100%);
}
&.month {
width: calc(5 / 12 * 100%);
}
&.year {
width: calc(4 / 12 * 100%);
}
}
</style>
We can use the created element as BirthdayElement
:
<Vueform>
<BirthdayElement name="birthday" label="Birthday" rules="after:2022-02-01" />
</Vueform>