Syndicate

Feed

Working on the product pages

Although we will want the browser to execute our JavaScript code on product pages only, we will not add our JavaScript code to product.liquid. We could, but we prefer to add all JavaScript to the head element of the document — where we can find all code in a snap, even when tired and confused. Open up your theme.liquid template, and add this Liquid condition to it: {% if template == 'product' %}. We also want to modify the document when it's ready, so we'll wrap everything in a function that will be executed when that happens.

{{ 'jquery-1.2.6.min.js' | asset_url | script_tag }}
{{ 'jquery.selectboxes.pack.js' | asset_url | script_tag }}
<script type="text/javascript">
  jQuery.noConflict();
  {% if template == 'product' %}
  jQuery(function() {
    /* All our descrambling code will be here */
  });
  {% endif %}
</script>

The following...

 $(function(){
   // Your code here
 });

... is a shorthand for...

 $(document).ready(function(){
   // Your code here
 });

Using a JavaScript Object to hold all of our stuff

We'll create a JavaScript Object to hold the info we read about our variants. This Object will also define functions we'll call, one after the other, to parse the variants titles, then build select boxes and then update the document appropriately when a visitor makes a selection. We will not create an Object to look like cool rock-on JavaScript programmers, but to make it easier to debug our code using Firebug.

Amend the above JavaScript to include the new Descrambler Object.

  jQuery.noConflict();
  {% if template == 'product' %}
  Descrambler = {
    // We will define a bunch of properties here,
    // among which there will be at least 5 functions.
    // Did you know that everything is a property
    // of an object in JavaScript? 
    // A function is a property too.
    // @see this article:
    // http://11heavens.com/everything-is-a-property-of-an-object-in-javascript
  }
  jQuery(function() {
    // Here, we'll call functions of the Descrambler object.
    // One after the other.
    // To get the job done.
    Descrambler.hideCurrentSelectionBox();
    Descrambler.buildAttrLists();
    Descrambler.addSelectBoxes();
    Descrambler.addPriceBox();
    Descrambler.addListenerOnSelection();
  });
  {% endif %}

Let's work a little harder and define some properties for our Object. We will initialize some properties as we define them. For now, functions will be empty shells: they will do nothing. We will fill up the gaps later on.

  jQuery.noConflict();
  {% if template == 'product' %}
  Descrambler = {
    delimiter: ' - ', // This is a sequence of characters. A String.
    variants: {}, // Object containing info on variants.
    totalAttr: 0, // Number of attributes for the product.
    hideCurrentSelectionBox: function() {
      // Hide the unsightly select box. Yuck.
    },
    buildAttrLists: function() {
      // Come up with lists of attributes.
      // We will update the totalAttr variable here.
    },
    addSelectBoxes: function() {
      // Add selection boxes to the page.
    },
    getCurrentSelection: function() {
      // Return the variant title that corresponds to what is currently selected.
    },
    addPriceBox: function() {
      // Add a placeholder element to display the price.
    },
    addListenerOnSelection: function() {
      // Add a listener to the new select boxes.
      // When visitor makes a selection, modify both the price 
      // and the current selection on the hidden form element.
    }
  }
  jQuery(function() {
    Descrambler.hideCurrentSelectionBox();
    Descrambler.buildAttrLists();
    Descrambler.addSelectBoxes();
    Descrambler.addPriceBox();
    Descrambler.addListenerOnSelection();
  });
  {% endif %}

We need a delimiter.

When our JavaScript will read each variant title, it will need to know where one attribute name ends and an other begins. A naming convention for the variant title will solve that problem for us. We need a delimiter. The delimiter can be anything at all, except a character that'll possibly get included within an attribute name (such as a space). However, the delimiter must look good, not be something tacky like *, because the delimiter will be part of the variant title, hence displayed on the cart page &mdash and shown on product pages to visitors who browse without JavaScript. The following delimiter is good enough:  - . Space-hyphen-space. Pick what you want. Just respect your naming convention whenever you fill up that variant title field while creating new products. And amend the code to reflect your choice. That's a single line of code to edit.

delimiter: ' - ', // This is a sequence of characters. A String.

Displayin' none that unsightly select box

We won't remove the current list of variants. We will simply hide it. Later on, after we've added our own select boxes, we'll want to update the current selection in the hidden "box" whenever the visitor makes a selection in the new boxes. Our new boxes will be just for show. None of their values will be submitted with the Add to Cart form — in other words.

