Listings
List-container
Renders a product listing using the list-item template. If no products are found, the message defined in list-empty is displayed.
The optional data-role="pagination" element is populated with content defined via pagination in the _listing.tpl (listing definitions) file (_listing_article.tpl for articles, etc.). Different templates can be specified using the data-name attribute.
WARNING! You must not wrap the pagination in a conditional display check. Such code would break when filtering via AJAX. If you need to hide pagination for single-page listings, add the condition inside <i:list-pagination> instead.
{# uses the "default" (or unnamed) template from _listing.tpl #}
<i:list-container type="product">
<div data-role="pagination"></div>
<div data-role="empty"></div>
<div data-role="list"></div>
</i:list-container><i:list-container type="product">
<div data-role="pagination"></div>
<div data-role="empty"></div>
<div data-role="list" data-name="short"></div>
<div data-role="pagination" data-name="simple" ></div>
</i:list-container>{# wrap boxes in groups of three into .wrap, insert <hr> between groups, and add class "third" #}
<i:list-container type="product" wrap="3:div.wrap" append="3:hr" box-class="3:third">
<div data-role="empty"></div>
<div data-role="list"></div>
</i:list-container>{# wrap boxes in groups of three into .wrap and .www-rap, insert <hr> between groups, and add class "third" #}
<i:list-container type="product" wrap="3:div.wrap.www-rap" append="3:hr" box-class="3:third">
<div data-role="empty"></div>
<div data-role="list"></div>
</i:list-container>{# uses the "news" template from _listing.tpl #}
<i:list-container type="product" name="news">
<div data-role="empty"></div>
<div data-role="list"></div>
</i:list-container>Attributes of list-container
| Attribute | Required | Description |
|---|---|---|
| type | yes | listing type |
| source | no | Twig object with listing parameters (cannot be combined with collection) |
| collection | no | list of items to render (cannot be combined with source and does not support pagination) |
| name | no | item template name (as defined by list-item) |
| analytics-name | no | label for analytics tracking (e.g. "product.detail.bottom") |
| wrap | no | wrap groups of boxes |
| append | no | append a tag after a group of boxes |
| box-class | no | add a CSS class |
| hide-empty | no | hide the entire container when it has no items (no value needed) |
| scroll-offset | no | CSS selector for accounting for a fixed header |
| insert-before | no | name of a listing-insert template to insert before each item |
| insert-after | no | name of a listing-insert template to insert after each item |
| insert-arguments | no | arguments to pass to the insert template - Twig written as {{ {abc: product.id} }} is processed via json_encode, so only simple values (numbers, arrays, simple objects) are supported |
| class | --- | CSS class |
hide-empty - hide listing with no items
When the hide-empty attribute is present (specified without a value), the container is not rendered at all if it contains no items. With this attribute, the empty listing definition is not used.
<i:list-container type="product" source="{kategorie:123,limit:10}" hide-empty>
<div data-role="list"></div>
</i:list-container>source - listing parameters
If the source attribute is not specified, the default listing for the current page is used. For example, a category page will list products from that category, etc. A listing without a specified source updates the URL on changes (page, filter, ...).
Global variables can be used in the parameters, but no local variables are allowed. The expression must work identically even if the rest of the template is deleted.
<i:list-container type="product" source="{kategorie:123,limit:10}">
<div data-role="empty"></div>
<div data-role="list"></div>
</i:list-container><i:list-container type="product" source="{kategorie:kategorie.id,limit:10}">
<div data-role="empty"></div>
<div data-role="list"></div>
</i:list-container>wrap - wrapping boxes into a single tag
The wrap attribute defines which tag with which class (class is optional) and under what conditions the boxes should be wrapped.
<i:list-container source="zdroj_zbozi" wrap="3:ul.sublist" class="list">
<div data-role="empty"></div>
<div data-role="list"></div>
</i:list-container><div class="list">
<ul class="sublist">
<li>product</li>
<li>product</li>
<li>product</li>
</ul>
<ul class="sublist">
<li>product</li>
<li>product</li>
<li>product</li>
</ul>
<ul class="sublist">
<li>product</li>
</ul>
</div>append - inserting a tag by position
The append attribute specifies a tag to insert after rendered boxes according to conditions. Multiple conditions can be specified, separated by commas -- the first matching tag is used.
<i:list-container append="3:hr.divider,last:hr.last" class="list">
<div data-role="empty"></div>
<div data-role="list"></div>
</i:list-container><div class="list">
<div class="item">product</div>
<div class="item">product</div>
<div class="item">product</div>
<hr class="divider"/>
<div class="item">product</div>
<div class="item">product</div>
<div class="item">product</div>
<hr class="divider"/>
<div class="item">product</div>
<hr class="last"/>
</div>List pagination
Pagination definition -- always in the _listing.tpl file. Defines a macro that is subsequently used for product listing in list-container.
Typically contains pagination, record count information, etc.
Global variables are not accessible; only the pagination and filterActive variables are available.
{# _listing.tpl #}
<i:list-pagination>
{%if pagination.stran > 1%}
{%trans%}celkem položek{%endtrans%}: <strong>{{pagination.nsum}}</strong>
{%trans%}strana{%endtrans%}: <strong>{{pagination.strany|raw}}</strong>
{%endif%}
</i:list-pagination>{# _listing.tpl #}
<i:list-pagination>
{# appends the next page to the listing instead of changing the page #}
<button data-role="add-page">{% trans 'listing.show-more-products' %}</button>
</i:list-pagination>Attributes of pagination
| Attribute | Default | Description |
|---|---|---|
| type | --- | item type (product, article, ...) |
| name | default | box name for linking with list-container |
Attributes of pagination.strany (pages)
| Attribute | Values | Description |
|---|---|---|
| spaces | true/false | insert non-breaking spaces between pages |
| size | 1/2/3/4/5/... | number of displayed page links |
| label_next | 'string' | text for the next page link |
| label_previous | 'string' | text for the previous page link |
| hash | 'string' | appends a hash fragment after # in links |
{# _listing.tpl #}
<i:list-pagination>
{{pagination.strany({spaces: true, label_next: 'next-page'|trans})}}
</i:list-pagination>List empty
Empty listing message definition -- always in the _listing.tpl file. Defines a macro that is subsequently used for product listing in list-container.
Only the url variable is available.
<i:list-empty type="product" name="short">
{% trans 'listing.no-products-found' %}
</i:list-empty><i:list-empty type="product">
<div data-role="default">{% trans 'listing.no-products-found' %}</div>
<div data-role="filter">
{% trans 'listing.filter-no-results' %}
<a href="{{url.reset}}">{% trans 'listing.reset-filter' %}</a>
</div>
</i:list-empty>Attributes of list-empty
| Attribute | Default | Description |
|---|---|---|
| type | --- | item type (product, article, ...) |
| name | default | name for linking with list-container |
Available variables
| Name | Value |
|---|---|
| url.reset | URL without filter and page |
List insert
Template definition that is inserted before (or after) each rendered item -- always in the _listing.tpl file. Defines a macro that is subsequently used for product listing in list-container.
<i:list-insert type="product" name="banner">
{% if limit == 16 and pagePosition % 5 === 0 %}
{% set article = repository.article.findOneBy({id:arguments.id}) %}
{{ article.name }}
{% endif %}
</i:list-insert>
<i:list-container insert-after="banner" insert-arguments="{{ {id:article.id} }}">
<div data-role="empty"></div>
<div data-role="list"></div>
</i:list-container>Attributes of list-insert
| Attribute | Description |
|---|---|
| type | item type (product, article, ...) |
| name | name for linking with list-container |
Available variables
| Name | Value |
|---|---|
| pagePosition | Position on the page (counted from 1) |
| page | Listing page number (counted from 1) |
| limit | Number of records per page (maximum) |
| recordsOnPage | Number of records on the current page (actual) |
| arguments | input data defined on the listing-container -- scalar only (passed through json_encode) |
List Item
Listing item definition -- always in the _listing.tpl file. Defines a macro that is subsequently used for product listing in list-container.
Global variables are not accessible and the position within the listing cannot be determined.
{# _listing.tpl #}
<i:list-item>
<h3>{{product.name}}</h3>
</i:list-item><i:list-item tag="li" name="news">
<h3>{{product.name}}</h3>
</i:list-item>Attributes of list-item
| Attribute | Default | Description |
|---|---|---|
| type | --- | item type (product, article, ...) |
| tag | div | which tag to wrap the resulting box in |
| name | default | box name for linking with list-container |
| class | --- | CSS class |
Complete Listing Definition Example
A production _listing.tpl defines multiple list-items (default product card, compact card for sliders, search result card), empty states with context-specific messages, and several pagination variants. The examples below are simplified from the buran production template (feature toggles stripped, core patterns preserved).
Default product card -- the main listing item used for category pages. Includes product image, labels (discount/new/custom), name, price with optional discount, availability with estimated delivery date, and add-to-cart form:
{# _listing.tpl -- default product card for category listing #}
<i:list-item type="product" class="swiper-slide cell cell--product">
<div class="white-item">
<div class="white-item__image">
<a href="{{ product.url }}">
<i:img src="product.image" format="359x479" fill />
</a>
{# Labels: discount percentage, new arrival, custom labels with dynamic color #}
<div class="labels">
{% if product.item.price.discountPercent %}
<strong class="label label__discount">-{{ product.item.price.discountPercent }}%</strong>
{% endif %}
{% if product.flagNew %}
<strong class="label label__new">{% trans 'template.label.new' %}</strong>
{% endif %}
{% for label in product.labels %}
<strong class="label" if:style="label.colorCode ? 'background: ' ~ label.colorCode">
{{ label.name }}
</strong>
{% endfor %}
</div>
</div>
<h2><a href="{{ product.url }}">{{ product.name }}</a></h2>
{# Price with optional strikethrough for discounted items #}
{% if product.item.price.discount %}
<span class="u-decoration-through">{{ product.item.price.beforeDiscount.formatted }}</span>
{% endif %}
<strong class="{{ product.item.price.discount ? 'u-color-red' }}">
{{ product.item.price.current.formatted }}
</strong>
{# Availability with estimated delivery date #}
<strong class="availability availability--{{ product.item.availability.hours }}">
{{ product.item.availability.text }}
</strong>
{% if product.item.availability.hours < 999 and product.item.availability.deliveryOption.dateDelivered %}
<span>
{% trans 'template.at-your-place' %}
<strong>{{ product.item.availability.deliveryOption.dateDelivered|date('j. n.') }}</strong>
</span>
{% endif %}
{# Add to cart #}
<form action="/Koupit" method="post">
<button type="submit" class="button--order">{% trans 'template.add-to-cart' %}</button>
<input name="zbozi" value="{{ product.id }}" type="hidden" />
</form>
</div>
</i:list-item>Compact card for sliders (name="simple") -- a simplified variant used in carousels (related products, homepage banners). Wrapped in a swiper-slide, image + name + price only:
{# _listing.tpl -- compact card for product carousels #}
<i:list-item type="product" name="simple" class="swiper-slide cell simple-box">
<span class="white-item">
<a href="{{ product.url }}">
<div class="white-item__image">
<i:img src="product.image" format="300x376" fill />
{# Labels (simplified) #}
<div class="labels">
{% if product.item.price.discountPercent %}
<strong class="label label__discount">-{{ product.item.price.discountPercent }}%</strong>
{% endif %}
{% for label in product.labels %}
<strong class="label" if:style="label.colorCode ? 'background: ' ~ label.colorCode">{{ label.name }}</strong>
{% endfor %}
</div>
</div>
<h3>{{ product.name }}</h3>
<strong>{{ product.item.price.current.formatted }}</strong>
</a>
</span>
</i:list-item>Empty listing states -- separate messages for filter results vs. default, plus specialized variants for search and wishlist:
{# _listing.tpl -- default empty states with filter/default variants #}
<i:list-empty type="product">
<p data-role="filter" class="callout">{% trans 'listing.empty.filter' %}</p>
<p data-role="default" class="callout">{% trans 'listing.empty.default' %}</p>
</i:list-empty>
{# Separate empty state for search results #}
<i:list-empty type="product" name="search">
<div data-role="filter" class="callout">{% trans 'listing.empty.filter' %}</div>
<div data-role="default" class="callout">{% trans 'listing.empty.search' %}</div>
</i:list-empty>
{# Empty state for wishlist #}
<i:list-empty type="product" name="wish">
<div data-role="default" class="callout">{% trans 'listing.empty.wish' %}</div>
</i:list-empty>Pagination variants -- buran defines multiple named paginations that are placed independently inside i:list-container via data-name:
{# Sort dropdown -- auto-generated by the system, just place the container #}
<div data-role="pagination" data-name="sort"></div>
{# Page number links #}
<i:list-pagination type="product" name="pages">
{% if pagination.pageCount > 1 %}
{{ pagination.html({'spaces': false, 'size': 2})|raw }}
{% endif %}
</i:list-pagination>
{# Result count summary (e.g. "Showing 1 - 24 of 156 products") #}
<i:list-pagination type="product" name="numbers">
{% if pagination.totalResults > 0 %}
{% trans 'listing.show' %} <strong>{{ pagination.resultsFrom }} - {{ pagination.resultsTo }}</strong>
{% trans 'listing.show-from' %} <strong>{{ pagination.totalResults }}</strong>
{% trans 'listing.products' %}
{% endif %}
</i:list-pagination>
{# Total result count (simple) #}
<i:list-pagination type="product" name="qty">
{{ pagination.totalResults }} {% trans 'listing.results' %}
</i:list-pagination>
{# "Load more" button (appends next page via AJAX instead of navigating) #}
<i:list-pagination type="product" name="add">
<a href="#" class="button" data-role="add-page">{% trans 'listing.show-more-products' %}</a>
</i:list-pagination>
{# Scroll-to-top anchor #}
<i:list-pagination type="product" name="scroll">
<a href="#main" class="button js_scrollToAnchor">{% trans 'template.scroll-top' %}</a>
</i:list-pagination>Common Source Patterns
The source attribute on i:list-container accepts a Twig object with query parameters. The collection attribute accepts a pre-loaded list of items (e.g. from content grid). When neither is specified, the listing uses the default source for the current page (e.g. a category page lists its products automatically).
{# Related/relevant products on product detail (uses "simple" card variant) #}
<i:list-container type="product" source="{relevant: product}" name="simple" hide-empty>
<div data-role="list"></div>
</i:list-container>
{# Products from a specific category by ID #}
<i:list-container type="product" source="{kategorie: 123, limit: 8}" hide-empty>
<div data-role="list"></div>
</i:list-container>
{# Products from a dynamic category variable #}
<i:list-container type="product" source="{kategorie: kategorie.id, limit: 10}">
<div data-role="empty"></div>
<div data-role="list"></div>
</i:list-container>
{# Products from the current category (automatic -- no source needed) #}
{# This is the typical pattern used in vypis.tpl (category listing page) #}
<i:list-container type="product">
<div data-role="pagination" data-name="sort"></div>
<div data-role="empty"></div>
<div data-role="list"></div>
<div data-role="pagination" data-name="pages"></div>
</i:list-container>
{# Products from a content grid product_list field (e.g. homepage banners) #}
<i:list-container type="product" collection="{{ item.right.products }}" name="simple" hide-empty
class="swiper-container" data-navigation="simple-slider-{{ loop.index }}">
<div data-role="list" class="swiper-wrapper"></div>
</i:list-container>For how listings integrate with filters (including i:filter-placeholder and i:filter-placeholder-values inside the container), see the filter integration example.