Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

This page will show how to customize item variations and .  If you need item options within instead, see this article: Modifying Item Options in a StoreFront.

Table of Contents

StoreFront Item Variations

...

Code Block
languagexml
linenumberstrue
#set($hasVariations = $item.getVariations().size() > 0)
#if($hasVariations)
  #foreach($variation in $item.getVariations())
	<label class="label-group"><span>${variation.getName()}</span>
	  <input type='hidden' name="VariationName${velocityCount}" value="${variation.getName()}"/>
	  <select name="VariationValue${velocityCount}" id="uc-variation-field-${velocityCount}-${item.getMerchantItemID()}" class='variationSelectBox'>
	  </select>
	</label>
  #end
#end

 

Item Options Form Elements

...

languagexml

...

This code is part of the much larger <form> block within the template_item.vm file

Warning
The naming of the select boxes is crucial.  They must have the proper id or the variations will not work.  Do not deviate from that id naming structure for any reason.

 

Javascript Component

Code Block
<script type="text/javascript" src="/catalog_5.1.js"></script>
 
<!-- there will be a lot of other html components in between the catalog_5.1.js reference and the actual item variation code ... just FYI -->
 
 
#if($item && $item.hasVariations() && "$cart" != "")
  <script type='text/javascript'>
  //this is dependent on jquery and catalog_5.1.js
  console.log('initializing catalog variations');
  #set($currencyCode = $cart.getCurrency().getSymbol())
  // this can be named anything or assigned anywhere.  Or not even assigned to a variable.
  // I just chose window.itemVariations so it would be easy to debug
  var itemVariationsOptions = {
    merchantItemId: "$item.getMerchantItemID()",
    variationMatrix: ${item.getVariationMatrix().getJavascriptItemVariationMatrix2(false, 80, 80)},
    multimedia: ${item.getMultimediaAsJson()},
    currencyCode: "$currencyCode",
    inventory: 'show-quantity',
    validation: function(missingFields){ 
      window.alert("Look jackball, select values for the following fields before continuing: #foreach" ($dropDownValue in $itemOption.getDropDownValues())+ missingFields.join(", ")); 
      return false; 
    },
    customOutOfStock: {'X-Large': 'out of <option value="$formatHelper.escapeHtml($dropDownValue)">$formatHelper.escapeHtml($dropDownValue)</option>season'},
    formatters: {
      'Color': function(formatData){
        #endvar priceIsRanged = (formatData.cost != formatData.maxCost);
        var costStr = " "  </select>+ formatData.costFormatted;
        if(priceIsRanged){
     #end     costStr = " [" + formatData.costFormatted + " - " + #if($itemOption.getType() == "single")formatData.maxCostFormatted + "]";
        }
        <label for="option-single${velocityCount}">$itemOption.getLabel()</label> if(formatData.inventory > 0){
           return formatData.optionText + costStr;
      <input  type="hidden" name="OptionName${velocityCount}" value="$formatHelper.escapeHtml($itemOption.getName())" />
 } else if(formatData.status == 'backorder') {
           return formatData.optionText + costStr + <input" class="uc-option-field" data-item-id="$item.getMerchantItemID()" data-option-name="$itemOption.getName()" id="option-single${velocityCount}" type="text" name="OptionValue${velocityCount}"  />(backorder" + (formatData.eta ? (" until " + formatData.eta + ")") : ")");
         } else if(formatData.status == 'made to order') #end{
           return formatData.optionText  #if($itemOption.getType() == "multiline")
                <label for="option-multiline${velocityCount}">$itemOption.getLabel()</label>+ costStr + " (made to order" + (formatData.leadTime ? (" in " + formatData.leadTime + " days)") : ")");
         } else if(formatData.status == 'preorder') {
   <input type="hidden" name="OptionName${velocityCount}" value="$formatHelper.escapeHtml($itemOption.getName())" />    return formatData.optionText + costStr + " (preorder" + (formatData.eta ? (", ships " + formatData.eta <textarea+ class="uc-option-field" data-item-id="$item.getMerchantItemID()" data-option-name="$itemOption.getName()" id="option-multiline${velocityCount}" type="text" name="OptionValue${velocityCount}"></textarea>")") : ")");
         } else {
     #end      return formatData.optionText + costStr + " (" +  #if($itemOption.getType() == "radio")formatData.status + ")";
        } //end-if inventory/status selections
    <label>$itemOption.getLabel()</label>  }, //end of 'Color' formatter
      'Size': function(formatData){
  <br>      var priceIsRanged = (formatData.cost != formatData.maxCost);
      <ul  var costStr class='option-group'> " " + formatData.costFormatted;
        if(priceIsRanged){
      #foreach($radioValues   in $itemOption.getValues())
             costStr = " [" + formatData.costFormatted + " - " + formatData.maxCostFormatted + "]";
       <li> }
         if(formatData.inventory > 0){
         <label for="option-radio${velocityCount}">
    return formatData.optionText + costStr;
         } else if(formatData.status == 'backorder') {
           return formatData.optionText + <input type="hidden" name="OptionName${velocityCount}" value="$formatHelper.escapeHtml($itemOption.getName())" />
          costStr + " (backorder" + (formatData.eta ? (" until " + formatData.eta + ")") : ")");
         }  <input class="uc-option-field" data-item-id="$item.getMerchantItemID()" data-option-name="$itemOption.getName()" type="radio" name="OptionValue${velocityCount}" value="$formatHelper.escapeHtml($radioValues.value)"  />$formatHelper.escapeHtml($radioValues.value)</label>
else if(formatData.status == 'made to order') {
           return formatData.optionText + costStr + " (made to </li>order" + (formatData.leadTime ? (" in " + formatData.leadTime + " days)") : ")");
     #end    } else if(formatData.status == 'preorder') {
       </ul>    return formatData.optionText + costStr + " (preorder" + (formatData.eta ? (", #endships " + formatData.eta + ")") : ")");
        #if($itemOption.getType() == "file attachment") } else {
           return formatData.optionText + costStr + <label" for="option-file${velocityCount}">$itemOption.getLabel()</label>(" + formatData.status + ")";
        } //end-if inventory/status selections
      } //end of 'Size' formatter        <input class="uc-option-field" data-item-id="$item.getMerchantItemID()" data-option-name="$itemOption.getName()" id="option-file${velocityCount}"
type="file" name="$formatHelper.escapeHtml($itemOption.getName())"  /> } //end of formatters
  }; //end of item variation options hash.
 #end window.itemVariations = new ultracart.ItemVariations(jQuery, itemVariationsOptions);
    window.itemVariations.on('options-changed', function(event,  </div>
   new_options){ console.log('options-change event triggered', new_options); });
    window.itemVariations.on('cost-changed',  #end

   function(event, cost_data){ console.log('cost-change event triggered', cost_data); });
    #end

 

 

