Streaming live at 10am (PST)

[TUTORIAL] Setup multistep composite products and bypass variants limitations

I wished to create an e-commerce with Webflow for a while. I thought I found the perfect fit with this small luxury accessories client.

Quite soon I realized I needed to face two big limitations:

  • Multistep Composite Products: The customer wanted a multistep process to mimic the creation of your very own accessory with custom pickers for product variants.
  • Maximum Products Variants (50): Some products had 3 color choices, 2 styling options and the possibility to have a custom letter on it. This would have created 3 * 2 * 24 = 144 variants!

I’ve solved both issues with custom code, a lot of patience and a bunch of trial and error.

The forum helped with some specific aspects but there is nothing that embraces those points from A to Z, this is why I wanted to create this post, hoping it will help those who are in the same struggle I was a couple of months ago.


Stola and Collo are the two products taking full advantage from both functionalities.

Disclaimer

  • This won’t be a guide step by step and I won’t go into too much detail otherwise the size of the article will be too big.
  • A basic/medium understanding of javascript is required in order to perform those functionalities.
  • Mine is just one way (the first one I came up with) to implement those functionalities. It has some limitations and for sure it can be improved (it would be great to have some help).

Multistep Composite Products

About the cart box
The “Add to cart” Webflow element and variants selects inside it are hidden.
The cart element will be shown only at the end of the customization process (using jQuery) because the “Add to cart” button has to be clicked in order to actually add the product in the cart with Webflow.

How to store the different variants
With jQuery I find and store those “selects” that will be modified later dynamically with custom pickers.

// The cart box, let's select it by ID (given to the element in Webflow "Element Settings")
const cartBox = $("#mc-cart-box");
// We take all the "select" elements in the cart, those elements are in charge of variants
const selectEl = cartBox.find("select");

// selectDetails variable store all the possible options of each variant
// It's important to know in advance the variant's order and number and the option's order and number

// Void is an utility option I have created for each variant.
// It's great when you want to show for example a specific image for a product before the user choose any color.
// So the default product variant in Webflow is set as color: void, passamaneria: void, cammeo: void.
window.selectDetails = {
    color: {
      choices: {
        beige: selectEl.eq(0).children().eq(1).val(),
        nero: selectEl.eq(0).children().eq(2).val(),
        rosso: selectEl.eq(0).children().eq(3).val(),
        void: selectEl.eq(0).children().eq(4).val(),
      },
      id: selectEl[0].id
    },
    passamaneria: {
      choices: {
        colore1: selectEl.eq(1).children().eq(1).val(),
        colore2: selectEl.eq(1).children().eq(2).val(),
        void: selectEl.eq(1).children().eq(3).val(),
      },
      id: selectEl[1].id
    },
    cammeo: {
      choices: {
        si: selectEl.eq(2).children().eq(1).val(),
        no: selectEl.eq(2).children().eq(2).val(),
        void: selectEl.eq(2).children().eq(3).val(),
      },
      id: selectEl[2].id
    },
  };

So now we have all the variants stored in a variable.

Let’s see the custom HTML code I’ve used in Webflow for my pickers:

<!-- Custom Color choice -->
<div class="choices-container" id="color-choice">
    <span onclick="modifySelectValue('color', 'beige', this);" class="mc-choice beige mc-choice-sel" choice="beige"></span>
    <span onclick="modifySelectValue('color', 'nero', this);" class="mc-choice nero" choice="nero"></span>
    <span onclick="modifySelectValue('color', 'rosso', this);" class="mc-choice rosso" choice="rosso"></span>
</div>

As you can see, we call the function modifySelectValue() with some hard coded values as parameters. This function acts as a middleman since its main scope is to update the picker style by adding or removing css classes. Then it will pass the parameters to updateAll() function.

updateAll() is the one that really takes care of selecting the variant in the cart and triggers the event that Webflow will recognize in order to update dynamically product images, price, availability and all the other values that you can find at the variant level in webflow.

For this function I have to thank @samliew who found this super solution to webflow not recognizing that the select changed it’s values. You can take a look at his original solution in this post.

The original function worked perfectly for products having only one option set. For products having more than one option set it recently started not to update correctly the fields after the first one. This is due to Webflow changing the way it updates the variant choice (probably before it updated only the one that really changed, now it updates each time all of them).

So I’ve modified the function and now it works perfectly even with more than one option set.

