The CWA is in heavy development
The CWA is still in alpha and not ready for production - some code and implementations are likely to change. If you would like to try out the CWA, please enjoy what we have provided and feel free to provide feedback, or get involved on GitHub.
Component
useCwaFormCollection
Manage a Symfony CollectionType field — dynamically add and remove entries at runtime.
useCwaFormCollection manages a Symfony CollectionType field. It clones the prototype from the API response each time addEntry() is called, assigns a monotonic index, and tracks the list of active entry full_name strings. Child components then call useCwaFormInput for each sub-field; their lifecycle hooks handle registration/unregistration automatically.
const { entries, addEntry, removeEntry, vars } = useCwaFormCollection(iri, 'form_name[fieldName]')
Parameters
| Parameter | Type | Description |
|---|---|---|
iri | Ref<string | undefined> | IRI of the Form component resource |
collectionFullName | string | full_name of the CollectionType field (e.g. registration[tags]) |
Return value
| Property | Type | Description |
|---|---|---|
entries | ComputedRef<string[]> | Ordered list of active entry full_name strings |
addEntry | () => void | Clone the prototype and append a new entry |
removeEntry | (fullName: string) => void | Remove an entry by its full_name |
vars | ComputedRef | Collection field vars from the API (label, prototype, allow_add, allow_delete, errors, etc.) |
How it works
addEntry()deep-clonesvars.prototype, replaces every__name__placeholder (recursively through nested children) with a monotonic counter, then pushes the resolvedfull_nameontoentries- Indices are never reused after removal, so Vue
v-forkeys remain stable - The prototype is never mutated
- Child components must call
useCwaFormInput(iriRef, entryFullName)(oruseCwaFormInput(iriRef, \${entryFullName}subField`)for compound types) — theironMounted/onUnmounted` hooks handle registration/deregistration with the form store
Example
Simple collection (TextType entries)
<!-- parent component -->
<script setup lang="ts">
const props = defineProps<{ iri: string }>()
const iriRef = toRef(props, 'iri')
const tags = useCwaFormCollection(iriRef, 'example_form[tags]')
</script>
<template>
<div>
<TagEntry
v-for="entry in tags.entries.value"
:key="entry"
:iri="props.iri"
:entry-full-name="entry"
@remove="tags.removeEntry(entry)"
/>
<button v-if="tags.vars.value?.allow_add" @click.prevent="tags.addEntry()">
Add Tag
</button>
</div>
</template>
<!-- TagEntry.vue — child component -->
<script setup lang="ts">
const props = defineProps<{ iri: string | undefined, entryFullName: string }>()
defineEmits<{ remove: [] }>()
const iriRef = toRef(props, 'iri')
const field = useCwaFormInput(iriRef, props.entryFullName)
</script>
<template>
<div>
<UInput v-model="field.value.value" @blur="field.onBlur" @input="field.onInput" />
<UButton @click="$emit('remove')">Remove</UButton>
</div>
</template>
Compound collection (sub-fields per entry)
For a CollectionType where each entry is a compound type (e.g. a ChildType with name and age sub-fields), the child component calls useCwaFormInput for each sub-field:
<!-- ChildEntry.vue -->
<script setup lang="ts">
const props = defineProps<{ iri: string | undefined, entryFullName: string }>()
defineEmits<{ remove: [] }>()
const iriRef = toRef(props, 'iri')
const name = useCwaFormInput(iriRef, `${props.entryFullName}[name]`)
const age = useCwaFormInput(iriRef, `${props.entryFullName}[age]`)
</script>
Notes
- Use
vars.value?.allow_add/vars.value?.allow_delete(from the API response) to conditionally show add/remove controls - Collection-level errors (e.g. min/max count violations) are in
vars.value?.errors— render them separately from individual entry errors - Entries removed via
removeEntry()are immediately dropped fromentries; the child component unmounts anduseCwaFormInputunregisters the field values, so they are excluded from the nextsubmit()payload automatically - See the Forms guide for full field-type examples