This code is part of the much larger <form> block within the template_item.vm file

Warning
The naming of the select boxes is crucial.  They must have the proper id or the variations will not work.  Do not deviate from that id naming structure for any reason.

 

Javascript Component

Code Block
<script type="text/javascript" src="/catalog_5.0.js"></script>
 
<!-- there will be a lot of other html components in between the catalog_5.0.js reference and the actual item variation code ... just FYI -->
 
 
#if($item && $item.hasVariations() && "$cart" != "")
<script type='text/javascript'>
   #set($currencyCode = $cart.getCurrency().getSymbol())
 
   // ========================================================
   // You will customize the options in this section
   // ========================================================
	var itemVariationsOptions = {
		inventory: 'show-quantity',
		validation: function(missingFields){ 
		  window.alert("Look here man, select some values for the following fields before continuing: " + missingFields.join(", ")); 
		  return false; 
		},
	  customOutOfStock: {'X-Large': 'out of season'},
	  formatters: {
		'Color': function(formatData){
		  var priceIsRanged = (formatData.cost != formatData.maxCost);
		  var costStr = " " + formatData.costFormatted;
		  if(priceIsRanged){
			costStr = " [" + formatData.costFormatted + " - " + formatData.maxCostFormatted + "]";
		  }
		  
		   if(formatData.inventory > 0){
			 return formatData.optionText + costStr;
		   } else if(formatData.status == 'backorder') {
			 return formatData.optionText + costStr + " (backorder" + (formatData.eta ? (" until " + formatData.eta + ")") : ")");
		   } else if(formatData.status == 'made to order') {
			 return formatData.optionText + costStr + " (made to order" + (formatData.leadTime ? (" in " + formatData.leadTime + " days)") : ")");
		   } else if(formatData.status == 'preorder') {
			 return formatData.optionText + costStr + " (preorder" + (formatData.eta ? (", ships " + formatData.eta + ")") : ")");
		   } else {
			 return formatData.optionText + costStr + " (" + formatData.status + ")";
		  } //end-if inventory/status selections
		}, //end of 'Color' formatter
		'Size': function(formatData){
		  var priceIsRanged = (formatData.cost != formatData.maxCost);
		  var costStr = " " + formatData.costFormatted;
		  if(priceIsRanged){
			costStr = " [" + formatData.costFormatted + " - " + formatData.maxCostFormatted + "]";
		  }
		  
		   if(formatData.inventory > 0){
			 return formatData.optionText + costStr;
		   } else if(formatData.status == 'backorder') {
			 return formatData.optionText + costStr + " (backorder" + (formatData.eta ? (" until " + formatData.eta + ")") : ")");
		   } else if(formatData.status == 'made to order') {
			 return formatData.optionText + costStr + " (made to order" + (formatData.leadTime ? (" in " + formatData.leadTime + " days)") : ")");
		   } else if(formatData.status == 'preorder') {
			 return formatData.optionText + costStr + " (preorder" + (formatData.eta ? (", ships " + formatData.eta + ")") : ")");
		   } else {
			 return formatData.optionText + costStr + " (" + formatData.status + ")";
		  } //end-if inventory/status selections
		} //end of 'Size' formatter            
	  } //end of formatters
	}; //end of item variation options hash.
 
 
 
 
    // this can be named anything or assigned anywhere.  Or not even assigned to a variable.
    // I just chose window.itemVariations so it would be easy to debug
   // ==================================================================
   // It is extremely rare that you will ever change this block of code.   
   // UltraCart recommends that you leave the following lines alone.
   // ==================================================================
	window.itemVariations = new ultracart.ItemVariations(jQuery, "$item.getMerchantItemID()",
        ${item.getVariationMatrix().getJavascriptItemVariationMatrix2(false, 80, 80)},window.itemVariations.on('item-selected', function(event, item_data){ console.log('item-selected event triggered', item_data); });
    window.itemVariations.on('item-unselected', function(event){ console.log('item-unselected event triggered (no extra data provided)'); });                      
    window.itemVariations.on('multimedia-selected', function(event, multimediaOid){ 
      console.log('multimedia-selected event triggered, multimediaOid:', multimediaOid); 
      var multimediaImageIndex = parseInt(jQuery("li.product-image[data-multimedia-oid='" + multimediaOid + "']").attr('index'));
      if(!isNaN(multimediaImageIndex)){
        "$currencyCode", itemVariationsOptions);


 
 
   // ==================================================================
   // Attach your event handlers here.
   // ==================================================================				 jQuery('#main-item-image-viewer-holder').slickGoTo(multimediaImageIndex);
      }                           
    });
    window.itemVariations.on('optionsmultimedia-changeunselected', function(event, new_options){             
      console.log('optionsmultimedia-changeunselected event triggered', new_options); });
    window.itemVariations.on('cost-change', function(event, cost_data){ console.log('cost-change event triggered', cost_data); });
    window.itemVariations.on('item-selected', function(event, item_data){ console.log('item-selected event triggered', item_data); (no extra data provided)');            
      // revert back to the original image, which is always at index 0.
      jQuery('#main-item-image-viewer-holder').slickGoTo(0);
    });     window.itemVariations.on('item-unselected', function(event){ console.log('item-unselected event triggered (no extra data provided)'); });                      