// Modified version to update variants correctly
function updateAll(selectName, newValue) {
    // We update the value a first time for single-variants products
    $("#" + selectDetails[selectName].id)
            .val(selectDetails[selectName].choices[newValue])
            .change();

    s = document.getElementsByTagName("select");
    for (let i = 0; i < s.length; i++) {
      setTimeout(
        (el) => {
          // Here I've changed the way the event is triggered
          // The function used before was deprecated and may be dangerous to use in the future
          const evt = new Event("change", {"bubbles":false, "cancelable":true});
          el.dispatchEvent(evt);
        
          // Each time we trigger the "change" event we modify again the value of the select 
          // that we are wishing to update
          $("#" + selectDetails[selectName].id)
            .val(selectDetails[selectName].choices[newValue])
            .change();
        },
        i * 10,
        s[i]
      );
    }
}

With those functions in place it’s possible to create really rich customization experiences for the users, now it’s time to see how to solve the second big issue.

Bypass Maximum Products Variants (50)

In a lot of situations 50 variants are just not enough, hopefully I’ve found a technique that works quite well for multiple purposes, still there are some cases where I strongly suggest not to use it as it is.

For example if you expect users to buy more than one identical product at the time with this “fake variants feature” it can create tricky behaviours.

Other case scenarios are when for each variant you need a different price, availability, size etc, since for having those you will still need the actual Webflow options in place.

The basic idea is quite simple: We let the user choose a letter on the product page, we store this choice in a cookie. In the checkout page we read the cookie, take the choice and put it in the additional info textarea. In my implementation the textarea is hidden on checkout so that your user won’t be able to edit it.

Note that with this particular trick also the customer will see his choice in the recap emails under “additional info”.

Here I want to thank @Finsweet since I’ve started with their Webflow hack #10 to familiarize with cookie management.

On the product page

I used the open source library js-cookie adding the following script:
<script src="https://pagecdn.io/lib/js-cookie/v2.2.1/js.cookie.js" crossorigin="anonymous"></script>

Then when the pick changes we create the cookie and save the user choice:

// The Cammeo is the one that can have a letter on it
$("#cammeo").on("change", function () {

    // CammeoSel stores the initial value
    // Useful to check for changes
    if (cammeoSel != this.value) {

        if (productType == "stola") {
            // This is where magic happens
            // We create a cookie, giving it a name, a value and an expiration (here 1 day)
            Cookies.set("liono-cammeo-stola", this.value, { expires: 1 });

            // As actual Webflow options we do have 3 for cammeo: yes, no and void
            // This helps us in this case to dynamically change the price of the product
            // since without cammeo the product is a little cheaper
            if (this.value == "none" || this.value == "nessuno") {
                modifySelectValue("cammeo", "no");
            } else {
                modifySelectValue("cammeo", "si");
            }
        }
        
        else if (productType == "collo") {
            Cookies.set("liono-cammeo-collo", this.value, { expires: 1 });
            modifySelectValue("cammeo", "si");
        }

        cammeoSel = this.value;
    }

});

On the checkout page we have to add the same script:
<script src="https://pagecdn.io/lib/js-cookie/v2.2.1/js.cookie.js" crossorigin="anonymous"></script>

Then we check if the cookie is there, we take the value and we add it in the additional info textarea:

// We check if the user choose a specific cammeo for the product stola
if (Cookies.get('liono-cammeo-stola')) {
    // We add the choice to the notes we will insert in additional info
    noteOrdine += ' Cammeo Selezionato per Stola: ' + Cookies.get('liono-cammeo-stola');

    // Optional (see below)
    cammeoSet('stola', 'stola');
}

// We check if the user choose a specific cammeo for the product collo
if (Cookies.get('liono-cammeo-collo')) {
    noteOrdine += ' Cammeo Selezionato per Collo: ' + Cookies.get('liono-cammeo-collo');

    // Optional (see below)
    cammeoSet('collo', 'collo');
}

if (noteOrdine != '') {
    // We add those choices to additional info
    // The id mc-checkout-notes was added to the textarea previously in Webflow
    $('textarea#mc-checkout-notes').val(noteOrdine);
}

// Changes the value in the product details at the end of the checkout page
// It's completely optional, setTimeout is used because this part of content is 
// generated asynchronously by webflow
function cammeoSet(product_code, product_type) {
    setTimeout(function () {
        let cammeo_container = $(".codice-prodotto:contains('" + product_code + "')").parent();
        cammeo_container.find(".valore_variante:contains('si')").text(Cookies.get('liono-cammeo-' + product_type));
    }, 2000);
}

Here we are, now we can ideally add as much variables and options as we want, just by repeating those steps and adding more cookie names and cases to our little script.

Conclusion

The code is not as clean and stylish as I wished it to be (mainly for time constraints), nevertheless the result is more than satisfactory and it fulfilled the project’s needs.

Those are for sure not easy, ready to run solutions and in a no-code community it may seems a little bit out of place, still these two techniques will help you generate richer and more customizable user experiences in your e-commerces.

I also hope it will help to open Webflow to new use cases were it wasn’t even taken into consideration due to those limitations.

4 Likes