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 and what the navigation links carry 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 

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 

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), 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
    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>
Copied!

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
<f:for each="{persons:contracts(profile: profile, detailBlock: publicProfile.details.contact)}" as="contract">
    ...
</f:for>
Copied!

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 

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 

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
    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>
Copied!

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.

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

Passing the 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. 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:

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

The list 

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 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). 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 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.

The options of the visitor filters 

While the content element offers a filter for visitors, 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. 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
    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>
Copied!

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.

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 

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
    data-namespace-typo3-fluid="true"
    xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
>
<p class="ace-count">{count} of {total}</p>
</html>
Copied!

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 

The list, list-and-detail, selected profiles and selected contracts elements render their profiles through the partial of their view mode, 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.

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 

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.
  2. Allow the mode: the site setting plugin.tx_academicpersons.viewMode.allowed becomes list,table,contact, see View modes.
  3. 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
    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
    }
    Copied!

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.

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 

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
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/
Copied!

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.