2
votes

I'm trying to build on caroline's solution for related products with metafields to build related product variants. I.e when you click on the white color variant for a desk, you will see the white variant of a chair as a related product. (As opposed to linking the desk product to the chair product regardless of variant.) Caroline's solution is here: https://gist.github.com/carolineschnapp/1003334 and below is my code. Right now it's putting the same product twice on page load, and nothing happens when a different vairant is selected. The way I am formatting the metafield value for each variant is by putting "related-product-handle-1, variant-id-1, related-product-handle-2,variant-id-2, related-product-handle-3, variant-id-3" instead of just the product handles.

{% assign image_size = 'compact' %}
{% assign heading = 'Related Products' %}

{% if product.selected_or_first_available_variant.metafields.recommendations.productHandles %}

<h3>{{ heading }}</h3>
<ul class="related-products"></ul>

{% endif %}

<script>!window.jQuery && document.write('<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"><\/script>')</script>
{{ 'api.jquery.js' | shopify_asset_url | script_tag }}

<script type="text/javascript" charset="utf-8">  

$(document).ready(function(){

setTimeout(function() {
    var dd = $('.single-option-selector#product-select-option-0');
      var vId = location.search.substring(9);
      switchRelated(vId);

    dd.on('change', function() {
      $('ul.related-products').empty();
      var vId = location.search.substring(9);
      switchRelated(vId);  
    });

    function switchRelated(vId) {
      var list = $('ul.related-products');
      var vIdd = parseInt(vId);
      {% for variant in product.variants %}
        {% if variantId == vIdd %}
          {% if variant.metafields.recommendations.productHandles %}
            recommendations = jQuery.trim({{ variant.metafields.recommendations.productHandles | json }}).split(/[\s,;]+/);
            for (var i=0; i < (recommendations.length); i+=2 ) {
              var j = (i + 1);
              if (recommendations.length && recommendations[i] && recommendations[j] !== '') {
                jQuery.getJSON('/products/' + recommendations[i] + '.js', function(product) {
                  product.variants.forEach(function(variant) {
                    if (variant.id == parseInt(recommendations[j])) {
                      list.append('<li><div class="image"><a href="' + product.url + '?variant=' + recommendations[j] +'"><img src="' + variant.featured_image.src + '" /></a></div><h4><a href="' + product.url + '?variant=' + recommendations[j] + '">' + product.title + '</a></h4></li>'); 
                    }
                  });
                });
              }
            } 
          {% endif %}   
        {% endif %}
      {% endfor %}
    }    
  }, 1);
});
</script>

Answer edited: The first was extremely helpful once I made some corrections to syntax errors and a couple of short additions. Here is my edited version of the answer for anyone who may need it:

Product.liquid:

 {% for variant in product.variants %}
                {% capture metafield_data %}{% endcapture %}
                {% assign related_products = variant.metafields.recommendations.productHandles | split: '|' %}
                {% for related_product in related_products %}
                  {% assign metafield_items = related_product | split: ',' %}
                  {% assign r_p = metafield_items[0] %}
                  {% assign r_v = metafield_items[1] | plus: 0 %} 
                  {% assign r_n = all_products[r_p].title %}
                  {% for related_variant in all_products[r_p].variants %}
                    {% if related_variant.id == r_v %} 
                      {% assign r_i = related_variant.image.src | img_url: 'small' %}
                    {% endif %}
                  {% endfor %}
                  {% capture metafield_data %}{{metafield_data}}{{ r_p }},{{ r_v }},{{ r_i }},{{ r_n }}{% unless forloop.last %}|{% endunless %}{% endcapture %}
                {% endfor %}
                <option id="{{ variant.id }}" data-metafield="{{ metafield_data }}" {% if variant == product.selected_or_first_available_variant %} selected="selected" {% endif %} value="{{ variant.id }}">{{ variant.title }} - {{ variant.price | money }}- {{ related_products.size }}</option>
              {% endfor %}

And related-variants javascript snippet:

$(document).ready(function(){

  setTimeout(function() {
    var dd = $('.single-option-selector#product-select-option-0');
    if(location.search.substring(9) != ''){
      var vId = location.search.substring(9);
     }
     else {
      var vId = {{ product.selected_or_first_available_variant.id }};
     }
    switchRelated(vId);


    $('#product-select option').each(function(index, element){

      $(".single-option-selector#product-select-option-0 option:eq(" + index + ")").attr('id', element.id);
      $('.single-option-selector#product-select-option-0 option:eq(' + index + ')').attr('data-metafield', $(element).attr("data-metafield"));
      $('#product-select option:eq(' + index + ')').attr('id', '');


    });


    dd.on('change', function() {
      var list = $('ul.related-products');
      $(list).children().remove();
       $(list).empty();
      if(location.search.substring(9) != ''){
        var vId = location.search.substring(9);
      }
      else {
       var vId = {{ product.selected_or_first_available_variant.id }};
      }
      switchRelated(vId);  
    });

    function switchRelated(vId) {
      var list = $('ul.related-products');
      $(list).children().remove();
      $(list).empty();
      var vIdd = parseInt(vId);

      console.log(vIdd)
      var variant_matches = $('#' + vId).attr('data-metafield').split('|');

      for (var i=0; i < variant_matches.length; i++) {
          var items = variant_matches[i].split(','); 

          list.append('<li><div class="image"><a href="/products/'+items[0]+'/?variant='+items[1]+'"><img src="'+items[2]+'" /></a></div><h4><a href="/products/' + items[0] + '?variant=' + items[2] + '">' + items[3].replace('_','') + '</a></h4></li>');

      }
    }   
  }, 1);
});

