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

ParameterTypeDescription
iriRef<string | undefined>IRI of the Form component resource
collectionFullNamestringfull_name of the CollectionType field (e.g. registration[tags])

Return value

PropertyTypeDescription
entriesComputedRef<string[]>Ordered list of active entry full_name strings
addEntry() => voidClone the prototype and append a new entry
removeEntry(fullName: string) => voidRemove an entry by its full_name
varsComputedRefCollection field vars from the API (label, allow_add, allow_delete, errors, etc.)

How it works

  • addEntry() deep-clones the collection field's prototype — a sibling of vars on the form view ($cwa.forms.getForm(iri)[collectionFullName].prototype), not one of its vars — replaces every __name__ placeholder (recursively through nested children, including Symfony's __name__label__ sentinel label, which is dropped) with a monotonic counter, then pushes the resolved full_name onto entries
  • Indices are never reused after removal, so Vue v-for keys remain stable
  • The prototype is never mutated
  • Child components must call useCwaFormInput(iriRef, entryFullName) (or useCwaFormInput(iriRef, \${entryFullName}subField`)for compound types) — each child registers its field value with the form store during setup (immediately, not on mount) and clears it inonBeforeUnmount`

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 from entries; the child component unmounts and useCwaFormInput unregisters the field values, so they are excluded from the next submit() payload automatically
  • See the Forms guide for full field-type examples