Tutorial

Thanks for checking out this project. It was for myself to learn more about the capabilities of Foxy and how it can handle a complex mix of products using the Webflow CMS.


Preview in Webflow: https://preview.webflow.com/preview/flexible-foxy-variant-test?preview=1042e288f8b3aea41b009fa184c92a0f

Published Site: http://flexible-foxy-variant-test.webflow.io

Showcase Page: https://webflow.com/website/Flexible-Foxy-Store

Forum Discussion: https://forum.webflow.com/t/ecommerce-tutorial-foxy-io-webflow-cms-clonable/62857

Official Foxy Website: https://foxy.io (affiliate link)

Project Details

What's the point of this project?

The goal of this project was to test the flexibility that Foxy provides when implemented using the Webflow CMS. Specifically how is it possible to mix'n'match a combination of variants and add-ons to a store of different types of products. Some of these big box ecommerce solutions either can't handle this challenge or charge higher plan pricing to do so.

Project Features

  • Products powered by Foxy and Webflow CMS (other tutorials and examples)
  • CMS custom fields for product images and categories (more fields possible than shown here)
  • Checkout and payments powered by Foxy
  • CMS plain text fields power the Foxy pricing, cart, and product info
  • Unused CMS fields are automatically hidden on the published site
  • Dynamic product price based on variant and add-ons selected

Other Great Features (not shown)

Terms Used

Instructions

Setup the product CMS Collection

Here I've create a new collection called Products and added the following basic fields:

  • Name
  • Image
  • Category(s)
  • Price

The image is the main product image for this project. You can add more image fields if needed. The categories are a multi-reference field that links to a separate Category collection. Since it's multi-reference a product can appear in multiple categories in the store. The price is a numerical value input. This number is your base price for a product prior to any variants or add-ons.

Each variant and add-on will need two fields:

  • Label
  • Code

The label is what the site visitor will see on the dropdown or checkbox fields. The code is what tells Foxy how to adjust the price, weight, product code, and more based on what the visitor chooses. For each variant or add-on, you will need to make sure and add both these fields. There's no limit to how many you can have. For this project example, I chose a maximum of 3 variants and 2 add-ons.

Note: a product can have up to the maximum number but is not required to have any or all variant/add-on options used. A script will remove unsused fields later.

Here is our 1st example product:

The product has an image, name, and price which can be seen in the CMS form below. The category is not shown here but is used for organizing products elsewhere. Those are the basic settings. We will get to the fun part in a minute.

Here is a 2nd example product:

As you can see it has a single variant applied and no other options. If we look at the CMS form below, it is possible to see the code that makes it possible. There is label called Amount which shows up in the cart followed by the selected variant. There are 4 variants listed by comma-separated-value or csv.

  • 1 Slice
  • 2 Slices
  • 3 Slices
  • Whole Pie

If it is necessary to adjust the price or other variable for a variant, that code is place inside of bracket {} after the name. In our example {p+2} adds $2 to the price of the 2 Slices variant. Following that logic, the following two variants are increased by $4 and $8. For a list of variable that can be adjusted see this article. NEED LINK!

Here is a 3rd example product:

As you can see it has two add-ons applied and no variants. If we look at the CMS form below, it is possible to see the code that makes it possible. There is label called Left-Hand Controller ($50) which shows up in the cart followed by the selected variant. Since this is a checkbox which is a yes/no option there is no csv. For the code to work here, you must enter Yes followed by the variables in brackets. The yes is what talks to the script that we will add later for when a checkbox is activated or selected. The ($50) is added to the label so that the customer knows what to expect. This is not necessary for the script to work and is purely for the customer experience.

Custom Code

Find the footer code input in the project settings.

Now copy+paste the following scripts into the box and click "save changes".

<!-- FOXYCART -->

<script data-cfasync="false" src="https://cdn.foxycart.com/foxy-store/loader.js" async defer></script>

