Today we’ll talk about most requested feature for variant metafields, i.e way the variant metafields get automatically updated on change in the selection. This will need update to your theme’s liquid code, but no worries we’ve tried to make it pretty simple.
Step 1:
Navigate to your store’s admin panel and open themes. You wanna try the code before publishing it on live, so better make a duplicate theme of your active theme and start editing code of your duplicated and unpublished theme.
Step 2:
Create a snippet with name “variant-dynamic-metafield” (you can choose any name, but keep in mind to use same name in Step 3 while rendering snippets on your product page)
Insert following code into this file and save:
{% comment %}
\*****Copyrights DigiBrew (digibrew.io)*****\
{% endcomment %}
{% if updateHTMLSelectors %}
<script>
if(typeof variantMetafieldValues === 'undefined'){
var variantMetafieldValues = {};
}
</script>
{% endif %}
{% for variant in product.variants %}
{% if updateHTMLSelectors %}
<script>
if(typeof variantMetafieldValues[{{ variant.id }}] === 'undefined')
variantMetafieldValues[{{ variant.id }}] = {}
if(typeof variantMetafieldValues[{{ variant.id }}]["{{ namespace }}"] === 'undefined')
variantMetafieldValues[{{ variant.id }}]["{{ namespace }}"] = {}
if(typeof variantMetafieldValues[{{ variant.id }}]["{{ namespace }}"]["{{ key }}"] === 'undefined')
variantMetafieldValues[{{ variant.id }}]["{{ namespace }}"]["{{ key }}"] = {}
if(typeof variantMetafieldValues[{{ variant.id }}]["{{ namespace }}"]["{{ key }}"]["selectors"] === 'undefined')
variantMetafieldValues[{{ variant.id }}]["{{ namespace }}"]["{{ key }}"]["selectors"] = [`{{ updateHTMLSelectors }}`]
else
variantMetafieldValues[{{ variant.id }}]["{{ namespace }}"]["{{ key }}"]["selectors"].push(`{{ updateHTMLSelectors }}`)
variantMetafieldValues[{{ variant.id }}]["{{ namespace }}"]["{{ key }}"]["value"] = "{{ variant.metafields[namespace][key] }}"
</script>
{% elsif is_image %}
{% if variant.metafields[namespace][key] %}
<img class="dynamic-variant-data variant-{{variant.id}} {{ class }}" src="{{ variant.metafields[namespace][key] }}" alt="{{ key }}" style="{% if variant.id != selected_variant.id %}display:none{% endif %}">
{% endif %}
{% elsif is_input %}
<input namespace="{{ namespace }}" key="{{ key }}" variant-id="{{variant.id}}" class="dynamic-variant-data-input-value" value="{% if variant.metafields[namespace][key] %}{{ variant.metafields[namespace][key] }}{% endif %}" style="display:none"/>
{% else %}
<span class="dynamic-variant-data variant-{{variant.id}} {{ class }}" style="{% if variant.id != selected_variant.id %}display:none{% endif %}">
{% if variant.metafields[namespace][key] %}
{{ variant.metafields[namespace][key] }}
{% endif %}
</span>
{% endif %}
{% endfor %}
{% if is_input %}
<input name="{{ name }}" namespace="{{ namespace }}" key="{{ key }}" class="dynamic-variant-data-input {{ class }}" {%if display %}style="display:{{ display }}"{% endif %} value="{{ selected_variant.metafields[namespace][key] }}"/>
{% endif %}
<script>
if(!variantDynamicMetafieldScriptAlreadyAdded){
var variantDynamicMetafieldScriptAlreadyAdded = true;
document.addEventListener('DOMContentLoaded', function(){
function usePushState(handler){
//modern themes use pushstate to track variant changes without reload
function track (fn, handler, before) {
return function interceptor () {
if (before) {
handler.apply(this, arguments);
return fn.apply(this, arguments);
} else {
var result = fn.apply(this, arguments);
handler.apply(this, arguments);
return result;
}
};
}
var currentVariantId = null;
function variantHandler () {
var selectedVariantId = window.location.search.replace(/.*variant=(\d+).*/, '$1');
if(!selectedVariantId) return;
if(selectedVariantId != currentVariantId){
currentVariantId = selectedVariantId;
handler(selectedVariantId);
}
}
// Assign listeners
window.history.pushState = track(history.pushState, variantHandler);
window.history.replaceState = track(history.replaceState, variantHandler);
window.addEventListener('popstate', variantHandler);
}
function updateSelectorVal(el,selector_options,value){
var defaultValue;
var valueType = typeof selector_options[1] !== 'undefined' ? selector_options[1] : 'innerHTML';
switch(valueType){
case 'innerHTML':
if(value){
defaultValue = el.innerHTML;
el.innerHTML = value;
}
break;
case 'value':
if(value){
defaultValue = el.value;
el.value = value;
}
break;
case 'attr':
const attrName = selector_options[2];
if(typeof attrName !== 'undefined'){
valueType += '-'+attrName
if(value){
defaultValue = el.getAttribute(attrName);
el.setAttribute(attrName,value)
}
}
break;
default:
if(value){
el.innerHTML = value;
el.value = value;
}
break;
}
const defaultValueAttrName = 'default-value-'+valueType;
if(value){
if(!el.getAttribute(defaultValueAttrName))
el.setAttribute(defaultValueAttrName,defaultValue)
}else{
const defaultAttrValue = el.getAttribute(defaultValueAttrName);
if(defaultAttrValue){
updateSelectorVal(el,selector_options,defaultAttrValue)
}
}
}
usePushState(function(variantId){
if(typeof variantMetafieldValues[variantId] !== 'undefined'){
for (namespace_index in variantMetafieldValues[variantId]){
const namespaces = variantMetafieldValues[variantId][namespace_index];
for (key_index in namespaces){
const key = namespaces[key_index];
key.selectors.map((selector) => {
const selectors = selector.split(",");
selectors.map((selector_option) => {
const selector_options = selector_option.split("|");
for (let el of document.querySelectorAll(selector_options[0])){
updateSelectorVal(el,selector_options,key.value);
}
})
})
}
}
}
for (let el of document.querySelectorAll('.dynamic-variant-data')) el.style.display = 'none';
for (let el of document.querySelectorAll('.dynamic-variant-data.variant-'+variantId)) el.style.display = '{{ display }}';
for (let el of document.querySelectorAll('.dynamic-variant-data-input')) {
const matching_el = document.querySelector('.dynamic-variant-data-input-value[namespace="'+el.getAttribute('namespace')+'"][key="'+el.getAttribute('key')+'"][variant-id="'+variantId+'"]');
el.value = matching_el ? matching_el.value : '';
};
});
});
}
</script>
Step 3:
You can render auto-updating variant metafields on product.liquid using following code. I’ll explain the customizable parameters below.
{% render "variant-dynamic-metafield", product: product, selected_variant: product.selected_or_first_available_variant, display: "inline-block", namespace: "your_variants_metafield_namespace", key: "your_variants_metafield_key" %}
Customizable Parameters:
- updateHTMLSelectors – update html elements using selectors with option to update innerHTML, attributes or value. Selector format:
[selector]|[innerHTML/value/attr]|[attr-name(optional)]
e.g
{% render "variant-dynamic-metafield", product: product, selected_variant: product.selected_or_first_available_variant, updateHTMLSelectors : 'head title|innerHTML,meta[property="og:title"]|attr|content', namespace: "global", key: "title_tag" %}
{% render "variant-dynamic-metafield", product: product, selected_variant: product.selected_or_first_available_variant, updateHTMLSelectors : 'meta[name="description"]|attr|content', namespace: "global", key: "description_tag" %}
{% render "variant-dynamic-metafield", product: product, selected_variant: product.selected_or_first_available_variant, updateHTMLSelectors : 'input[name="variant-description"]:value', namespace: "global", key: "description_tag" %}
- display – css display property, possible values “block”, “inline-block” etc. (https://www.w3schools.com/cssref/pr_class_display.asp)
- namespace – Variant’s metafield namespace
- key – Variant’s metafield key
- class – custom css class for variant element
- is_image – true/false
- is_input – true/false
- name – input name (only applicable if is_input is set to true)
And that’s it. Happy Coding ! Let us know if this worked for you or not.