Skip to content

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.

twig
{# 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>
twig
<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>
twig
{# 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>
twig
{# 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>
twig
{# 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

AttributeRequiredDescription
typeyeslisting type
sourcenoTwig object with listing parameters (cannot be combined with collection)
collectionnolist of items to render (cannot be combined with source and does not support pagination)
namenoitem template name (as defined by list-item)
analytics-namenolabel for analytics tracking (e.g. "product.detail.bottom")
wrapnowrap groups of boxes
appendnoappend a tag after a group of boxes
box-classnoadd a CSS class
hide-emptynohide the entire container when it has no items (no value needed)
scroll-offsetnoCSS selector for accounting for a fixed header
insert-beforenoname of a listing-insert template to insert before each item
insert-afternoname of a listing-insert template to insert after each item
insert-argumentsnoarguments 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.

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

twig
<i:list-container type="product" source="{kategorie:123,limit:10}">
    <div data-role="empty"></div>
    <div data-role="list"></div>
</i:list-container>
twig
<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.

twig
<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>
html
<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.

twig
<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>
html
<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.

twig
{# _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>
twig
{# _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

AttributeDefaultDescription
type---item type (product, article, ...)
namedefaultbox name for linking with list-container

Attributes of pagination.strany (pages)

AttributeValuesDescription
spacestrue/falseinsert non-breaking spaces between pages
size1/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
twig
{# _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.

twig
<i:list-empty type="product" name="short">
    {% trans 'listing.no-products-found' %}
</i:list-empty>
twig
<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

AttributeDefaultDescription
type---item type (product, article, ...)
namedefaultname for linking with list-container

Available variables

NameValue
url.resetURL 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.

twig
<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

AttributeDescription
typeitem type (product, article, ...)
namename for linking with list-container

Available variables

NameValue
pagePositionPosition on the page (counted from 1)
pageListing page number (counted from 1)
limitNumber of records per page (maximum)
recordsOnPageNumber of records on the current page (actual)
argumentsinput 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.

twig
{# _listing.tpl #}
<i:list-item>
    <h3>{{product.name}}</h3>
</i:list-item>
twig
<i:list-item tag="li" name="news">
    <h3>{{product.name}}</h3>
</i:list-item>

Attributes of list-item

AttributeDefaultDescription
type---item type (product, article, ...)
tagdivwhich tag to wrap the resulting box in
namedefaultbox 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:

twig
{# _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:

twig
{# _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:

twig
{# _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:

twig
{# 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).

twig
{# 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.