<script>
// NOTE: This snippet doesn't support Foxy's multicurrency. For help with that, please contact Foxy.io support.
var currency_symbol = '$';
var modifier_text_summary = true;
var foxy_pattern=/^([^\{,]+)(\{((?:[pwcy][+:-][^\|\}]+\|?)+)\})?$/,modifier_pattern=/^p([+:-])([\d.]+)/;function convertSlugAsNeeded(a){return a.replace(/^\d/,function(a){return"\\"+a.charCodeAt(0).toString(16)+" "})}
for(var slug in options){var target=document.querySelectorAll("form."+convertSlugAsNeeded(slug))[0];if(target)for(var key in options[slug])if(""!=options[slug][key]){var select=target.querySelectorAll("select."+convertSlugAsNeeded(key))[0];if(select)for(options_arr=options[slug][key].split(","),i=0;i<options_arr.length;i++){var option=options_arr[i].match(foxy_pattern),modifiers=[];option[1]=option[1].trim();var option_text="";option[3]&&(modifiers=option[3].split("|"));if(modifiers)for(var j=0;j<
modifiers.length;j++){var price_modifier=modifiers[j].match(modifier_pattern);!price_modifier||"undefined"!==typeof modifier_text_summary&&!0!==modifier_text_summary||(option_text+=" (",option_text+=":"==price_modifier[1]?"":price_modifier[1],option_text+=currency_symbol+price_modifier[2],option_text+=")")}select.options[select.options.length]=new Option(option[1]+option_text,option[0])}}};
</script>

<script>
 // NOTE: This snippet hides variant fields that are empty
 $('.variant-container').each(function(i) {
   if ($(this).find("select").children('option').length == 0) {
    $(this).remove();
   } else if ($(this).find("select").children('option').length == 1) {
$(this).hide();
   }
 });
 
 $('.w-checkbox').each(function(i) {
     if ($(this).find("input").val() == '') {
     $(this).remove();
     }
 });
</script>

<script>
 // NOTE: This snippet is needed for displaying dynamic pricing