</script>
#end     
  </script>
#elseif($item && $item.hasOptions() && "$cart" != "")

   ## THIS WILL CONTAIN A LARGE CODE BLOCK FOR ITEM OPTIONS.  IT WAS REMOVED TO AVOID CONFUSION IN THIS DOCUMENTATION.

#end



 Options

inventory : 'show-quantity' | 'hide-out-of-stock'

show-quantity will display the quantity of inventory next to the option

hide-out-of-stock will remove any options that have an inventory less than or equal to 0.  this option will also exclude backorder and preorder items.

For find tune control, use 'show-quantity' and use custom formatters to render your options.

...

This event fires whenever an variation changes options (customer selects a different value).  

data:  

/**
* @typedef {Object} VariationEventOptionsChange
* this event will contain a hash of all options and their currently selected values. any unselected values will have
* a zero length string.
*/

 

Tip

The variations event above has a plural options, while the item options event has a singular option. Please note the difference.

 

 

cost-changed

This event fires whenever the minimum cost changes.

/**
* @typedef {Object} VariationEventCostChange
* @property {number} cost
* @property {string} costFormatted
* @property {number} originalCost
* @property {string} originalCostFormatted
* @property {number} saleCost
* @property {string} saleCostFormatted
* @property {boolean} hasSale true if these combination of items is on sale.
* @property {boolean} hasSelectedItem true if a single item is selected, else false
* @property {string} itemId will contain the item id of the item if it's selected, else null.
* @property {number} parentCost
* @property {string} parentCostFormatted
* @property {number} parentOriginalCost
* @property {string} parentOriginalCostFormatted
* @property {number} parentSaleCost
* @property {string} parentSaleCostFormatted
* @property {boolean} parentHasSale true if these combination of items is on sale.
* this event contains all relevant price data.
*/

 

