5
votes

I'm using shopify and would like to show the variant price in the variant selector dropdown. Shopify uses an asset named option_selection.js to implement variant selection features. This asset is necessary for proper operation of the site; however, this asset overwrites the select tag that is created in the product.liquid file.

Without option_selection.js included, you can simply add the variant price to each option in the product.liquid file. Something like this:

<div class="variants-wrapper clearfix {% if product.variants.size == 1 %}visuallyhidden{% endif %}"> 
    <select id="product-select" name="id">
        {% for variant in product.variants %}
            <option value="{{ variant.id }}">{{ variant.title | escape }} - {{ variant.price | money }}{% if variant.available == false %} - No Stock{% endif %}</option>
        {% endfor %}
    </select>
</div>

However, with option_selection.js enabled, this select dropdown is replaced with a different one containing only the variant title and no other information.

After some trial and error I found that I could overwrite Shopify.Product.prototype.optionValues in a script tag placed directly after the option_selection.js script tag with the following snippet:

Shopify.Product.prototype.optionValues = function(o) {
  if (!Shopify.isDefined(this.variants)) return null;
  var t = Shopify.map(this.variants, function(t) {
    var e = "option" + (o + 1);
    if(t[e] == undefined) {
      return null
    } else {
      var value = t[e] + " - " + Shopify.formatMoney(t.price)
      if(!t.available) value += " - No Stock"
      return value
    }
  });
  return null == t[0] ? null : Shopify.uniq(t)
}

That snippet overwrites the following snippet:

Shopify.Product.prototype.optionValues = function(o) {
    if (!Shopify.isDefined(this.variants)) return null;
    var t = Shopify.map(this.variants, function(t) {
        var e = "option" + (o + 1);
        return t[e] == undefined ? null : t[e]
    });
    return null == t[0] ? null : Shopify.uniq(t)
}

However, this causes the rest of the features implemented in option_selection.js to stop working entirely. I don't understand why this change should affect the rest of the script, but it does.

To test for this, you can create an item with two variants, one available and one unavailable. Selecting the available item should enable the "Add to cart" button, while selecting the unavailable option should disable the button. This is one of many features implemented by option_selection.js that fails once this attempted fix is implemented.

How can I go about including the variant price in the variant select dropdown while retaining option_selection.js and its features?


1

1 Answers

1
votes

It turns out that the problem with the second attempted solution resides in the following snippet from option_selection.js:

Shopify.SingleOptionSelector = function(o, i, t, e) {
    this.multiSelector = o, this.values = e, this.index = i, this.name = t, this.element = document.createElement("select");
    for (var r = 0; r < e.length; r++) {
        var n = document.createElement("option");
        n.value = e[r], n.innerHTML = e[r], this.element.appendChild(n)
    }
    return this.element.setAttribute("class", this.multiSelector.selectorClass), this.element.setAttribute("data-option", "option" + (i + 1)), this.element.id = o.domIdPrefix + "-option-" + i, this.element.onchange = function(t, e) {
        e = e || {}, o.updateSelectors(i, e)
    }, !0
}

In this snippet the .innerHTML property is set the same as the value. You don't actually want to change the value, you only want to change the innerHTML. In order to achieve this effect, we will have to change a few more things in option_selection.js.

It is easier to make a copy of shopify's option_selection.js and make our own. To accomplish this you can read the generated page source to find the script URL then copy the source to a new asset with the file name option_selection and the extension .js. Next paste the source (using a code beautifier will make it a lot easier to work with) into the newly created asset. Then you just have to change the shopify asset tag to a regular asset tag by replacing shopify_asset with asset.

Find the following snippet in the new asset:

Shopify.Product.prototype.optionValues = function(o) {
    if (!Shopify.isDefined(this.variants)) return null;
    var t = Shopify.map(this.variants, function(t) {
        var e = "option" + (o + 1);
        return t[e] == undefined ? null : t[e]
    });
    return null == t[0] ? null : Shopify.uniq(t)
}

Then add the following snippet after that snippet:

Shopify.Product.prototype.optionTexts = function(o) {
  if (!Shopify.isDefined(this.variants)) return null;
  var t = Shopify.map(this.variants, function(t) {
    var e = "option" + (o + 1);
    if(t[e] == undefined) {
      return null
    } else {
      var value = t[e] + " - " + Shopify.formatMoney(t.price)
      if(!t.available) value += " - No Stock"
      return value
    }
  });
  return null == t[0] ? null : Shopify.uniq(t)
}

Notice that this function has a new name, .optionTexts, as this function will only create the text to be inserted inside the option tags.

Next find the following line:

var e = new Shopify.SingleOptionSelector(this, t, this.product.optionNames()[t], this.product.optionValues(t));

Then replace that line with the following line:

var e = new Shopify.SingleOptionSelector(this, t, this.product.optionNames()[t], this.product.optionValues(t), this.product.optionTexts(t));

Here we are calling our newly created text function and passing the result alongside the original value function.

Next find the following snippet:

Shopify.SingleOptionSelector = function(o, i, t, e) {
    this.multiSelector = o, this.values = e, this.index = i, this.name = t, this.element = document.createElement("select");
    for (var r = 0; r < e.length; r++) {
        var n = document.createElement("option");
        n.value = e[r], n.innerHTML = e[r], this.element.appendChild(n)
    }
    return this.element.setAttribute("class", this.multiSelector.selectorClass), this.element.setAttribute("data-option", "option" + (i + 1)), this.element.id = o.domIdPrefix + "-option-" + i, this.element.onchange = function(t, e) {
        e = e || {}, o.updateSelectors(i, e)
    }, !0
}

Then replace that snippet with the following snippet:

Shopify.SingleOptionSelector = function(o, i, t, e, texts) {
    this.multiSelector = o, this.values = e, this.index = i, this.name = t, this.element = document.createElement("select");
    for (var r = 0; r < e.length; r++) {
        var n = document.createElement("option");
        n.value = e[r], n.innerHTML = texts[r], this.element.appendChild(n)
    }
    return this.element.setAttribute("class", this.multiSelector.selectorClass), this.element.setAttribute("data-option", "option" + (i + 1)), this.element.id = o.domIdPrefix + "-option-" + i, this.element.onchange = function(t, e) {
        e = e || {}, o.updateSelectors(i, e)
    }, !0
}

Here we are accepting the previously passed list of text to be inserted in the option tags and are inserting that list instead of the list of values.

This way the values stay the same and the text is changed to whatever you want it to be.