ADJUST={};var pricemod_regex=/[{\|]p([+\-:])([\d\.]+)(?:\D{3})?(?=[\|}])/,FC=FC||{};
FC.onLoad=function(){FC.client.on("ready.done",function(){$('form[action*="'+FC.settings.storedomain+'"]').each(function(){var b={base_price:parseFloat($(this).find("[name='price'],[name^='price||']").first().val()),quantity:1},d={},c=$(this).find("[name='quantity'],[name^='quantity||']"),g=!1,e=$(this).find("[name='code'],[name^='code||']");if(0!=e.length){var f=clearHash(e.first().val());$(this).find("input,select").each(function(){if("SELECT"==this.tagName){var b=!1;$(this).children("option").each(function(){-1<
this.value.search(pricemod_regex)&&(b=!0)});b&&($(this).data("fc-adjust-for",f),d[clearHash(this.name)]=clearHash(this.value),$(this).on("change",function(){ADJUST[$(this).data("fc-adjust-for")].attributes[clearHash(this.name)]=clearHash(this.value);recalcTotal()}))}else if(-1<this.value.search(pricemod_regex))switch($(this).data("fc-adjust-for",f),$(this).attr("type")){case "checkbox":$(this).is(":checked")?d[clearHash(this.name)]=clearHash(this.value):d[clearHash(this.name)]="";$(this).on("change",
function(){$(this).is(":checked")?ADJUST[$(this).data("fc-adjust-for")].attributes[clearHash(this.name)]=clearHash(this.value):ADJUST[$(this).data("fc-adjust-for")].attributes[clearHash(this.name)]="";recalcTotal()});break;case "radio":d.hasOwnProperty(clearHash(this.name))||(d[clearHash(this.name)]=""),$(this).is(":checked")&&(d[clearHash(this.name)]=clearHash(this.value)),$("[name='"+this.name+"']").data("fc-adjust-for",f).on("change",function(){ADJUST[$(this).data("fc-adjust-for")].attributes[clearHash(this.name)]=
clearHash(this.value);recalcTotal()})}});b.attributes=d;if(0<c.length){var e=0,h=getElementType(c);-1<["select","text"].indexOf(h)?(g=!0,e=parseFloat(clearHash(c.val()))):-1<["radio","checkbox"].indexOf(h)&&(g=!0,1==c.filter(":checked").length&&(e=parseFloat(clearHash(c.filter(":checked").val()))));if(g)c.data("fc-adjust-for",f).on("change",function(){var b=getElementType($(this)),c=0;if(-1<["select","text"].indexOf(b)||-1<["radio","checkbox"].indexOf(b)&&$(this).is(":checked"))c=parseFloat(clearHash(this.value));
isNaN(c)&&(c=0);ADJUST[$(this).data("fc-adjust-for")].quantity=c;recalcTotal()});isNaN(e)&&(e=0);b.quantity=e}if(!$.isEmptyObject(d)||g)ADJUST[f]=b}});recalcTotal()})};function clearHash(b){return b.replace(/\|\|[\d\w]+(?:\|\|open)?$/,"")}function getElementType(b){if("SELECT"==b[0].tagName)return"select";if("INPUT"==b[0].tagName)switch(b.attr("type").toLowerCase()){case "text":case "number":case "tel":return"text";default:return b.attr("type").toLowerCase()}}
function recalcTotal(){for(p in ADJUST){var b=ADJUST[p].base_price,d=0;for(a in ADJUST[p].attributes){var c=ADJUST[p].attributes[a].match(pricemod_regex);if(c)switch(c[1]){case ":":b=parseFloat(c[2]);break;case "+":d+=parseFloat(c[2]);break;case "-":d-=parseFloat(c[2])}}b+=d;b*=ADJUST[p].quantity;b="object"==typeof FC&&FC.hasOwnProperty("json")&&FC.json.config.hasOwnProperty("currency_format")?jQuery.trim(FC.util.money_format(FC.json.config.currency_format,b)):b.formatMoney(2);$("."+p+"_total").html(b)}}
Number.prototype.formatMoney=function(b,d,c){var g=this;b=isNaN(b=Math.abs(b))?2:b;d=void 0==d?".":d;c=void 0==c?",":c;var e=0>g?"-":"",f=parseInt(g=Math.abs(+g||0).toFixed(b))+"",h=3<(h=f.length)?h%3:0;return e+(h?f.substr(0,h)+c:"")+f.substr(h).replace(/(\d{3})(?=\d)/g,"$1"+c)+(b?d+Math.abs(g-f).toFixed(b).slice(2):"")};
</script>

<!-- /FOXYCART -->

Here's a brief description of what each script does.

  1. This script loads the javascript for your Foxy store. Note that the source url should be replaced with your store's url.
  2. This script tells the product embeds how to interpret the data provided from the CMS data fields.
  3. This script removes any variant options or add-on checkboxes that are empty in the CMS fields.
  4. This script allows for the price to dynamically update as the visitor adjust the products options.

Product Embed

It is possible to use the following code block on either the product CMS collection template page or inside of a collection list on any page (like the homepage).

  1. Add a custom html embed element.
  2. Paste in the code below.
  3. Edit code based on your project. Code in BOLD and CAPS should be dynamically linked to your project's equivelant product CMS fields. The form action url needs to be replaced with your store's url and make sure to have the "/cart" at the end.

<script>
 var options = options || {};
 options["SLUG"] = {};
 options["SLUG"]["var1"] = "VARIANT 1 - CODE";
 options["SLUG"]["var2"] = "VARIANT 2 - CODE";
 options["SLUG"]["var3"] = "VARIANT 3 - CODE";
</script>