The only thing I'm nervous about is the fact that I am copying over the data from the 'product-select' dropdown over to the 'single-option-selector' dropdown. I am doing this because there is no template for rendering the single-option-selector, it seems to be getting added through javascript. If anyone has any insight into manipulating the single-option-selector in liquid, please let me know. Thank you!!

1
This is a great question and you have my upvote. But what is {% if variantId == vIdd %} in code? Are you trying to validate JS variable with liquid variable?HymnZzy
@HymnZ I am passing in the variant parameter from the url using location.search (vId) and vIdd is the parsed integer form of vId. Since I cannot query the Product object by variant ID directly, I am iterating through product.variants and catching the variant that has the same ID as the url, which is the current selected variant. (The reason I am not using liquid's "selected_or_first_available_variant" object is because it is not capturing the right variant ID when a user selects a new variant from the dropdown.Tatiana Frank
All as it is, you cannot pass a JavaScript variable into liquid code. Since you are doing that, the entire JavaScript is loaded only with values that are first generated from the server side (liquid values) and hence the subsequent changes don't affect. Your function breaks off here {% if variantId == vIdd %}. Check console logs. If you can update your question with a proper sample of a metafield value I can provide an answer.HymnZzy
I do check console logs @hymnz An example of a meta field value for the key productHandles would be: classic-chair, 18273486, modern-chair, 15438726, wood-chair, 19484672Tatiana Frank

1 Answers

1
votes

The fastest way to implement this is to let Shopify servers structure the elements for to pull links and image URLs with any AJAX calls. Here's how to do it.

From your example of productHandles, I'm guessing the 3 items in the list are related to a particular variant id xxxxxx. Structure the metafield value this way

rph-1,rph-v-id-1|rph-2,rph-v-id-2|rph-3,rph-v-id-3

Now in product liquid find this section

<select class="product-select" id="product-select" name="id" .... </select>

Change the inside html to the one below -

{% for variant in product.variants %}
    {% assign related_products = variant.metafields.recommendations.productHandles | split: '|' %}
    {% for related_product in related_products %}
      {% assign metafield_items = related_product | split: ',' %}
      {% assign r_p = metafield_items[0] %}
      {% assign r_v = metafield_items[1] | plus: 0 %} {% comment %} converting string to number {% endcomment %}
      {% assign r_n = all_products[r_p].title | replace: ' ','_' %}
      {% for related_variant in all_products[r_p].variants %}
        {% if related_variant.id == r_v %} {% comment %} fails if r_v is a string {% endcomment %}
          {% assign r_i = related_variant.image.src }}
        {% endif %}
      {% endfor %}
      {% capture metafield_data %}{{metafield_data}}{{ r_p }},{{ r_v }},{{ r_i }},{{ r_n }}{% unless forloop.last %}|{% endunless %}{% endcapture %}
    {% endfor %}
    <option id="{{ variant.id }}" metafield-data={{ metafield_data }}{% if variant == product.selected_or_first_available_variant %} selected="selected" {% endif %} value="{{ variant.id }}">{{ variant.title }} - {{ variant.price | money }}</option>
{% endfor %}

"metafield_data" will contain all the related products information (product,variant,variant image).

JS to replace "switchRelated(vId)"

function switchRelated(vId) {
    var list = $('ul.related-products');
    var vIdd = parseInt(vId);

    list.children().remove()

    var variant_matches = $('#vId').attr('metafield-data').split('|')

    for (var i=0; i < variant_matches.length; i++) {
        var items = variant_matches[i].split(',')
        list.append('<li><div class="image"><a href="/products/'+items[0]+'/?variant='+items[1]+'"><img src="'+items[2]+'" /></a></div><h4><a href="/products/'+items[0]+'?variant='item[2]'">'+items[3].replace('_','')+'</a></h4></li>'); 
    }
}    

In layman terms, you are taking the product handle and variant id from the metafields and adding title and image to them using liquid (server side function). You are then matching them to variants and assigning a data variable in the element which you are using again to change the html elements.

P.S. The code is long and not properly aligned and I may have missed code punctuations here and there. Please check them. The logic is simple and the entire weight of AJAX is removed and shifted to normal HTML calls.