load_customizations
Tag {% load_customizations %}
Seção intitulada “Tag {% load_customizations %}”Sintaxe:
{% load_customizations product_id: product.id %}Variáveis de output:{{ loaded_customization }}e{{ loaded_customization_types }}Contexto: página de produto (quando a funcionalidade de produtos personalizados está habilitada)
Sua loja pode oferecer produtos com personalização para seus clientes. Produtos personalizados são produtos que permitem adicionar uma customização, como uma gravação ou item adicional no produto. Para auxiliar o controle desses itens temos a tag load_customizations. Essa tag lista as personalizações disponíveis de um produto.
❗️ Pré-requisitos
Fique atento aos requisitos para utilizar a Produtos Personalizados. O uso da tag
load_customizationsestá diretamente relacionado a ativação da funcionalidade de personalização.
- Ative a funcionalidade no Admin:
- Acesse Configurações > Gerais > Extras.
- Selecione a caixa de seleção ☑ Habilitar cadastro e configuração de produtos personalizados;
- Clique em Salvar.
- Garanta que o template ou layout utilizado na loja suporte a funcionalidade de personalização.
Saiba mais sobre produtos personalizados em Funcionalidades: Produtos Personalizados.
Utilização e Estrutura
Seção intitulada “Utilização e Estrutura”Para utilizar a tag utilize o parâmetro de identificação do produto product_id:
{% load_customizations product_id: product.id %}A tag irá retornar dois arrays
loaded_customization: com todas as personalizações disponíveis;loaded_customization_types: com as personalizações disponíveis agrupadas por tipo de personalização.
📘
Na estrutura base dos templates, as personalizações são tratadas com o agrupamento pelo tipo. Isso permite fazer seletores no front de forma mais organizada. Se precisar alterar, não esqueça de verificar o restante da montagem no arquivo _customizations.liquid.
Elas também recebem a mesma estrutura de HTML que os atributos: mesmas classes, hierarquia e ordem dos elementos. Isso permite reutilizar a estilização, mantendo o mesmo visual já aplicado nos atributos.
Exemplo de uso
Seção intitulada “Exemplo de uso”Quando montamos personalizações do produto, normalmente precisamos prever 2 tipos, com base no cadastro delas no admin. Além disso, cada uma dessas personalizações requer uma estrutura HTML diferentes:
- Personalizações do tipo Texto: são personalizações onde os usuários do site digitam em um campo o texto que desejam. No código, devem ser tratadas com inputs do tipo text.
- Personalizações do tipo Opções: são um conjunto de opções, das quais o usuário do site escolhe uma opção para cada grupo. No código, devem ser tratadas com inputs do tipo radio.
Todos os inputs de personalização recebem alguns atributos em específico. Alguns fazem parte do contrato de compra e outros só são necessários quando a interface também exibe o valor adicional da personalização.
Obrigatórios no contrato de compra
Seção intitulada “Obrigatórios no contrato de compra”name: precisa ser no formatocustomizations[][{{ customization.name }}]data-customization: define que é um input referente a uma personalização. Recebe tratamentos específicos das personalizações na adição do produtodata-customization-name="{{ customization.name }}": utilizado no processo de adição do produto no carrinho, auxilia na definição da personalização correta que está sendo adicionada com o produto
Obrigatórios na interface, quando houver preço adicional
Seção intitulada “Obrigatórios na interface, quando houver preço adicional”-
data-customization-price="{{ option.price }}": para exibir o preço da personalização, caso tenha. O preço é exibido no elemento que possui o atributodata-value, dentro do bloco da própria customização. Nos templates, este elemento é um<span>, junto do título:<p class="topic-title"><span>{{ customization.name }}</span><span class="value" data-value></span></p>
Exemplo de um input de personalização do tipo Texto:
Seção intitulada “Exemplo de um input de personalização do tipo Texto:”<input type="text" placeholder="{{ customization.name }}" name="customizations[][{{ customization.name }}]" id="{{ customization.name }}-customization-{{ forloop.index }}" data-customization data-customization-price="{{ text_custom.price }}" data-customization-name="{{ customization.name }}"/>Exemplo de múltiplos inputs de personalização do tipo Opção:
Seção intitulada “Exemplo de múltiplos inputs de personalização do tipo Opção:”{% assign options_list = customization.customizations | sort: 'name' %}
{% for option in options_list %} <input type="radio" name="customizations[][{{ customization.name }}]" value="{{ option.name }}" id="{{ customization.name }}-customization-{{ forloop.index }}" class="input" data-customization data-customization-price="{{ option.price }}" data-customization-name="{{ customization.name }}" />{% endfor %}O que uma implementação precisa resolver
Seção intitulada “O que uma implementação precisa resolver”Mesmo que a sua loja não utilize a estrutura padrão como ponto de partida, a implementação de personalizações precisa resolver estes pontos:
- Carregar as personalizações do produto com
{% load_customizations product_id: product.id %}; - Separar as personalizações por grupo ou tipo, para que cada conjunto seja exibido de forma independente;
- Renderizar apenas opções válidas para compra;
- Expor no HTML os atributos mínimos esperados pelo fluxo de adição ao carrinho;
- Prever a exibição de valor adicional, quando a personalização tiver preço;
- Tratar corretamente personalizações do tipo texto e do tipo opção;
- Garantir que o formulário final consiga enviar as personalizações junto com SKU e quantidade.
Na prática, a implementação precisa responder a estas perguntas:
- Qual é o nome da personalização que está sendo preenchida;
- Qual valor foi selecionado ou digitado;
- Onde esse valor adicional será exibido na interface;
- Como esse valor será enviado no formulário ou no payload de compra.
Obrigatório vs opcional
Seção intitulada “Obrigatório vs opcional”Antes de seguir para os exemplos, vale separar o que faz parte do contrato mínimo de implementação e o que pertence apenas ao comportamento da implementação de referência.
Obrigatório
Seção intitulada “Obrigatório”- Chamar
{% load_customizations product_id: product.id %}na página de produto; - Renderizar os campos de personalização dentro do mesmo
<form>usado para a compra; - Manter
name="customizations[][{{ customization.name }}]"nos campos; - Expor
data-customizationnos campos que devem ser lidos pela compra; - Expor
data-customization-namecom o nome correto da personalização; - Garantir que campos de opção tenham
valuee que campos de texto permitam leitura do conteúdo digitado; - Garantir que a implementação consiga enviar essas informações junto do SKU e da quantidade.
Opcional na implementação de referência
Seção intitulada “Opcional na implementação de referência”- Exibir grupos de personalização em formato de dropdown;
- Usar
data-valuepara mostrar o valor adicional na interface; - Reaproveitar
partials/components/check_colorquando a opção representar cor; - Trocar o quickview para CTA de “Personalize”;
- Remover a faixa de compra flutuante em produtos com personalização.
Exemplo completo de implementação
Seção intitulada “Exemplo completo de implementação”O exemplo a seguir mostra uma implementação completa usada como referência. Ele não é obrigatório como estrutura exata, mas cobre todos os pontos mínimos que a página precisa resolver.
{% comment %} Define se o seletor deve vir em dropdown. Mudar para true caso queira dropdown{% endcomment %}{% assign dropdown = true %}
{% comment %} ========== {% endcomment %}
{% load_customizations product_id: product.id %}
{% for customization in loaded_customization_types %}
{% comment %} Verifica se há estoque nas personalizações antes de exibir em tela {% endcomment %} {% assign show_custom = false %} {% for opt in customization.customizations %} {% if opt.quantity > 0 %} {% assign show_custom = true %} {% break %} {% endif %} {% endfor %}
{% comment %} Previne estrutura de dropdown em personalizações de texto {% endcomment %} {% unless customization.options %} {% assign dropdown = false %} {% endunless %}
{% assign class_dropdown = '' %} {% if dropdown %} {% assign class_dropdown = '-dropdown' %} {% endif %}
{% comment %} HTML {% endcomment %} {% if show_custom %} <div class="prod-option -customization {{ class_dropdown }}" data-custom-option> <p class="topic-title"> <span>{{ customization.name }}</span> <span class="value" data-value></span> </p> <div class="inner"> {% if customization.options %} {% comment %} Personalização do tipo Opções {% endcomment %}
{% if dropdown %} <div class="list"> {% endif %}
{% render 'partials/content/product/customizations/options', customization: customization %}
{% if dropdown %} </div> {% endif %}
{% else %} {% comment %} Personalização do tipo Texto {% endcomment %} {% render 'partials/content/product/customizations/text', customization: customization, text_area: false %}
{% endif %} </div> </div> {% endif %}{% endfor %}Nesse exemplo, os papéis de cada parte são estes:
loaded_customization_typesorganiza as personalizações por grupo;show_customimpede a renderização de grupos sem opções disponíveis;data-custom-optiondelimita visual e funcionalmente um grupo de personalização;data-valuemarca o ponto em que o valor adicional pode ser exibido;customization.optionssepara o fluxo de opções do fluxo textual;dropdowncontrola apenas a forma de exibição, não o contrato de envio.
Na implementação de referência, esse bloco não existe isoladamente. Ele entra dentro da área de compra do produto, ao lado do render de variantes:
<div class="attributes" data-product-attributes> {% render 'partials/content/product/variants/variants', subscription: subscription, product: product, random: random, dropdown: dropdown, tag_medidas: tag_medidas, hide_model_attributes: hide_model_attributes %} {% if customs_tag %} {% render 'partials/content/product/customizations/customizations', product: product %} {% endif %}</div>Nesse ponto, cada render representa uma responsabilidade diferente dentro do mesmo formulário:
partials/content/product/variants/variantsmonta a seleção de variantes e precisa levar o produto até um SKU válido;partials/content/product/customizations/customizationsmonta os campos extras de personalização que acompanham esse SKU na compra;- O container
data-product-attributesreúne esses dois blocos para que o formulário final tenha, ao mesmo tempo, seleção comercial do item e dados complementares da personalização.
Se o objetivo for identificar qual bloco do formulário resolve a personalização, este é o ponto central:
{% if customs_tag %} {% render 'partials/content/product/customizations/customizations', product: product %}{% endif %}Esse render é o responsável por colocar no formulário os campos que depois serão lidos pela lógica de compra. Sem ele, o produto ainda pode resolver variante e SKU, mas não terá os inputs extras necessários para enviar personalizações junto da compra.
Tabela de contrato dos atributos
Seção intitulada “Tabela de contrato dos atributos”| Campo | Origem no HTML | Obrigatório | Usado por | Finalidade |
|---|---|---|---|---|
name="customizations[][{{ customization.name }}]" | input ou textarea | Sim | Envio padrão com FormData | Agrupa a personalização no formulário de compra |
data-customization | input ou textarea | Sim | addProduct(), addItem(), addItemsCustom() | Marca quais campos pertencem ao fluxo de personalização |
data-customization-name | input ou textarea | Sim | addItemsCustom() | Define a chave usada no objeto customs |
value | input de opção | Sim, para opções | addItemsCustom() | Define o valor escolhido em personalizações do tipo opção |
| Conteúdo digitado | input de texto ou textarea | Sim, para texto | addItemsCustom() | Define o valor informado em personalizações textuais |
data-customization-price | input ou textarea | Não para envio, Sim se houver preço visual | customizations.js | Permite exibir o valor adicional na interface |
data-value | elemento visual do grupo | Não para envio, Sim se houver preço visual | customizations.js | Recebe o texto do valor adicional exibido ao cliente |
[data-customization] + label | relação estrutural entre input e label | Obrigatório na referência com dropdown | purchase/form.js | Permite o comportamento de abre/fecha do dropdown |
Caso 1: personalização do tipo Opção
Seção intitulada “Caso 1: personalização do tipo Opção”Use esse caso quando o cliente precisa escolher uma opção entre várias possibilidades, como cor de gravação, acabamento ou conteúdo adicional.
Na implementação de referência, esse tipo é renderizado com input type="radio":
{% assign options_list = customization.customizations | sort: 'name' %}
{% for option in options_list %}
{% assign opt_available = '-available' %} {% assign attr_disabled = '' %} {% if option.quantity <= 0 %} {% assign opt_available = '-unavailable' %} {% endif %}
<input type="radio" name="customizations[][{{ customization.name }}]" value="{{ option.name }}" id="{{ customization.name }}-customization-{{ forloop.index }}" class="input" data-customization data-customization-price="{{ option.price }}" data-customization-name="{{ customization.name }}" />
<label for="{{ customization.name }}-customization-{{ forloop.index }}" class="label {{ opt_available }}"> {% if option.name contains '|' %} {% render 'partials/components/check_color', color: option.name, product: product %} {% else %} <span class="text">{{ option.name }}</span> {% endif %} </label>{% endfor %}O que esse caso precisa garantir:
- Todas as opções pertencem ao mesmo grupo por meio do mesmo
name; - O valor enviado no formulário vem de
value="{{ option.name }}"; - O preço adicional, quando existir, fica disponível em
data-customization-price; - A interface consegue diferenciar opções indisponíveis por
option.quantity; - Na implementação de referência, o
labelpermanece logo após o input, porque o comportamento de dropdown procura a estrutura[data-customization] + label.
Caso 2: personalização do tipo Texto
Seção intitulada “Caso 2: personalização do tipo Texto”Use esse caso quando o cliente precisa digitar um conteúdo livre, como nome, frase ou dedicatória.
Na implementação de referência, esse tipo percorre customization.customizations e ignora entradas sem estoque:
{% unless text_area %} {% assign text_area = false %}{% endunless %}
{% for text_custom in customization.customizations %}
{% if text_custom.quantity <= 0 %} {% continue %} {% endif %}
{% if text_area %} <textarea placeholder="{{ customization.name }}" name="customizations[][{{ customization.name }}]" type="textarea" id="{{ customization.name }}-customization-{{ forloop.index }}" rows="5" data-customization data-customization-price="{{ text_custom.price }}" data-customization-name="{{ customization.name }}"></textarea> {% else %} <input type="text" placeholder="{{ customization.name }}" name="customizations[][{{ customization.name }}]" id="{{ customization.name }}-customization-{{ forloop.index }}" data-customization data-customization-price="{{ text_custom.price }}" data-customization-name="{{ customization.name }}" /> {% endif %}{% endfor %}O que esse caso precisa garantir:
- O campo continua pertencendo ao grupo correto por meio de
nameedata-customization-name; - O texto digitado possa ser lido no momento da compra;
- Campos vazios não sejam tratados como personalização válida;
- O preço adicional continue disponível em
data-customization-price.
Diferença de comportamento entre Texto e Opção
Seção intitulada “Diferença de comportamento entre Texto e Opção”Na implementação de referência, os dois tipos não são tratados exatamente da mesma forma:
- O grupo inteiro só é renderizado se existir pelo menos uma customização com estoque disponível;
- Em personalizações do tipo texto, entradas sem estoque são ignoradas com
{% continue %}; - Em personalizações do tipo opção, entradas sem estoque continuam sendo renderizadas, mas recebem a classe
-unavailable; - Em personalizações do tipo opção, a referência ainda tenta marcar automaticamente a primeira opção disponível no carregamento da PDP.
📘 Inputs e Labels
Nos exemplos aqui, só demonstramos a criação dos inputs pois eles que são mais restritos, e com atributos obrigatórios que devem ser atentados na hora da criação.
Os elementos de label, para tratar da exibição mais gráfica das opções, já estão previstas nos templates também e você pode alterá-los conforme a necessidade.
Como o envio das personalizações funciona
Seção intitulada “Como o envio das personalizações funciona”A estrutura visual pode variar de uma loja para outra. O que não pode variar é o contrato necessário para o momento da compra.
Na implementação de referência, o JavaScript da compra busca todos os campos com data-customization, lê o tipo de cada um e monta o objeto de personalizações a partir de data-customization-name, value ou custom.value.
Sequência real da referência:
handleSubmit()intercepta o envio do formulário;Store.validateFormProduct()valida a compra antes do envio;Store.addProduct()decide qual fluxo será usado;Store.addItem()enviaFormDataquando a quantidade é1;Store.addItemsCustom()montaitems[].customizationsquando existe personalização e a quantidade é maior que1.
Fluxo com quantidade igual a 1
Seção intitulada “Fluxo com quantidade igual a 1”Quando existe apenas uma unidade no formulário, a implementação de referência segue o envio padrão do próprio formulário. Nesse caso, a decisão acontece em addProduct(): se houver personalização, mas quantity não for maior que 1, o fluxo segue para addItem() e os dados são enviados como FormData.
addProduct: function(form, parent, callback) { const customizations = form.querySelectorAll('[data-customization]') const quantity = Number(form.querySelector('[name="quantity"]').value)
if (Store.useSubscription && form.hasAttribute('data-product-subscription')) { Store.addSubscription(form, parent) } else { if (customizations.length > 0 && quantity > 1) { Store.addItemsCustom(form, parent, callback) } else { Store.addItem(form, parent, callback) } }},
addItem: async function (form, parent, callback) { const _this = this; const btnComprar = parent.querySelector('[data-action="add-cart"]'); const urlAdd = '/carrinho/adicionar'; const formData = urlencodeFormData(new FormData(form)); const boxResponse = parent.querySelector('[data-form-product] .msg-response:not(.resp-validate)');
if (!btnComprar.classList.contains('-adding')) { btnComprar.classList.add('-adding');
try { while (true) { const response = await fetch(urlAdd, { method: 'POST', headers: { 'Accept': 'application/json, text/javascript, */*; q=0.0', 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', }, body: formData, });
if (response.status === 202) { const retryAfter = response.headers.get('Retry-After'); const delay = retryAfter ? parseInt(retryAfter, 10) : 3000; await new Promise((resolve) => setTimeout(resolve, delay)); } else { if (!response.ok) { const text = await response.text() console.error('addItem - Erro na requisição de adição de produto.') console.error(response) console.error(text) return }
const data = await response.json();
if (typeof callback === 'function') { callback('produto-adicionado', data, boxResponse, form); } else { _this.addItemResult('produto-adicionado', data, boxResponse, form); } break; } } } catch (error) { if (typeof callback === 'function') { callback('erro-adicionar', error, boxResponse, form); } else { _this.addItemResult('erro-adicionar', error, boxResponse, form); } }
btnComprar.classList.remove('-adding'); }}Nesse caminho, o contrato importante é que os inputs de personalização estejam dentro do mesmo <form> de compra, com name, data-customization e data-customization-name corretamente preenchidos, para que entrem no FormData.
Antes desse envio, a implementação de referência percorre os campos com data-customization e marca como disabled os campos textuais vazios. Com isso, personalizações sem conteúdo não entram na compra por acidente. No fluxo customizado, esses campos são ignorados na leitura do payload e, ao final da tentativa de compra, o disabled é removido novamente.
Fluxo com quantidade maior que 1
Seção intitulada “Fluxo com quantidade maior que 1”Quando existe personalização e a quantidade é maior que 1, a implementação de referência muda o fluxo e monta um payload próprio com items[].customizations.
Fluxo resumido:
- O formulário localiza todos os campos com
data-customization; - Para campos textuais, o valor digitado é lido diretamente do input ou textarea;
- Para campos de opção, o valor selecionado é lido do atributo
valuedo input marcado; - O nome de cada personalização vem de
data-customization-name; - Quando há personalização e quantidade maior que
1, a implementação de referência monta um payload comitems[].customizations.
Por isso, mesmo em uma implementação própria, os campos precisam continuar expondo corretamente:
data-customization;data-customization-name;name="customizations[][{{ customization.name }}]";valuepara opções;- Conteúdo digitado para texto.
Na lógica de compra da implementação de referência, esse contrato repercute diretamente na montagem do payload. Quando o produto tem personalização e quantidade maior que 1, o frontend executa o fluxo abaixo:
addItemsCustom: async function(form, parent, callback) { const _this = this; const btnComprar = parent.querySelector('[data-action="add-cart"]'); const boxResponse = parent.querySelector('[data-form-product] .msg-response:not(.resp-validate)'); const quantity = Number(form.querySelector('[name="quantity"]').value) let hasCustoms = false let customs = {}
// Prepara o item e suas personalizações let data = { items: [ { sku: form.querySelector('[name="sku"]').value, quantity, customizations: [] } ]}
const customizations = form.querySelectorAll('[data-customization]') if (customizations.length > 0) customizations.forEach(custom => { const disabled = custom.getAttribute('disabled') const type = custom.getAttribute('type') let checked = true; if (type !== 'text' && type !== 'textarea') checked = custom.checked
if (checked && disabled !== 'true') { hasCustoms = true if (type === 'text' || type === 'textarea') { customs[custom.getAttribute('data-customization-name')] = custom.value } else { customs[custom.getAttribute('data-customization-name')] = custom.getAttribute('value') } } })
if (hasCustoms) { for (let index = 0; index < quantity; index++) { data.items[0].customizations.push(customs) } }
const json_data = JSON.stringify(data)
try { const response = await fetch('/carrinho/adicionar/kit', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: json_data, });Nesse fluxo, o bloco que resolve a personalização no HTML é justamente o conjunto de campos criado por partials/content/product/customizations/customizations. É dele que saem os elementos com data-customization, data-customization-name, data-customization-price e os valores efetivamente lidos para montar customs e preencher items[].customizations.
Em termos práticos, a responsabilidade fica dividida assim:
- O bloco de variantes resolve qual SKU está sendo comprado;
- O bloco de personalizações resolve quais dados extras acompanham esse SKU;
- A lógica
addItemsCustom()combina os dois resultados e transforma isso no payload final de compra.
Um exemplo do payload final montado nesse fluxo:
{ "items": [ { "sku": "12345", "quantity": 2, "customizations": [ { "Nome": "Nicolas", "Cor da gravacao": "Dourado" }, { "Nome": "Nicolas", "Cor da gravacao": "Dourado" } ] } ]}Comportamentos da implementação de referência
Seção intitulada “Comportamentos da implementação de referência”A implementação de referência também mostra alguns comportamentos úteis para quem estiver montando a própria estrutura.
Página de produto
Seção intitulada “Página de produto”A página identifica produtos com personalização por meio da tag personalizacao:
{% assign customs_tag = product.tags | where: 'type', 'personalizacao' | first %}Quando essa tag existe, a faixa de compra flutuante não é renderizada:
{% if show_floating_buy != false and isBundle == false and customs_tag == nil %} {% render 'partials/content/product/floating_buy', product: product, mobile: mobile %}{% endif %}Quickview
Seção intitulada “Quickview”No quickview, produtos com personalização deixam de seguir o fluxo direto de compra e passam a apontar para a página do produto:
{% assign customs_tag = product.tags | where: 'type', 'personalizacao' | first %}
{% if customs_tag == nil and isBundle == false %} {% render 'partials/content/product/infos/purchase', product: product, show_button: true, show_short_desc: false, text_string: 'Comprar', show_qtd_selector: false, show_more_link: false, dropdown: dropdown %}
{% else %} <div class="add-to-cart"> <a aria-label="Ir para o produto {{ product.name }}" href="{{ product.url }}" class="button-default add-to-cart-button"> {% if isBundle == false %} Personalize {% else %} Comprar {% endif %} </a> <a aria-label="Acessar o produto {{ product.name }}" class="more-link" href="{{ product.url }}">Mais detalhes</a> </div>{% endif %}Esses comportamentos não são obrigatórios em toda loja, mas mostram um padrão importante: produtos com personalização frequentemente exigem um fluxo de compra mais controlado do que produtos simples.
Comportamento JavaScript da referência
Seção intitulada “Comportamento JavaScript da referência”Na implementação de referência, o comportamento das personalizações na PDP é inicializado pelo JavaScript da página de produto. Esse comportamento:
- Exibe o valor adicional no elemento com
data-value; - Marca automaticamente a primeira opção disponível em personalizações do tipo opção.
Nesse arquivo, formatMoney é uma função utilitária já existente na estrutura base. Ela é responsável por transformar o valor numérico de data-customization-price no texto monetário exibido ao cliente, como R$ 10,00. No exemplo abaixo, o import aparece apenas para deixar explícito de onde vem essa formatação.
import { formatMoney } from '../../components/utilities'
const Customizations = {
// Configura exibição do valor individual de cada personalização handleIndividualPrices: function() { const customs = document.querySelectorAll('[data-custom-option]')
customs.forEach(custom => { const options = custom.querySelectorAll('[data-customization]') const displayValue = custom.querySelector('[data-value]') if (!displayValue) return
options.forEach(option => { option.addEventListener('change', () => { const type = option.getAttribute('type') const value = Number(option.getAttribute('data-customization-price'))
if (value > 0) {
// Separa validação por input de seleção e input de texto if (type === 'radio' || type === 'checkbox') { option.checked ? displayValue.innerText = `+ ${formatMoney(value)}` : displayValue.innerText = ''
} else if (type === 'text' || type === 'textarea') { option.value != '' ? displayValue.innerText = `+ ${formatMoney(value)}` : displayValue.innerText = '' }
} else { displayValue.innerText = '' } }) }) }) },
// Marca a primeira opção das customizações que possuem mais de uma opção markFirstOption: function() { const customs = document.querySelectorAll('[data-custom-option]') customs.forEach(custom => { const inputs = custom.querySelectorAll('input[type="radio"], input[type="checkbox"]') if (inputs.length > 0) { const firstAvailable = custom.querySelector('.label.-available') if (firstAvailable) firstAvailable.click() } }) },
init: function() { this.handleIndividualPrices() this.markFirstOption() }}
export default CustomizationsErros comuns de implementação
Seção intitulada “Erros comuns de implementação”Alguns erros aparecem com frequência quando a personalização é implementada fora da estrutura de referência.
- Renderizar os campos de personalização fora do
<form>de compra; - Remover
data-customizationdos campos e impedir que o JavaScript os encontre; - Alterar ou remover
data-customization-name, quebrando a chave usada no objetocustoms; - Usar o exemplo textual com
option.priceem vez detext_custom.price; - Remover o
valuedos inputs de opção; - Quebrar a adjacência entre input e label em personalizações com dropdown;
- Supor que o fluxo de compra é sempre o mesmo, sem considerar a diferença entre
quantity = 1equantity > 1; - Tratar elementos visuais como
data-valuecomo obrigatórios para envio, quando eles só participam da exibição do preço adicional.
Checklist do contrato mínimo
Seção intitulada “Checklist do contrato mínimo”Ao alterar a estrutura de personalizações, valide se estes pontos continuam preservados:
- A tag
{% load_customizations product_id: product.id %}continua sendo chamada na PDP; - Cada grupo de personalização continua separado logicamente dos demais;
- O HTML continua expondo
data-customizationem todos os campos relevantes; - O HTML continua expondo
data-customization-namecom o nome correto da personalização; - O HTML continua expondo
data-customization-pricequando houver preço adicional; - Os inputs continuam com
name="customizations[][{{ customization.name }}]"; - Campos de opção continuam expondo
valuecom a opção selecionada; - Campos de texto continuam permitindo a leitura do conteúdo digitado;
- Se houver exibição de preço em tela, continua existindo um alvo claro para esse valor, como
data-value.
🚧 Alteração de valores em produtos personalizados
No caso de personalizações com valor adicional em um produto o preço deve ser alterado. Atente-se a realizar o cálculo do novo valor do item. Isso porque a soma do novo preço não é feita nativamente na página do produto. Esse cálculo é feito automaticamente apenas na página de carrinho.