<form action="https://foxy-store.foxycart.com/cart" method="post" accept-charset="utf-8" class="SLUG">
 <input type="hidden" name="code" value="SLUG" />
 <input type="hidden" name="name" value="NAME">
 <input type="hidden" name="price" value="PRICE">
 <input type="hidden" name="image" value="IMAGE">
 <div>
 <h2>NAME</h2>
 <h4><span class="SLUG_total">$PRICE</span></h4>
 <div class="variant-container">
   <label class="w-label">VARIANT 1 - LABEL</label>
    <select name="VARIANT 1 - LABEL" class="var1 w-select" required=""></select></div>
   <div class="variant-container">
    <label class="w-label">VARIANT 2 - LABEL</label>
     <select name="VARIANT 2 - LABEL" class="var2 w-select" required=""></select></div>
   <div class="variant-container">
    <label class="w-label">VARIANT 3 - LABEL</label>
    <select name="VARIANT 3 - LABEL" class="var3 w-select"></select></div>
   <div class="w-checkbox variant-containter">
    <input type="checkbox" id="ADD-ON 1 - LABEL" name="ADD-ON 1 - LABEL" class="w-checkbox-input" value="ADD-ON 1 - CODE">
     <label for="ADD-ON 1 - LABEL" class="w-form-label">ADD-ON 1 - LABEL</label></div>
   <div class="w-checkbox variant-containter">
    <input type="checkbox" id="ADD-ON 2 - LABEL" name="ADD-ON 2 - LABEL" class="w-checkbox-input" value="ADD-ON 2 - CODE">
     <label for="ADD-ON 2 - LABEL" class="w-form-label">ADD-ON 2 - LABEL</label></div>
</div>
<div>
   <label class="w-label">Quantity</label>
   <input class="w-input" name="quantity" type="number" min="1" value="1">
 </div>
 <button class="w-button" type="submit">Add To Cart</button>
</form>

Once you've added the code into the embed editor and placed all of the fields from the CMS, it should look like this.

In the script, the first option uses the unique product slug or url to connect to the Foxy scripts. The 2nd, 3rd, and 4th options connect each variant selector to the code input into the CMS. The class name can be whatever you choose as long as it matches in both instances for that variant. Do not use a class name that is used elsewhere on the site or else there could be unexpected styling implemented by the combo class.

To add/remove variants to match the # of variants used in the project CMS, simple copy/delete this single line:\

options["SLUG"]["var1"] = "VARIANT 1 - CODE";

Make sure to adjust the class name and linked code field so that each variant is unique.

The next part of the embed begins the form. Again make sure the action url matches your Foxy store. The CMS fields should be connect as seen in this example.

You can replace the H2 and H4 tags to whatever your project calls for whether it is different heading tags or specific class names.

Now to the variant selectors. Each variant is inside of a div with the class name of variant-container. Changing the selector's class name will unlink the script in the project settings that dynamically hides empty variants. Remember that the class name on the select element links the selector to the appropriate code in the script above.

To add/remove variants to match the # of variants used in the project CMS, simple copy/delete these three lines:

<div class="variant-container">
<label class="w-label">VARIANT 1 - LABEL</label>
<select name="VARIANT 1 - LABEL" class="var1 w-select" required=""></select></div>

Make sure to adjust the class name and linked code field so that each variant is unique and matches the script option.

Now to the add-on checkboxes. Each variant is inside of a div with the class name of variant-container. Make sure to connect the value to the CMS code field and not the label.

To add/remove add-on checkboxes to match the # of add-ons used in the project CMS, simple copy/delete these three lines:

<div class="w-checkbox variant-containter">
<input type="checkbox" id="ADD-ON 2 - LABEL" name="ADD-ON 2 - LABEL" class="w-checkbox-input" value="ADD-ON 2 - CODE">
<label for="ADD-ON 2 - LABEL" class="w-form-label">ADD-ON 2 - LABEL</label></div>

Conclusion and Contact

Thanks for reading this tutorial. I hope you've found it helpful. If you have any questions or suggestions please reach out to me on the forum post. Foxy.io is also on the forum.

If you are signing up for either Webflow or Foxy and enjoyed this site/tutorial, I'd be grateful if you use my affiliate links. I don't do this for the clicks, but because these two products are great tools that work very well together.

Happy designing!

-Matthew

Website - Webflow Showcase - Project Showcase