item-selected

This event fires whenever enough variations have been selected to narrow down the possible choice of child items to one.  This is very useful for changing the main item image to the child item.

/**
* @typedef {Object} Variation This object would be better termed 'VariationItem', since each record represents a child variation of the top level item
* @property {string} itemId the merchant item id
* @property {number} cost The current cost of the variation item. Example 19.99
* @property {number} originalCost The original cost of this variation. Example: 29.99
* @property {number} [saleCost] The sale cost of this variation. Example: 29.99. Will be absent entirely if item is not on sale
* @property {boolean} hasSale true if this variation is on sale, false otherwise
* @property {number} inventory the inventory of this variation. if inventory is not tracked, this will be 1000
* @property {boolean} inventoryTracked true if inventory is tracked on this item
* @property {boolean} allowBackOrder true if this item allows backOrder
* @property {boolean} preorder true if this item is a preorder item
* @property {string} eta date string of the estimate time of arrival for backOrder or preorder
* @property {boolean} madeToOrder true if this item is made to order
* @property {integer} [leadTime] number of days required to make this item, will be missing if madeToOrder = false
* @property {Object} values an object of option values that match this variation. For example: Object { Size="Large", Color="Black"}
* @property {string} compositeKey a pipe delimited list of values in 'sort' order to allow for easy option value -> variation lookup.
*/

item-unselected

This event fires whenever the variations change and a single item is no longer selected.  For example, if the customers starts over with the first select box again, this will fire.  This is useful for reverting any image or description information back to the main parent item.

...

This event fires whenever a multimedia (image) is applicable to the currently selected variations.  This can occur based on a selection from one of the select boxes (or radio buttons), or when enough selections are made to narrow down to a single child item which has an assigned multimedia image.

/**
* @typedef {Object} Multimedia contains high level information about one of the multimedia objects associated with the parent item
* @property {string} type
* @property {boolean} isDefault
* @property {number} imageWidth
* @property {number} imageHeight
* @property {string} viewUrl
* @property {string} viewSsl
* @property {string} description
* @property {string} code
* @property {string} merchantItemId
* @property {boolean} excludeFromGallery
* @property {string} filename
* @property {number} multimediaOid
*/

multimedia-unselected

This event fires whenever the customer has changed their selections (or cleared them out) to the point where no images can be found to match the current set of variation choices.

This event does not have any extra data associated with it.

 Item Options Events