Skip to content

product_tile

The product_tile macro is used to render a single tile of a product. Such tile can be highly configured in terms of informations that should be rendered which will be shown in the examples.

Definition

{% product_tile(product, options, overrides) %}

Input parameters

product

Product parameter represents a Product object from ObjectApi.

options

object parameter represents an object of product tile options

Option key Type Default Required Description
options.instanceId string "" yes Unique identifier commonly provided by a special module variable moduleInstance
options.imageSize string "" yes A number representing a size of the image to render in pixels
options.hasFavouriteIcon boolean false no If set to true a button allowing to add the product to favourites will be rendered
options.hasSkuCode boolean false no If set to true the SKU code of the product will be rendered
options.hasProducerCode boolean false no If set to true the producer code of the product will be rendered
options.shouldShortenLongProductName boolean false no If set to true a long product name will be shortened to two rows
options.hasProducerName boolean false no If set to true the name of the producer will be rendered
options.hasUnitPrice boolean false no If set to true the unit price of the product will be rendered
options.hasRegularPriceWhilePromotionActive boolean false no If set to true the regular price will be rendered alongside a discounted one for products undergoing a promotion
options.hasPercentageDiscountValue boolean false no If set to true the percentage value of a discount will be rendered for products undergoing a promotion
options.hasPriceWithoutDeliveryInfo boolean false no If set to true the information about product price not accounting delivery costs will be rendered
options.hasAddToBasketButton boolean false no If set to true the button allowing to add the product to the basket will be rendered
options.hasAvailabilityStatus boolean false no If set to true the availability status of the product will be rendered
options.isInactive boolean false no If the product is inactive and additional styling or logic should be applied set this property to true
options.className string "" no A list of classes that will be added to the product tile

overrides

object parameter represents an object of configuration parameters that override product and options configuration options if needed

Option key Type Default Required Description
options.url string "" no A url path to the given product

Example

In this example we use a product tile macro to render a list of products based on a given product list. To get a list of products we use a getProducts() method from ObjectApi and to get a default size of the image we use the System Configuration object from SVE.

{% set productList = ObjectApi.getProducts() %}

{% for product in productList %}
    {{ product_tile(product, {
        instanceId: moduleInstance,
        imageSize: systemConfig.lSize
    }) }}
{% endfor %}

Example

In this example we use a similar product list but this time all product tiles have additional buttons allowing to add products to favourites and to the basket. They also have a producer name along with it's code.

{% set productList = ObjectApi.getProducts() %}

{% for product in productList %}
    {{ product_tile(product, {
        instanceId: moduleInstance,
        imageSize: systemConfig.lSize,
        hasFavouriteIcon: true,
        hasAddToBasketButton: true,
        hasProducerName: true,
        hasProducerCode: true
    }) }}
{% endfor %}

Example

In this example the product tiles have a button allowing to add them to the basket and in case of a promotion a percent discount value is rendered as well as a regular price

{% set productList = ObjectApi.getProducts() %}

{% for product in productList %}
    {{ product_tile(product, {
        instanceId: moduleInstance,
        imageSize: systemConfig.lSize,
        hasAddToBasketButton: true,
        hasPercentageDiscountValue: true,
        hasRegularPriceWhilePromotionActive: true
    }) }}
{% endfor %}

Example

In this example the product tiles have a custom className making them visible on all devices but mobile

{% set productList = ObjectApi.getProducts() %}

{% for product in productList %}
    {{ product_tile(product, {
        instanceId: moduleInstance,
        imageSize: systemConfig.lSize,
        className: 'hidden-xs-only'
    }) }}
{% endfor %}

Macro source code

