Collections & Pagination
The Collection built-in component proxies any resource collection endpoint. On the front-end, useCwaComponent with the withCollection() plugin unwraps the Hydra response and gives you items, pagination state, and navigation helpers — all URL-bound.
Display Component
<!-- app/cwa/components/BlogList/BlogList.vue -->
<template>
<div>
<div v-if="isLoadingCollection" class="space-y-4">
<div v-for="n in 6" :key="n" class="h-48 bg-gray-100 animate-pulse rounded" />
</div>
<div v-else-if="collectionItems" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<ArticleCard
v-for="item in collectionItems"
:key="item['@id']"
:article="item"
:to="resolveResourceLink(item, 'routePath')"
/>
</div>
<div v-if="totalPages > 1" class="flex items-center justify-center gap-4 mt-8">
<button
:disabled="!pageModel || pageModel <= 1"
class="btn"
@click="goToPreviousPage"
>
Previous
</button>
<span class="text-sm text-gray-600">
Page {{ pageModel }} of {{ totalPages }}
</span>
<button
:disabled="pageModel >= totalPages"
class="btn"
@click="goToNextPage"
>
Next
</button>
</div>
</div>
</template>
<script setup lang="ts">
import type { IriProp } from '#cwa/composables/cwa-resource'
import { useCwaComponent, withCollection } from '#imports'
const props = defineProps<IriProp>()
const {
resource,
exposeMeta,
collectionItems,
isLoadingCollection,
totalPages,
pageModel,
goToNextPage,
goToPreviousPage,
resolveResourceLink
} = useCwaComponent(props, [withCollection()])
defineExpose(exposeMeta)
</script>
Return Values
| Return | Type | Description |
|---|---|---|
collectionItems | ComputedRef<CwaResource[] | undefined> | The response's collection.member array — each item is a raw API resource, so read its fields directly (item.title, not item.data.title) |
isLoadingCollection | Ref<boolean> | true while the proxied collection fetch is in progress |
totalPages | Ref<number> | Parsed from the page parameter of the collection's view.last link; 1 when there is no last-page link |
pageModel | Ref<number> | Current page number — bound to the URL ?page= query param |
goToNextPage | () => void | Increments pageModel |
goToPreviousPage | () => void | Decrements pageModel (min: 1) |
changePage | (page: number) => void | Jump to a specific page |
resolveResourceLink | (resource, property) => string | RouteLocation | Turn an item into a link target — see below |
collectionItems is undefined until the first response arrives — guard your list with v-if="collectionItems".
Numbered Pagination
useCwaCollectionPagination returns a sliding window of page numbers — at most maxPagesToDisplay (default 7), centred on the current page. They are always plain numbers; there are no ellipsis markers.
It takes a single props object of plain numbers, which makes it a natural fit for a dedicated pagination child component:
<!-- app/components/CollectionPagination.vue -->
<template>
<nav class="flex gap-1">
<button
v-for="page of pages"
:key="page"
:class="page === currentPage ? 'font-bold' : ''"
@click="$emit('change', page)"
>
{{ page }}
</button>
</nav>
</template>
<script setup lang="ts">
import { useCwaCollectionPagination } from '#cwa/composables/cwa-collection-pagination'
import type { CwaPaginationEmits, CwaPaginationProps } from '#cwa/composables/cwa-collection-pagination'
const props = defineProps<CwaPaginationProps>() // { currentPage, totalPages, maxPagesToDisplay }
defineEmits<CwaPaginationEmits>() // next, previous, change
const { pages } = useCwaCollectionPagination(props)
</script>
Then drop it into the collection component in place of the Previous/Next buttons:
<CollectionPagination
:current-page="pageModel || 1"
:total-pages="totalPages"
:max-pages-to-display="7"
@next="goToNextPage"
@previous="goToPreviousPage"
@change="changePage"
/>
Linking to a Resource's Page
resolveResourceLink(item, property) — destructured from useCwaComponent(props, [withCollection()]) above — returns item[property] when the item carries its own path (e.g. routePath on a published page-data resource), and otherwise a Vue Router location targeting CWA's internal resource-page route.
<CwaLink :to="resolveResourceLink(item, 'routePath')">{{ item.title }}</CwaLink>
:to, not :href — the fallback is a route location object, not a string. Pass the property name too; without it the helper always falls through to the internal route.This is how you turn a collection item into a link without knowing its URL in advance — including draft entries that have no public route yet.
User-Controlled Sorting and Filtering
useQueryBoundModel binds any query parameter to the URL, triggering a re-fetch when it changes:
import { useQueryBoundModel } from '#imports'
// Pass the bracket prefix — the composable expands it to order[<key>] in both directions
const { model: sortOrder } = useQueryBoundModel('order', { defaultValue: { createdAt: 'desc' } })
// sortOrder.value → { createdAt: 'desc' }; writing it updates ?order[createdAt]=... in the URL
<select
:value="sortOrder.createdAt"
@change="sortOrder = { createdAt: ($event.target as HTMLSelectElement).value }"
>
<option value="desc">Newest first</option>
<option value="asc">Oldest first</option>
</select>
Assign a new object rather than mutating a key — the model only writes back to the URL when the ref itself is reassigned.
The composable returns { model } — not the ref itself — and its second argument is an options object (defaultValue, delay, asNumber). For a plain scalar parameter, pass the parameter name and v-model the ref directly:
const { model: searchModel } = useQueryBoundModel('title', { delay: 250 })
The collection re-fetches automatically when the URL changes. Default query parameters set in the PHP Collection entity are the baseline; useQueryBoundModel lets users override them.
Admin: Setting Up a Collection
The manager tab (auto-provided by the built-in Collection entity) lets admins:
- Select which resource to list (
resourceIri— searchable dropdown) - Set
perPage— how many items per page - Configure
defaultQueryParameters— default sort and filter values
No custom admin tab file needed for the standard Collection component — it's provided by the bundle.