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¶
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 %}