Neste tutorial, adicionaremos 2 espaços para produtos relacionados (alternativos e complementares) exibidos na parte inferior da página de detalhes do produto.
Os tipos de produtos relacionados que serão exibidos são:
- Produtos da mesma categoria do produto principal (este é o comportamento padrão)
- Se os produtos forem vinculados manualmente a partir do formulário de produtos no administrador, serão exibidos:
- Produtos alternativos: devem ser produtos semelhantes ao produto principal
- Produtos complementares: devem ser os produtos que complementam o produto principal

HTML
1. Vamos criar um novo snipplet com o nome product-related.tpl dentro da pasta snipplets/product. O código para isso é o seguinte:
{# Default related products visibility conditions #}
{% set related_products = [] %}
{% set related_products_ids_from_app = product.metafields.related_products.related_products_ids %}
{% set has_related_products_from_app = related_products_ids_from_app | get_products | length > 0 %}
{% if has_related_products_from_app %}
{% set related_products = related_products_ids_from_app | get_products %}
{% endif %}
{% if related_products is empty %}
{% set max_related_products_length = 8 %}
{% set max_related_products_achieved = false %}
{% set related_products_without_stock = [] %}
{% set max_related_products_without_achieved = false %}
{% if related_tag %}
{% set products_from_category = related_products_from_controller %}
{% else %}
{% set products_from_category = category.products | shuffle %}
{% endif %}
{% for product_from_category in products_from_category if not max_related_products_achieved and product_from_category.id != product.id %}
{% if product_from_category.stock is null or product_from_category.stock > 0 %}
{% set related_products = related_products | merge([product_from_category]) %}
{% elseif (related_products_without_stock | length < max_related_products_length) %}
{% set related_products_without_stock = related_products_without_stock | merge([product_from_category]) %}
{% endif %}
{% if (related_products | length == max_related_products_length) %}
{% set max_related_products_achieved = true %}
{% endif %}
{% endfor %}
{% if (related_products | length < max_related_products_length) %}
{% set number_of_related_products_for_refill = max_related_products_length - (related_products | length) %}
{% set related_products_for_refill = related_products_without_stock | take(number_of_related_products_for_refill) %}
{% set related_products = related_products | merge(related_products_for_refill) %}
{% endif %}
{% endif %}
{% set complementary_products = complementary_product_list | length > 0 %}
{# Show alternative products when there are default category alternatives with no complementaries or manually selected alternatives #}
{% set alternative_products = related_products | length > 0 and not (complementary_products and source_alternative == 'default') %}
{# Set related products classes #}
{% set section_class = 'section-products-related my-3' %}
{% set container_class = 'container' %}
{% set title_class = 'h3 text-center' %}
{% set products_container_class = 'position-relative swiper-container-horizontal' %}
{% set slider_container_class = 'swiper-container' %}
{% set swiper_wrapper_class = 'swiper-wrapper' %}
{% set slider_control_pagination_class = 'swiper-pagination' %}
{% set slider_control_class = 'icon-inline icon-w-8 icon-2x svg-icon-text' %}
{% set slider_control_prev_class = 'swiper-button-prev' %}
{% set slider_control_next_class = 'swiper-button-next' %}
{% set control_prev = include ('snipplets/svg/chevron-left.tpl', {svg_custom_class: slider_control_class}) %}
{% set control_next = include ('snipplets/svg/chevron-right.tpl', {svg_custom_class: slider_control_class}) %}
{# Alternative products #}
{% set alternative_data_component = source_alternative == 'default' ? 'related-products' : 'alternative-products' %}
{% if alternative_products %}
{{ component(
'products-section',{
title: settings.products_related_title,
id: 'related-products',
data_component: alternative_data_component,
products_amount: related_products | length,
products_array: related_products,
product_template_path: 'snipplets/grid/item.tpl',
product_template_params: {'slide_item': true},
slider_controls_position: 'bottom',
slider_pagination: true,
svg_sprites: false,
section_classes: {
section: 'js-related-products ' ~ section_class,
container: container_class,
title: title_class,
products_container: products_container_class,
slider_container: 'js-swiper-related ' ~ slider_container_class,
slider_wrapper: swiper_wrapper_class,
slider_control_pagination: 'js-swiper-related-pagination ' ~ slider_control_pagination_class,
slider_control_prev_container: 'js-swiper-related-prev ' ~ slider_control_prev_class,
slider_control_prev: 'icon-flip-horizontal',
slider_control_next_container: 'js-swiper-related-next ' ~ slider_control_next_class,
},
custom_control_prev: control_prev,
custom_control_next: control_next,
})
}}
{% endif %}
{# Complementary products #}
{% set complementary_section_id = 'complementary-products' %}
{% if complementary_products %}
{{ component(
'products-section',{
title: 'Para comprar con este producto' | translate,
id: complementary_section_id,
data_component: complementary_section_id,
products_amount: complementary_product_list | length,
products_array: complementary_product_list,
product_template_path: 'snipplets/grid/item.tpl',
product_template_params: {'slide_item': true},
slider_controls_position: 'bottom',
slider_pagination: true,
svg_sprites: false,
section_classes: {
section: 'js-complementary-products ' ~ section_class,
container: container_class,
title: title_class,
products_container: products_container_class,
slider_container: 'js-swiper-complementary ' ~ slider_container_class,
slider_wrapper: swiper_wrapper_class,
slider_control_pagination: 'js-swiper-complementary-pagination ' ~ slider_control_pagination_class,
slider_control_prev_container: 'js-swiper-complementary-prev ' ~ slider_control_prev_class,
slider_control_prev: 'icon-flip-horizontal',
slider_control_next_container: 'js-swiper-complementary-next ' ~ slider_control_next_class,
},
custom_control_prev: control_prev,
custom_control_next: control_next,
})
}}
{% endif %}
Pode ver que estamos incluindo o snipplet item.tpl da pasta snipplets/grid (com base no layout Base), pode ser que você precise incluir o snipplet single_product.tpl. O importante é usar o mesmo snipplet que usamos para o item nas listagens de produtos, como aqueles nos templates category.tpl ou search.tpl.
Por outro lado, é importante mencionar que estamos utilizando o componente privado seção de produtos. Para mais informações sobre as opções deste componente, recomendamos este artigo.
2. Dentro de item.tpl ou single_product.tpl iremos adicionar na div onde temos as classes para as colunas do Bootstrap o condicional {% if slide_item %}js-item-slide swiper-slide{% endif %}.. Abaixo está um exemplo de como aplicá-lo:
<div class="{% if slide_item %}js-item-slide swiper-slide{% else %}col-12 col-sm-4{% endif %} item item-product">
...
</div> 3. Incluímos o snipplet de produtos relacionados no template product.tpl abaixo de tudo, como abaixo:
{% include 'snipplets/product/product-related.tpl' %}4. Finalmente, para a parte do HTML, precisamos adicionar uma pasta SVG dentro da pasta snipplets. Aqui vamos adicionar os SVGs que usamos para os ícones no carrinho.
chevron-left.tpl
<svg class="{{ svg_custom_class }}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 512"><path d="M231.293 473.899l19.799-19.799c4.686-4.686 4.686-12.284 0-16.971L70.393 256 251.092 74.87c4.686-4.686 4.686-12.284 0-16.971L231.293 38.1c-4.686-4.686-12.284-4.686-16.971 0L4.908 247.515c-4.686 4.686-4.686 12.284 0 16.971L214.322 473.9c4.687 4.686 12.285 4.686 16.971-.001z"/></svg>chevron-right.tpl
<svg class="{{ svg_custom_class }}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 512"><path d="M24.707 38.101L4.908 57.899c-4.686 4.686-4.686 12.284 0 16.971L185.607 256 4.908 437.13c-4.686 4.686-4.686 12.284 0 16.971L24.707 473.9c4.686 4.686 12.284 4.686 16.971 0l209.414-209.414c4.686-4.686 4.686-12.284 0-16.971L41.678 38.101c-4.687-4.687-12.285-4.687-16.971 0z"/></svg>CSS
Requisito:
Ter adicionado helper classes em seu layout. Você pode seguir este pequeno tutorial para fazer isso (é só copiar e colar algumas classes, não leva mais que 1 minuto).
Como neste exemplo usamos um slider com o Swiper, precisamos adicionar o plugin. Para ver como fazer isso, você pode ler este pequeno artigo e continuar com este tutorial.
Se você preferir mostrar os produtos em um grid clássico sem slider ou se já tiver incluído o Swiper, pode pular esta etapa.
JS
⚠️ A partir do dia 30 de janeiro de 2023, a biblioteca jQuery será removida do código de nossas lojas, portanto, a função "$" não poderá ser utilizada.
Precisamos aplicar as funções no arquivo store.js.tpl (ou onde suas funções JS estiverem) com o seguinte código:
{% if template == 'product' %}
{# /* // Product Related */ #}
// Set loop for related products products sliders
{% set columns = settings.grid_columns %}
const desktopColumns = {% if columns == 1 %}3{% else %}4{% endif %};
function calculateRelatedLoopVal(sectionSelector) {
let productsAmount = jQueryNuvem(sectionSelector).attr("data-related-amount");
let loopVal = false;
const applyLoop = (window.innerWidth < 768 && productsAmount > {{ columns }}) || (window.innerWidth > 768 && productsAmount > desktopColumns);
if (applyLoop) {
loopVal = true;
}
return loopVal;
}
let alternativeLoopVal = calculateRelatedLoopVal(".js-related-products");
let complementaryLoopVal = calculateRelatedLoopVal(".js-complementary-products");
{# Alternative products #}
createSwiper('.js-swiper-related', {
lazy: true,
watchOverflow: true,
loop: alternativeLoopVal,
centerInsufficientSlides: true,
spaceBetween: 30,
slidesPerView: {{ columns }},
pagination: {
el: '.js-swiper-related-pagination',
clickable: true,
},
navigation: {
nextEl: '.js-swiper-related-next',
prevEl: '.js-swiper-related-prev',
},
breakpoints: {
767: {
slidesPerView: desktopColumns,
}
}
});
{# Complementary products #}
createSwiper('.js-swiper-complementary', {
lazy: true,
watchOverflow: true,
loop: complementaryLoopVal,
centerInsufficientSlides: true,
spaceBetween: 30,
slidesPerView: {{ columns }},
pagination: {
el: '.js-swiper-complementary-pagination',
clickable: true,
},
navigation: {
nextEl: '.js-swiper-complementary-next',
prevEl: '.js-swiper-complementary-prev',
},
breakpoints: {
767: {
slidesPerView: desktopColumns,
}
}
});
{% endif %}Configurações
No arquivo config/settings.txt vamos adicionar a opção de alterar o título da seção dps produtos alternativos (você pode fazer isso também para as complementares se precisar) dentro da seção “Detalhes do produto”.
title
title = Productos relacionados
i18n_input
description = Título para los productos alternativos
name = products_related_titleTraduções
Nesta etapa adicionamos os textos para as traduções no arquivo config/translations.txt
es "Productos relacionados" pt "Produtos relacionados" en "Related products" es_mx "Productos relacionados" es "Título para los productos alternativos" pt "Título para os produtos alternativos" es_mx "Título para los productos alternativos"
Dentro de config/defaults.txt vamos adicionar os textos das mensagens padrão.
products_related_title_es = Productos similares products_related_title_en = Similar products products_related_title_pt = Produtos similares
Ativação
Pronto, uma vez relacionados os produtos a partir do formulário de produtos no administrador, serão visualizados os relacionados criados manualmente, caso contrário serão mostrados os padrão mencionados no início deste artigo. Excelente!