In the Vogue theme, the variants are radio elements. Radio elements come with labels, and wishing to hide all of this, I will select the list that contains these radios and labels, and apply jQuery.hide() to it, like so:

hideCurrentSelectionBox: function() {
  jQuery('#product-variants ul').hide();
},
jQuery.hide() applies the following CSS to what is selected: display:none;.

Building list of attributes

We will read each variant title and split it in parts using our delimiter. We will put the first part in one bag (a first Array we'll call attributes_0), then we'll put the 2nd part in a 2nd bag (Array named attributes_1), third part in third bag, etc. for as many parts as the variant title contains. Before we blindly put a part in a bag, we'll look into the bag to see if a part with the same name is not already in there.

Additionally, while we loop through the product variants, we'll record information about each variant: the variant's title, its unique ID, and its price. We will need to refer to this information later on. We will use the following Object to hold our information: Descrambler.variants.

buildAttrLists: function() {
  var firstTitle = jQuery.trim('{{ product.variants.first.title }}');
  this.totalAttr = firstTitle.split(this.delimiter).length;
  for (var i = 0; i < this.totalAttr; i++) {
    // Initializing each Attributes Array.
    this['attributes_' +i] = [];
  }
  var split = []; // Initializing a split result.
  {% for variant in product.variants %}
  var variantTitle = jQuery.trim('{{ variant.title }}');
  this.variants[variantTitle] = {};
  this.variants[variantTitle].id = '{{ variant.id }}';
  this.variants[variantTitle].price = '{{ variant.price }}';
  split = variantTitle.split(this.delimiter);
  for (var j = 0; j < split.length; j++) {
    // If the attribute option is not yet recorded to its Attribute Array.
    if (jQuery.inArray(split[j], this['attributes_' + j]) == -1) {
      this['attributes_' + j].push(split[j]);
    }
  }
  {% endfor %}
},

The native JavaScript method String.split() is used to split a String into an Array of Strings. The syntax is String.split(separator, howmany). The first parameter is a regular expression, or a string, used to determine where the split must occur. The second parameter is optional, it is a number, and limits the number of parts returned.

jQuery.trim() is used to get rid of any space that could have been inserted by accident at the beginning or end of the variant title, something we often do when copying and pasting variant titles in the Shopify Admin UI.
jQuery.inArray() is a jQuery utility function we'll use to make sure there are no duplicates in our attributes_n Arrays.

From jQuery in Action

Taken from jQuery in Action

Adding select boxes

The API of the jQuery plugin Select box manipulation is quite simple. The matter in which we add options to a select box is demonstrated here:

var myOptions = {
  "Value 1" : "Text 1",
  "Value 2" : "Text 2",
  "Value 3" : "Text 3"
}
$("#myselect").addOption(myOptions);

In our case, value and text will be the same. We will create each select box with jQuery('<select></select>').

addSelectBoxes: function() {
  for (var i = 0; i < this.totalAttr; i++) {
    var options = {};
    for (var j = 0; j < this['attributes_' +i].length; j++) {
      var option = this['attributes_' +i][j];
      options[option] = option;
    }
    var labels = ['Choose a quantity:', 'Choose a flavor:', 'Sugar or no sugar:'];
    jQuery('<label for="select_' + i + '"></label>').html(labels[i]).appendTo('#product-variants');
    jQuery('<select></select>').addOption(options).attr('id', 'select_' + i).appendTo('#product-variants').find('option:first').attr('selected', 'selected');
  }
},

We start by creating the select box, then we add options to it, then we give it an id attribute, append it to the div with id 'product-variants', and, it's not over, we select the first option and mark it as selected. Using jQuery, we can chain all these actions, we can make it all happen with one single instruction, that is, one line of code followed by a semicolon.

What variant title is being chosen

We need a utility function, one that will read the values of our select boxes and reconstruct from these values the title of a variant.

getCurrentSelection: function() {
  var currentValues = [];
  for (var i = 0; i < this.totalAttr; i++) {
    currentValues.push(jQuery('#select_' + i).val());
  }
  var currentVariantTitle = currentValues.join(this.delimiter);
  return currentVariantTitle;
},
In JavaScript, the reverse of a split is a join. So we've used the function Array.join(separator) to re-construct the variant title that corresponds to the current selections. How do we read the selected value of a select element? We read it in the same way as we read the value of an input element, we use jQuery(selector).val().

How much does this cost

We will add a placeholder element to the page in which we will display the price of the currently selected variant. We will put something in this placeholder right away, we will initialize its value.

addPriceBox: function() {
  var firstVariantTitle = '{{ product.variants.first.title }}';
  var price = (parseFloat({{ product.variants.first.price }}) / 100);
  jQuery('<p></p>').attr('id', 'variant_price').html('Base price: $' + price.toFixed(2)).appendTo('#product-variants');
},

Updating the price and the variant selection

We will now add a listener to the new select elements so that whenever a visitor picks something from either of these boxes, we update the price displayed on the page and select the corresponding variant in the hidden select box. That hidden box will still be part of the document and its value will get submitted with the form when the customer clicks on the Add to Cart button.

addListenerOnSelection: function() {
  jQuery('select').change(function() {
    var currentVariantTitle = Descrambler.getCurrentSelection();
    var newId = Descrambler.variants[currentVariantTitle].id;
    var newPrice = Descrambler.variants[currentVariantTitle].price;
    newPrice = parseFloat(newPrice) / 100;
    // Here we select the radio button which corresponds to our variant id.
    jQuery(':radio[name=id]').val(newId);
    // Here we update the price shown on the page.
    jQuery('#variant_price').hide().html('New price: $' + newPrice.toFixed(2)).fadeIn('slow');
    return true;
  });
}

The price will be updated when a new selection is made.

The price will be updated when a new selection is made.

And we're done.

Something not working? Time to test

Click on the Firebug icon at the bottom right of your Firefox navigator window, or press F12.

The firebug icon in Firefox


Open your console. Then, type Descrambler in the right portion of the Firebug panel.

The Firebug console


Then press Run.

The run command


Expand the Descrambler Object by clicking on it. We'll examine its content.

Examining the content of Descrambler


Here is what I see for the Popsicles product.

The content of Descrambler

Things to look for:

  • Do the Arrays attributes_0, attributes_1 contain what they should...?
  • Do you have the right number of categories of attributes, ie: totalAttr ?
  • Does the Object variants contain an accurate description of your variants?

Common mistakes:

  • Leaving behind a few uncommented console.log's. This will cause problems in IE.
  • Creating an element like this: jQuery('<select>'). That's deprecated jQuery, and could cause problems in IE. Use the full HTML fragment, ie: jQuery('<select></select>')
  • Putting a comma after the last property in the Descrambler definition. Some browsers tolerate an extra comma after the last member of an object or element of an array, but some do not. IE and Opera will ignore everything that comes after the extra comma, without generating any error. Beware of this. This is most often referred to as the trailing comma bug.

In conclusion

If you've used this tutorial for your shop, consider dropping me a thank you line here, and/or donate to my website. I don't work for Shopify. No one is paying me to write these tutorials. I am doing this to help the Shopify community. Ironically, I am doing myself a disservice, because if I show you how to do these things, and do this well enough, then you won't need to hire me do do these things for you ;-) Thanks.

Browse this article

Last edited by Caroline Schnapp about 13 years ago.

Comments

Ran into an error.

Hi,

I tried to out your tutorial and thought I had it all figured out, except the drop downs do not show up. When I use fire bug, everything looks right, excepts my attributes are listed in red and in quotes. What step did I miss?

Thanks in advance!
.m.

Strings in Firebug

When I use fire bug, everything looks right, excepts my attributes are listed in red and in quotes. What step did I miss?

These are in red and in quotes because that's how String variables are displayed in Firebug. I have looked at the screen capture you sent me by email, and I can tell you that much: there's no problem whatsoever in how your variants are “descrambled”.

The code I provide is tailored to work for a certain theme (Vogue). If the drop-downs don't show up in your own theme, it's probably because they're not appended to the right div element in the code.

Although we will want the

Although we will want the browser to execute our JavaScript code on product pages only, we will not add our JavaScript code to product.liquid. We could, but we prefer to add all JavaScript to the head element of the document — where we can find all code in a snap, even when tired and confused.
http://e-papierossy.com.pl/en/e-papierosy/96-e-papierosy-gambit-5609.html
http://e-papierossy.com.pl/en/e-papierosy/95-e-papierosy-gambit-5412.html
http://e-papierossy.com.pl/en/e-papierosy/94-e-papierosy-gambit-9812.html

variants descrambled

hey caroline, i can see that my variants are descrambled correctly.
my shop is a custom version of glacialis and i cant see the descrambled dropdowns.

is there something i should change in the code you gave?

please help! this has been two weeks of hell for me and my client is losing business because of this...

DOM scripting

please help! this has been two weeks of hell for me and my client is losing business because of this...

The code provided involves manipulation of the Document Object Model, the algorithm is explained in details, same with the code. If you need my help with this, hire me. Implementation of the Descrambler costs $300 USD.

Alternatively, teach yourself DOM scripting with the JavaScript library of your choice. If you like jQuery, I recommend reading the book JavaScript The Missing Manual, or Learning jQuery 1.3.

uh huh

got it.
forget it.
thanks for the help.

wasn't complaining.
I only joined this site for this tutorial.
doesn't matter anywho.

take care.

and please don't think the word "client" means I make ANY money from this...
I'm just helping someone, that's why it's been 2 weeks - I'm not a programmer...
If this were a real client, I would just pay my programmer to do it.

Well then...

I hope that your friend has paid for your subscription, and that you will get lots of beer too.

Take care,

Caroline

lol

no they didn't.

no beer either...

i'd rather have a nice dark scotch...

Yes

i'd rather have a nice dark scotch...

A nice 10 to 15 year-old scotch bottle.

very new to this and so far i appreciate everything here

my question is with using this and shopify i have figured out how to use single sku's for multi items using your tutorial which was great thank you. but what i need to do now is be able to change those options per item. am i correct that this is the correct way following this tutorial or is there a easier way? as i said im very new to this and am learning as i go. this is strictly to help my wife out only. i come from a hardware backround and this is all new to me.

essentially if i have 2 items but want to offer them in different sizes and or colors what is the best way to implement that? that is my last step in getting her going. any help would be great and thank you for all the hard work in your tutorials.

By the way, you're posting

By the way, you're posting your comment under the wrong tutorial if you want to use 1 SKU per product and have options. The tutorial that shows how to use JavaScript attributes is over here.

but what i need to do now is be able to change those options per item... [...] essentially if i have 2 items but want to offer them in different sizes and or colors what is the best way to implement that

You will need to do the following if your options differ on a per-product basis:

  1. Define your options in your product description (at the bottom), using JavaScript
  2. From these options, build your drop-down boxes dynamically on the product page

For no 1, define 2 arrays in your product description and wrap your code in notextile tags, like so:

<notextile>
<script type="text/javascript">
if ((typeof Shopify) == 'undefined') {
  var Shopify = {};
}
Shopify.colors = [....];
Shopify.sizes = [....];
</script>
</notextile>

As for no 2, you can make your life easier if you use the select element manipulation plugin I recommend here.

Thanks for the positive feedback. It is very much appreciated. It made my day!

Thank you so much im trying this now....

it was driving me crazy trying to figure this out i had no idea i could edit the descriptions like that using javascript. im testing it now. ill be so happy if this works.. you are awesome ill report back as soon as i see if i can get it.. thank you again for taking the time out to help

okay now i think im

okay now i think im confused... prior to your posting i was able to get the buttons viewable and working by adding this (see below)

directly to the product description page. the buttons show up BUT... if i click add to cart it does not tell me what was chosen just that that certain product is in the cart. no size and or color.

i have uploaded the plugin you directed me to and tried removing what i had and adding your script (thank you again so very much for your help here id be lost without your site) but with that script i do not show the buttons at all.

i must be missing something and i hope maybe something stupid its been one of those days. what am i doing wrong? thank you again for everything you do for the shopfiy community

<notextile>
     <select name="Size">
     <option value="32A">32A</option>
     <option value="36C" selected="selected">36C</option>
     <option value="34D">34D</option>
   </select>
   <select name="color">
     <option value="White">White</option>
     <option value="Black" selected="selected">Black</option>
     <option value="Yellow">Yellow</option>
     <option value="brown">brown</option>
</notextile>

Plugin

but with that script i do not show the buttons at all.

Linking to the plugin is not sufficient. You still have to use the plugin.

It has its own documentation. I provide a link to the plugin's documentation page.

By the way, you don't have to use a plugin, and if you do you don't have to use that particular one.

hmm okay reading

the plugin info now. still unsure but i think (or hope) im getting this.. i come from the old days of ibm and assembly etc and a lot of this is new to me.. im just starting to learn all this new java stuff lol.. thanks again

good topic.

Actually I am write up a report on this article and it helped me with a mischievousness glance.