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, prototype, allow_add, allow_delete, errors, etc.)

How it works

  • addEntry() deep-clones vars.prototype, replaces every __name__ placeholder (recursively through nested children) 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) — 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 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