---
title: "Item and list partials"
manual: "Academic Profiles"
version: "3.0"
permalink: "https://docs.typo3.org/permalink/fgtclb/academic-persons:templates-item-and-list-partials@3.0"
source: "Templates/Partials/Index.rst"
rendered: "2026-10-26T17:55:22+00:00"
---

# Item and list partials {#templates-item-and-list-partials}

The profile item and the profile list are built from small partials. An
integrator changes one aspect of them by putting one file of a few lines into
the configured partial root path, instead of copying a whole template and
maintaining it against every later correction.

The entry points stay `Profile/Item.html`,
`Profile/List/ItemList.html`, `Profile/List/Pagination.html` and
`Profile/List/AlphabetPagination.html`. The first two delegate to the
partials below, with unchanged arguments. The page navigation is a leaf that
gained a class and the argument `activeListArguments`. The letter navigation
gained both as well, and `alphabetFilterLetters` next to them; a project copy
of either keeps working and renders the links it rendered before - see
[the letter navigation](https://docs.typo3.org/permalink/fgtclb/academic-persons:templates-letter-navigation@3.0) and
[what the navigation links carry](https://docs.typo3.org/permalink/fgtclb/academic-persons:templates-navigation-links@3.0) below.

An existing copy of the item or the list body keeps working - and stops
profiting from the partials below, because a copy does not render them.

## The item {#templates-item-partials}

`Partials/Profile/Item.html` is rendered by the list, list-and-detail,
card, selected-profiles and selected-contracts elements, and by the contacts
element of `EXT:academic_contacts4pages`. It renders:

| Partial | Renders | Class |
| --- | --- | --- |
| `Profile/Item/DetailLink.html` | The address of the detail view, as text | none, it renders no element |
| `Profile/Item/Name.html` | The academic title and the names, as text | `ace-name`, next to `ace-title` on the heading |
| `Profile/Item/Contracts.html` | The contract block, through `Profile/Contract/Item.html` | none, the list element belongs to that partial |
| `Profile/Item/Image.html` | The image, through the shared partial of `EXT:academic_base` | `ace-image`, and `ace-picture` on its `<picture>`, both from the shared partial |

### Which contracts an item renders {#templates-contract-selection}

`Profile/Contract/Item.html` renders the one contract a caller passes as
`contract` \- the selected contracts element and
`EXT:academic_contacts4pages` do. Otherwise it renders the contracts the
element's options select (see [Which contracts a profile shows](https://docs.typo3.org/permalink/fgtclb/academic-persons:configuration-contract-display@3.0)), through
the ViewHelper `<persons:contracts>`. The detail view's
`Profile/PublicProfile/Position.html` and
`Profile/PublicProfile/Contact.html` use it with their block of
`Settings.yaml` instead:

**EXT:my_sitepackage/Resources/Private/Partials/Profile/Contract/Item.html**

```html
<html
    data-namespace-typo3-fluid="true"
    xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
    xmlns:persons="http://typo3.org/ns/FGTCLB/AcademicPersons/ViewHelpers"
>
<f:if condition="{contract}">
    <f:then>
        <p class="my-contract">{contract.position}</p>
    </f:then>
    <f:else>
        <f:for each="{persons:contracts(profile: profile, settings: settings)}" as="contract">
            <p class="my-contract">{contract.position}</p>
        </f:for>
    </f:else>
</f:if>
</html>
```

Keep the `contract` branch in a copy: without it, the selected contracts element
and `EXT:academic_contacts4pages` would show the profile's contracts instead of
the chosen one.

**The detail view, one block**

```html
<f:for each="{persons:contracts(profile: profile, detailBlock: publicProfile.details.contact)}" as="contract">
    ...
</f:for>
```

`settings` are the plugin settings, `detailBlock` a block of
`publicProfile.details`; a passed block wins. Without either, every contract is
returned. The ViewHelper also limits the page cache lifetime while a validity
option applies, which is why a template should call it rather than filter
`{profile.contracts}` itself: a copy that loops over
`{profile.contracts}` keeps working, and ignores the options.

### The name of a function type {#templates-function-type-name}

`Profile/Contract/FunctionTypeName.html` renders the name of a function
type for a profile's gender: the female name for `ms`, the male name for
`mr`, and the general name otherwise or where the gendered name is empty. It
takes the arguments `functionType` and `gender`. The position line of the
detail view renders it, and so does `Profile/Contract/Field.html` for
the field `contracts.functionType` of the fields to show. For that,
`Profile/Contract/Item.html` hands the profile's gender to the field
partial as `gender`. A project copy of `Profile/Contract/Item.html` that
does not pass it shows the general name.

### Two of them render text, not markup {#templates-item-partials-text}

`Profile/Item/Name.html` and `Profile/Item/DetailLink.html` produce
a **value**. The item hands it to `Profile/Header.html` or
`Profile/SectionHeader.html`, which put it inside their heading element -
and escape it there, once, on the way out.

An override of either therefore renders the profile values raw:

**EXT:my_sitepackage/Resources/Private/Partials/Profile/Item/Name.html**

```html
<html
    data-namespace-typo3-fluid="true"
    xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
>
<f:format.raw>{profile.lastName}</f:format.raw>, <f:format.raw>{profile.firstName}</f:format.raw>
</html>
```

Without `<f:format.raw>` the value is escaped twice, and a name such as
`O'Neill` reaches the browser as the markup `O&amp;#039;Neill`, which a visitor
reads as `O&#039;Neill`. Markup written in one of these two partials arrives as
literal text for the same reason - the heading element is what the header
partials own.

> [!WARNING]
> These two partials render unescaped text, so they are only safe where
> something else escapes them: as an argument of a partial that writes them
> into an element, which is what `Profile/Item.html` does. Never render
> one of them in an output position of a template - `{f:render(partial:
> 'Profile/Item/Name', ...)}` written into markup would emit the stored profile
> values unescaped.

Every other partial on this page renders markup and is written as any Fluid
partial is.

### Passing the detail page {#templates-item-partials-detail-page}

`Profile/Item/DetailLink.html` builds the address from
`plugin.tx_academicpersons.detailPid`, or stays on the current page for the
list-and-detail element, which shows the detail view itself. While
`plugin.tx_academicpersons.detailLink` is `none`, it renders nothing for every
other element and the name is shown as text, see
[Names without a detail link](https://docs.typo3.org/permalink/fgtclb/academic-persons:configuration-detail-link@3.0). An override of the partial follows that
setting only when it checks it as well.

A template that renders an item without those plugin settings passes the page
instead. A passed page wins over both branches, the list-and-detail one
included, and links whatever `plugin.tx_academicpersons.detailLink` says:

```html
<f:render
    partial="Profile/Item"
    arguments="{
        profile: profile,
        settings: settings,
        data: data,
        detailPid: 42
    }"
/>
```

<a id="templates-letter-navigation"></a>

## The list {#templates-list-partials}

`Partials/Profile/List/ItemList.html` renders:

| Partial | Renders | Class |
| --- | --- | --- |
| `Profile/List/ResultCount.html` | Nothing | none, it renders no element |
| `Profile/List/GroupHeader.html` | The heading above one group of a grouped list | `ace-group-header`, next to `ace-title` on the heading |
| `Profile/ViewMode/<Mode>.html` | The profiles, in the [view mode](https://docs.typo3.org/permalink/fgtclb/academic-persons:templates-view-modes@3.0) of the element - once per group of a grouped list | depends on the mode |
| `Profile/List/Pagination.html` | The page navigation | `ace-pagination`, on the `<nav>` |
| `Profile/List/EmptyState.html` | The text shown when nothing was found | `ace-empty` |

`Profile/List/AlphabetPagination.html` is the sixth, and the one
`ItemList.html` does not render: the list template
`Templates/Profile/List.html` renders it above the list body, when
`settings.alphabetPaginationEnabled` is switched on and no profiles are
selected by hand - a manual selection ignores the letter filter. Its
`<nav>` carries `ace-navigation ace-alphabet-navigation`.

The list template passes it `alphabetFilterLetters` next to `demand`: every
letter from `a` to `z`, mapped to whether the list holds a profile under it
(see [Which letters lead somewhere](https://docs.typo3.org/permalink/fgtclb/academic-persons:developers-letter-availability@3.0)). The partial renders:

-   A letter with profiles as a link `a.ace-link` in a plain
    `li.ace-list-item`.
-   A letter without profiles as `li.ace-list-item.disabled` holding a
    `span.ace-link`, no link, with a `visually-hidden` "no profiles" for
    assistive technology.
-   The selected letter as `li.ace-list-item.active`, its `ace-link` with
    `aria-current="page"`. It is a link back to the list without a
    letter, with a `visually-hidden` "show all profiles", only when
    [the reset setting](https://docs.typo3.org/permalink/fgtclb/academic-persons:configuration-letter-navigation@3.0) is on, and a
    `span.ace-link` otherwise.
-   **A-Z** as `li.ace-list-item.active` with
    `aria-current="page"` on its link while no letter is selected.

The `<nav>` is named by the label `list.alphabetFilter.navigation`. A
list template of a project that renders this partial without
`alphabetFilterLetters` gets every letter as a link, as before.

### What the navigation links carry {#templates-navigation-links}

The list action assigns `activeListArguments`: the choices of the visitor the
list is shown with, as the plugin accepted them - the page while it is not the
first, the letter while one is selected. A value of the request the plugin does
not accept from a visitor is never part of it, nor is a value the content
element sets, such as the sorting. The list template passes it to the letter
navigation, and `Profile/List/ItemList.html` to the page navigation.

Both navigations build every link from it and change only what the link is
responsible for, through the view helper `persons:listArguments`:

**A page link and a letter link**

```html
<html
    xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
    xmlns:persons="http://typo3.org/ns/FGTCLB/AcademicPersons/ViewHelpers"
    data-namespace-typo3-fluid="true"
>

<f:link.action
    arguments="{demand: '{persons:listArguments(arguments: activeListArguments, overrides: {currentPage: page})}'}"
>{page}</f:link.action>

<f:link.action
    arguments="{demand: '{persons:listArguments(arguments: activeListArguments, overrides: {alphabetFilter: letter}, remove: \'currentPage\')}'}"
>{letter}</f:link.action>
```

`overrides` sets values, `remove` drops the comma separated keys it names. A
letter link and **A-Z** drop the page, so a new letter starts on the
first page.

Besides page and letter, a visitor chooses the [view mode](https://docs.typo3.org/permalink/fgtclb/academic-persons:templates-view-modes@3.0), where the element offers the switch, and the
[filters](https://docs.typo3.org/permalink/fgtclb/academic-persons:templates-visitor-filters@3.0), where the element offers them. A
link carries each while it differs from the default. They reach
`activeListArguments` like the other two, and both navigations carry them
without an edit. A project copy of either partial, and a list template that
does not pass `activeListArguments` on, keeps the links it has and drops the
view mode and the filters on the next click, until it adopts the view helper as
above.

### The options of the visitor filters {#templates-visitor-filters}

While the content element offers a [filter for visitors](https://docs.typo3.org/permalink/fgtclb/academic-persons:configuration-visitor-filters@3.0), the list action assigns its options to
`filterOptions`: `filterOptions.functionTypes` holds the function types,
`filterOptions.organisationalUnits` the organisational units, each ordered by
name in the language of the page. Each is known by its uid in the default
language, which is the value a link carries. A filter the element does not
offer has no key, and a manual selection gets no options at all. The active
values are `demand.functionTypeFilter` and `demand.organisationalUnitFilter`,
`0` for none.

The shipped list template renders them in `Profile/List/Filter.html`,
above the letter navigation: one select per filter with options, "all" first,
the active filter selected, and a submit button. The form posts to the
`filter` action, which redirects to the URL of the filtered list, see
[Filtered lists](https://docs.typo3.org/permalink/fgtclb/academic-persons:configuration-route-enhancers-filters@3.0). It keeps the view mode and the
letter, and contains no JavaScript. The partial takes `filterOptions`,
`demand`, `activeListArguments` and `contentElementUid`, which keeps the ids of
a list and a list-and-detail element on one page apart. Two elements of the same
plugin on one page share their arguments, as their page links do, so a
submission is checked against the options of the element that renders first. A
project copy of `Profile/List.html` that does not render the partial shows
no form.

A project that prefers links to a form links a filtered list with the action
link view helper, which adds the cHash the cached list needs, and keeps the
other choices of the visitor:

**A link per function type**

```html
<html
    xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
    xmlns:persons="http://typo3.org/ns/FGTCLB/AcademicPersons/ViewHelpers"
    data-namespace-typo3-fluid="true"
>

<f:for each="{filterOptions.functionTypes}" as="functionType">
    <f:link.action
        arguments="{demand: '{persons:listArguments(arguments: activeListArguments, overrides: {functionTypeFilter: functionType.uid}, remove: \'currentPage\')}'}"
    >{functionType.functionName}</f:link.action>
</f:for>
```

`Profile/List/Items.html`, the grid the view mode `list` renders,
carries the Bootstrap row and column classes, so overriding it changes the grid
of all four elements **of this extension** at once. It takes either `profiles`
or `contracts`: the selected contracts element passes the latter, because it
shows one item per selected contract, and each of those items renders that one
contract rather than every contract of its profile.

`Profile/List/EmptyState.html` is rendered by all four as well. The
selected contracts element passes the label about contracts to it through its
`key` argument; every other caller takes the default.

> [!NOTE]
> The contacts element of `EXT:academic_contacts4pages` is **not** one of the
> four. It renders `Profile/Item` through a partial of its own,
> `Contacts/Item.html`, inside a grid of its own, and it has no empty
> state at all - a page without contacts renders nothing. Overriding
> `Profile/List/Items.html` therefore does not change how its contacts
> are arranged; overriding `Profile/Item.html` or one of the item
> partials does change how each of them looks, and a project that wants a
> card of its own for the contacts only overrides `Contacts/Item.html`
> of that extension instead.

The grid is an `ace-itemlist` and the empty state an `ace-empty` in all four
elements. What tells the elements apart is their outermost element,
`academic-persons-list`, `academic-persons-card`, `academic-persons-profiles` or
`academic-persons-contracts`.

### The result count is an empty hook {#templates-list-partials-result-count}

`Profile/List/ResultCount.html` renders nothing on purpose - printing a
number by default would change every list that exists. It receives the number of
profiles rendered below it as `count`, the number the list found as `total` \-
the same one unless the list paginates - those profiles as `profiles`, and the
`paginator` of a paginated list for its page number and page count:

**EXT:my_sitepackage/Resources/Private/Partials/Profile/List/ResultCount.html**

```html
<html
    data-namespace-typo3-fluid="true"
    xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
>
<p class="ace-count">{count} of {total}</p>
</html>
```

`total` is counted in `ItemList.html` rather than read from the paginator
on purpose: `getTotalAmountOfItems()` is protected on every core version this
extension supports, so Fluid cannot reach it and
`{paginator.totalAmountOfItems}` renders an empty string.

## View modes {#templates-view-modes}

The list, list-and-detail, selected profiles and selected contracts elements
render their profiles through the partial of their [view mode](https://docs.typo3.org/permalink/fgtclb/academic-persons:configuration-view-modes@3.0), `Profile/ViewMode/<Mode>.html` \- the mode with
an upper case first letter, `ContactCards.html` for `contactCards`. The
action assigns the mode as `viewMode` and the partial name
as `viewModePartial`; the list renders the partial from
`Profile/List/ItemList.html`, the selected profiles and contracts from
their templates.

| Partial | Renders | Class |
| --- | --- | --- |
| `Profile/ViewMode/List.html` | The mode `list`, labelled **Tiles**: the grid of `Profile/List/Items.html` | those of the grid |
| `Profile/ViewMode/Table.html` | The mode `table`: a header row, then one row per profile - per contract in the selected contracts element - with one cell per entry of `settings.table.columns` | `ace-table`, inside an `ace-table-wrap` |
| `Profile/ViewMode/Table/Cell.html` | One cell of the table, by its column | none |
| `Profile/ViewMode/Switch.html` | The switch between the allowed modes, only when the content element offers it | `ace-navigation ace-view-mode-switch`, on the `<nav>` |

Every mode partial receives the same arguments: `profiles`, or `contracts` in
the selected contracts element, `settings`, `data` and `groupedProfiles` from a
grouped list. The grid is an `ace-itemlist` and the table an `ace-table`, so a
stylesheet tells the two apart by their own classes.

The switch renders one link per allowed mode - nothing while fewer than two
modes are allowed - with `rel="nofollow"`, and marks the active one with
`aria-current="true"`. Its `<nav>` is named by the label
`list.viewMode.navigation`, a mode by `list.viewMode.<mode>` of this extension -
or by its name, for a mode without a label. In the list elements a link keeps
`activeListArguments` and changes only the mode; the link to the default mode
carries no mode, and is the page itself when the visitor chose nothing else.
In the selected elements the mode is keyed by the uid of the content element in
the default language, `viewMode[<uid>]`, so that two elements on one page keep
their modes apart. Fluid cannot build that key, so their actions assign
`viewModeArguments`, the plugin arguments of the link to each mode, and the
selected templates pass it to the switch. A copy of a selected template that
does not pass it on, or a copy of `Profile/ViewMode/Switch.html` from a
3.0 development state that does not read it, renders links no element answers
to.

A project copy of `Profile/List/ItemList.html`,
`Templates/Profile/SelectedProfiles.html` or
`Templates/Profile/SelectedContracts.html` keeps rendering the grid
whatever the mode; a copy of `Templates/Profile/List.html` has no switch.

### Adding a view mode {#templates-view-modes-own}

A view mode of your own needs no template override. Taking the mode `contact`:

1.  Provide the partial `Profile/ViewMode/Contact.html` in a partial path
    of the plugin, such as that of your site package.
1.  Allow the mode: the site setting
    `plugin.tx_academicpersons.viewMode.allowed` becomes
    `list,table,contact`, see [View modes](https://docs.typo3.org/permalink/fgtclb/academic-persons:configuration-view-modes@3.0).
1.  Offer it to the editor as an item of **View Mode Default**, in page
    TSconfig, for each element that shall have it:

    **EXT:my_sitepackage/Configuration/page.tsconfig**

    ```typoscript
    TCEFORM.tt_content.pi_flexform {
      academicpersons_list.sDEF.settings\.viewMode\.default.addItems.contact = Contact cards
      academicpersons_listanddetail.sDEF.settings\.viewMode\.default.addItems.contact = Contact cards
    }
    ```

The switch offers the mode as soon as it is allowed; label it with
`list.viewMode.contact` in a language override of this extension. A mode that is
allowed without a partial fails with Fluid's error for a missing partial. Its
links carry it as a query argument until it is added to the route enhancer, see
[a view mode of your own in the URL](https://docs.typo3.org/permalink/fgtclb/academic-persons:configuration-route-enhancers-view-modes@3.0).

To add a column to the table, override `Profile/ViewMode/Table/Cell.html`,
render your column there and keep the shipped ones; a column the partial does
not know renders an empty cell. Its header is the label `list.table.<column>`,
or the column's name without one.

## Contacts for pages resolves the partials separately {#templates-partials-contacts4pages}

`EXT:academic_contacts4pages` renders `Profile/Item` through partial root
paths of its own. An override registered for the persons plugins alone does not
reach it, so register the same path twice:

**TypoScript constants**

```typoscript
plugin.tx_academicpersons.view.partialRootPath = EXT:my_sitepackage/Resources/Private/Extensions/academic_persons/Partials/
plugin.tx_academiccontacts4pages.view.partialRootPath = EXT:my_sitepackage/Resources/Private/Extensions/academic_persons/Partials/
```

That has always been true for a copy of the item; it is worth repeating here
because a small override makes it easy to forget one of the two lines.
