How-to library · Developer docs

How-to library

Task-shaped pages: a title that starts with a verb, the situation in a few lines, the steps, the working code in both engines, and why this way rather than another. Found from a search, done, and left.

Render a custom entity listing and detail page

The listing renders the definition's template with the records under the plural identifier plus _pagination; the detail renders the layout's template with the record under the identifier and as record. Both are 404 without a template.

Context

Context. Saving a definition with a path and a record path creates the routes: /<path>, /<path>/2, /<path>/tags/<slugs> and /<path>/categories/<slugs> for the listing, /<record path>/<slug> for one record. The listing renders the template named on the definition; the detail renders the template of the definition's layout. Each is a 404 while its template is missing, which is the usual first symptom.

Steps

  1. Name the listing template on the definition and point the definition's layout at the detail template. Deploy both.
  2. In the listing, loop the plural variable, print the tag filter state if the route has one, and include a pagination partial driven by _pagination.
  3. In the detail, read the record under its identifier; its SEO settings are on the active revision.
  4. For several looks from one definition, branch inside the one template on a select area: the platform allows one layout per definition.

Template code

Template code
{* templates/stores/index.html *}
{if isset($tag)}<h1>Stores tagged "{$tag->getName()|escape}"</h1>{else}<h1>Our stores</h1>{/if}
{foreach $stores as $store}
  <h2><a href="{$store->getFullPath()}">{$store->getName()|escape}</a></h2>
{/foreach}
{if $_pagination->getPagesCount() > 1}
  <nav class="pagination">
  {foreach $_pagination->getPages(10) as $p}
    <a href="{$p->getUrl()}" class="{if $p->isCurrent()}active{/if}">{$p->getNumber()}</a>
  {/foreach}
  </nav>
{/if}

{* templates/stores/show.html *}
{$seo = $store->getActiveRevision()->getSeoSettings()}
{if $store->getAreaText('kind') == 'outlet'}{include './show-outlet.html'}{else}{include './show-standard.html'}{/if}
{# templates/stores/index.html #}
{% if tag is defined and tag %}<h1>Stores tagged "{{ tag.getName()|e }}"</h1>{% else %}<h1>Our stores</h1>{% endif %}
{% for store in stores %}
  <h2><a href="{{ store.getFullPath() }}">{{ store.getName()|e }}</a></h2>
{% endfor %}
{% if _pagination.getPagesCount() > 1 %}
  <nav class="pagination">
  {% for p in _pagination.getPages(10) %}
    <a href="{{ p.getUrl() }}" class="{% if p.isCurrent() %}active{% endif %}">{{ p.getNumber() }}</a>
  {% endfor %}
  </nav>
{% endif %}

{# templates/stores/show.html #}
{% set seo = store.getActiveRevision().getSeoSettings() %}
{% if store.getAreaText('kind') == 'outlet' %}{{ include('stores/show-outlet.html') }}{% else %}{{ include('stores/show-standard.html') }}{% endif %}

Why this and not that

Not a page with a component that queries the records: the built-in listing gives paging, tag and category routes and the plural variable for free. Not a listing path that collides with a module prefix (blogs, products): definition routes are registered ahead of the modules and would take the module over. Page size and default order are settings; a positioned definition is forced into position order.

Go deeper

Was this page helpful?

Your answer goes to the docs team.