{% macro product_tile(product, options, overrides) %}
    {% from "@macros/image.twig" import image %}
    {% from "@macros/ribbon.twig" import ribbon %}
    {% from "@macros/ribbon_group.twig" import ribbon_group %}
    {% from "@macros/add_to_favourites_button.twig" import add_to_favourites_button %}
    {% from "@macros/price_base.twig" import price_base %}
    {% from "@macros/price_product_tile_regular.twig" import price_product_tile_regular %}
    {% from "@macros/price_product_tile_regular_small.twig" import price_product_tile_regular_small %}
    {% from "@macros/price_product_tile_special.twig" import price_product_tile_special %}
    {% from "@macros/price_product_tile_inactive.twig" import price_product_tile_inactive %}
    {% from "@macros/price_unit.twig" import price_unit %}
    {% from "@macros/icon.twig" import icon %}

    {% set settings = ObjectApi.getProductPricesSettings() %}
    {% set notificationSettings = ObjectApi.getNotificationSettings() %}
    {% set productUrl = overrides.url ?? product.url %}
    {% set basketSettings = ObjectApi.getBasketSettings() %}
    {% set shopUrls = ObjectApi.getShopUrls() %}

    {% set availability = product.availability %}

    {% set inactiveClass = options.isInactive ? 'product-tile_image_inactive inactive' : '' %}

    {% if product.hasSpecialOffer %}
        {% set promoProductRibbon =  {
            text: translate('Deal'),
            options: {
                variant: 'primary',
                classNames: 'ribbon_promo'
            }
        } %}
    {% endif %}

    {% if product.isNew %}
        {% set newProductRibbon =  {
            text: translate('New Product'),
            options: {
                classNames: 'ribbon_new'
            }
        } %}
    {% endif %}

    {% if product.isBundle and not product.bundle.hasItemWithNotStockOption %}
        {% set bundleItems = [] %}
        {% for item in product.bundle.items %}
            {% set bundleItems = bundleItems|merge([{ variantId: item.variant.id, options: null }]) %}
        {% endfor %}
    {% endif %}

    {% set showVatInfo = settings.showAdditionalTaxInfo and options.hasTaxInfo %}

    <product-tile product-id="{{ product.id }}" name="{{ product.name }}"
                  price="{{ product.price.grossValue }}" producer="{{ product.producer.name }}"
                  category="{{ product.category.name }}"
                  currency="{{ product.price.currency }}" list="{{ options.instanceId }}"
                  id="{{ product.id }}-{{ options.instanceId }}"
                  class="product-tile {% if options.className %}{{ options.className }}{% endif %}">
        <div class="product-tile__row product-tile__header">
            <div class="product-tile__row product-tile_row_image">
                <div class="product-tile__image">
                    {% if promoProductRibbon or newProductRibbon %}
                        {{ ribbon_group([promoProductRibbon, newProductRibbon], { position: "absolute" }) }}
                    {% endif %}

                    <product-link id="{{ product.id }}" name="{{ product.name }}"
                                  price="{{ product.price.grossValue }}" producer="{{ product.producer.name }}"
                                  category="{{ product.category.name }}"
                                  currency="{{ product.price.currency }}" list="{{ options.instanceId }}">
                        <a href="{{ productUrl }}" title="{{ product.name }}">
                            {{ image({
                                img: {
                                    src: product.promotingImage.thumbnailUrl(options.imageSize, options.imageSize),
                                    alt: product.promotingImage.name,
                                    title: product.promotingImage.name,
                                    width: options.imageSize,
                                    height: options.imageSize,
                                    loading: 'lazy',
                                    class: inactiveClass
                                }
                            }, [
                                {
                                    src: product.promotingImage.webpThumbnailUrl(options.imageSize, options.imageSize),
                                    type:  'image/webp'
                                }
                            ]) }}
                        </a>
                    </product-link>

                    {% if options.hasFavouriteIcon %}
                        <div class="product-tile__favourites">
                            {{ add_to_favourites_button(product, { instanceId: options.instanceId }) }}
                        </div>
                    {% endif %}
                </div>
            </div>

            {% if product.sku or product.productCode %}
                <span class="product-tile__codes">
                    {% if product.sku and options.hasSkuCode %}
                        <span class="product-tile__code product-tile__code_sku">{{ product.sku }}</span>{% endif %}
                    {% if product.producerCode and options.hasProducerCode %}
                        <span class="product-tile__code product-tile__code_producer">{{ product.producerCode }}</span>
                    {% endif %}
                </span>
            {% endif %}

            {% if product.name %}
                <product-link id="{{ product.id }}" name="{{ product.name }}" price="{{ product.price.grossValue }}"
                              producer="{{ product.producer.name }}"
                              category="{{ product.category.name }}" currency="{{ product.price.currency }}"
                              list="{{ options.instanceId }}">
                    <a href="{{ productUrl }}" title="{{ product.name }}" class="link_no-underline">
                        <h2 class="product-tile__name {% if options.shouldShortenLongProductName %}product-tile__name_fixed-height{% endif %}">
                            {{ product.name }}
                        </h2>
                    </a>
                </product-link>
            {% endif %}

            {% if product.producer.name and options.hasProducerName %}
                <span class="product-tile__producer">{{ product.producer.name }}</span>
            {% endif %}
        </div>

        <div class="product-tile__row product-tile__content">
            {% set prices %}
                {% if settings.showGrossPrice %}
                    <div slot="logged-in" class="product-tile__row product-tile_row_prices-gross">
                        <div class="product-tile__price">
                            {% if product.hasSpecialOffer %}
                                {{ price_product_tile_special(product.price.formatGross) }}
                            {% else %}
                                {{ price_product_tile_regular(product.price.formatGross) }}
                            {% endif %}

                            {% if showVatInfo and product.tax.value %}
                                <span class="vat-info">
                                    {{ translate('with %s VAT', "<span class='js__price-vat-value'>#{product.tax.value}%</span>") }}
                                </span>
                            {% endif %}
                        </div>

                        {% if product.hasUnitPriceCalculation and settings.showUnitPrice and options.hasUnitPrice %}
                            <div class="product-tile__unit-price">
                                {{ price_unit((product.unitPrice.formatGross ~ ' / ' ~ product.unitPriceCalculationUnit.name)) }}
                            </div>
                        {% endif %}

                        {% if product.hasSpecialOffer %}
                            {% if options.hasRegularPriceWhilePromotionActive %}
                                <div class="product-tile__regular-price">
                                    {{ price_product_tile_inactive(product.basePrice.formatGross, translate('Regular price') ~ ':') }}

                                    {% if options.hasPercentageDiscountValue %}
                                        {{ price_base('-' ~ product.specialOffer.formatDiscount, '', {
                                            price: 'price_xs',
                                            priceValue: 'price__value_special'
                                        }) }}
                                    {% endif %}
                                </div>
                            {% endif %}

                            {% if settings.showLowestPriceIn30Days %}
                                <div class="product-tile__lowest-price">
                                    {{ price_product_tile_inactive(product.lowestHistoricalPriceInLast30Days.getPrice().formatGross, translate('Lowest price') ~ ':') }}

                                    {% if options.hasPercentageDiscountValue %}
                                        {% set lowestHistoricalPricePercentage = (100 - product.price.grossValue * 100 / product.lowestHistoricalPriceInLast30Days.getPrice().grossValue)|round() %}

                                        {{ price_base('-' ~ lowestHistoricalPricePercentage ~ '%', '', {
                                            price: 'price_xs',
                                            priceValue: 'price__value_special'
                                        }) }}
                                    {% endif %}
                                </div>
                            {% endif %}
                        {% endif %}
                    </div>
                {% endif %}

                {% if settings.showNetPrice %}
                    <div slot="logged-in" class="product-tile__row product-tile_row_prices-net">
                        <div class="product-tile__price">
                            {% if product.hasSpecialOffer and not settings.showGrossPrice %}
                                {{ price_product_tile_special(product.variant.price.formatNet) }}
                            {% else %}
                                {{ price_product_tile_regular_small(product.price.formatNet) }}
                            {% endif %}

                            {% if showVatInfo and product.tax.value %}
                                <span class="vat-info{% if settings.showGrossPrice %} vat-info_xs{% endif %}">
                                    {{ translate('without VAT') }}
                                </span>
                            {% endif %}
                        </div>

                        {% if product.hasUnitPriceCalculation and settings.showUnitPrice and options.hasUnitPrice %}
                            <div class="product-tile__unit-price">
                                {{ price_unit((product.unitPrice.formatNet ~ ' / ' ~ product.unitPriceCalculationUnit.name)) }}
                            </div>
                        {% endif %}

                        {% if product.hasSpecialOffer and not settings.showGrossPrice %}
                            {% if options.hasRegularPriceWhilePromotionActive %}
                                <div class="product-tile__regular-price">
                                    {{ price_product_tile_inactive(product.basePrice.formatNet, translate('Regular price') ~ ':') }}

                                    {{ price_base('-' ~ product.specialOffer.formatDiscount, '', {
                                        price: 'price_xs',
                                        priceValue: 'price__value_special'
                                    }) }}
                                </div>
                            {% endif %}

                            {% if settings.showLowestPriceIn30Days %}
                                <div class="product-tile__lowest-price">
                                    {{ price_product_tile_inactive(product.lowestHistoricalPriceInLast30Days.getPrice().formatNet, translate('Lowest price') ~ ':') }}

                                    {% set lowestHistoricalPricePercentage = (100 - product.price.netValue * 100 / product.lowestHistoricalPriceInLast30Days.getPrice().netValue)|round() %}

                                    {{ price_base('-' ~ lowestHistoricalPricePercentage ~ '%', '', {
                                        price: 'price_xs',
                                        priceValue: 'price__value_special'
                                    }) }}
                                </div>
                            {% endif %}
                        {% endif %}
                    </div>
                {% endif %}
            {% endset %}

            {% if settings.showPricesToUnregistered  %}
                {{ prices }}
            {% else %}
                <auth-controller hidden>
                    {{ prices }}
                </auth-controller>
            {% endif %}

            {% if options.hasPriceWithoutDeliveryInfo %}
                <div class="product-tile__row product-tile_row_info">
                    <span class="product-tile__info">{{ translate('Shipping costs are not included in the price.') }}</span>
                </div>
            {% endif %}
        </div>

        {% set shouldShowBuyActionButtonToUnregistered = basketSettings.isBuyingEnabled and settings.showPricesToUnregistered %}

        {% if options.hasAddToBasketButton or options.hasAvailabilityStatus %}
            <div class="product-tile__footer">
                {% if options.hasAddToBasketButton %}
                    {% if basketSettings.isBuyingEnabled %}
                        {% if product.variant.isAvailable or not product.hasOnlyDefaultVariant %}
                            {% set buyActionButton %}
                                {% if product.isBundle and product.bundle.hasItemWithNotStockOption %}
                                    <a class="btn btn_s btn_special" href="{{ product.url }}">
                                        {{ translate('Set up bundle') }}
                                    </a>
                                {% else %}
                                    {% set productIsNotAvailableWithNotificationsEnabled = notificationSettings.getIsProductNotificationEnabled() and product.availability.isNotificationEnabled and not product.isAvailable %}
                                    {% set productHasOnlyDefaultVariantAndIsNotAvailable = product.hasOnlyDefaultVariant and not product.isAvailable  %}

                                    {% if product.packageQuantity %}
                                        {% set quantity = product.packageQuantity %}
                                    {% else %}
                                        {% set quantity = 1 %}
                                    {% endif %}

                                    <buy-button
                                            quantity="{{ quantity }}"
                                            product-id="{{ product.id }}"
                                            variant-id="{{ product.variant.id }}"
                                            is-buyable="{{ product.availability.isAvailable }}"
                                            product-path="{{ productUrl }}"
                                            {% if basketSettings.redirectToBasketAfterAdding %}
                                                basket-path="{{ shopUrls.basketUrl }}"
                                            {% elseif not basketSettings.showConfirmationMessage %}
                                                has-flash-message
                                            {% endif %}
                                            {% if options.basketTrackingParams is defined %}
                                                basket-tracking-params="{{ options.basketTrackingParams }}"
                                            {% endif %}
                                            {% if product.options.count > 0 %}
                                                has-product-variants
                                            {% endif %}

                                            {% if productIsNotAvailableWithNotificationsEnabled or productHasOnlyDefaultVariantAndIsNotAvailable %}
                                                hidden
                                            {% endif %}

                                            on-interaction
                                            {% if product.isBundle and not product.bundle.hasItemWithNotStockOption %}
                                                bundle-items="{{ bundleItems|json_encode }}"
                                            {% endif %}
                                    >
                                        <div class="product-tile__footer-btn">
                                            <button class="btn btn_s btn_primary">{{ translate('To cart') }}</button>
                                        </div>
                                    </buy-button>
                                {% endif %}
                            {% endset %}

                            <auth-controller>
                                <div slot="logged-in" hidden>
                                    {{ buyActionButton }}
                                </div>
                                <div slot="logged-out">
                                    {% if shouldShowBuyActionButtonToUnregistered %}
                                        {{ buyActionButton }}
                                    {% else %}
                                        <span class="btn btn_primary-disabled">
                                            {{ translate('Add to cart') }}
                                        </span>
                                    {% endif %}
                                </div>
                            </auth-controller>
                        {% elseif not notificationSettings.getIsProductNotificationEnabled() or not product.availability.isNotificationEnabled %}
                            <span class="btn btn_s btn_special btn_special-disabled">
                                {{ translate('Unavailable') }}
                            </span>
                        {% endif %}

                        {% if notificationSettings.getIsProductNotificationEnabled() and product.availability.isNotificationEnabled %}
                            <availability-notifier-btn
                                    product-name="{{ product.name }}"
                                    product-image="{{ product.promotingImage.webpThumbnailUrl(120, 120) }}"
                                    product-id="{{ product.id }}"
                                    product-variant-id="{{ product.variant.id }}"
                                    {% if not product.availability.isNotificationEnabled or product.isAvailable %}
                            hidden
                                    {% endif %}>
                                <button class="btn btn_secondary btn_s" slot="subscribe">
                                    {{ translate('Notify me when available') }}
                                </button>
                                <button class="btn btn_outline btn_s" slot="unsubscribe" hidden>
                                    {{ translate('Opt out of notifications') }}
                                </button>
                            </availability-notifier-btn>
                        {% endif %}
                    {% else %}
                        <span class="btn btn_s btn_special btn_special-disabled">
                            {{ translate('Cart disabled') }}
                        </span>
                    {% endif %}
                {% endif %}

                {% if options.hasAvailabilityStatus %}
                    <span class="product-tile__availability">
                        {{ translate("Availability") }}:
                        <strong class="{{ html_classes('product-tile__availability-value', {
                            'product-tile__availability-value_inactive': not product.availability.isAvailable
                        }) }}">{{ product.availability.name }}</strong>
                    </span>
                {% endif %}
            </div>
        {% endif %}
    </product-tile>
{% endmacro %}

Object Api objects reference

Object Api methods reference