How to create facet widget from Shopify metafield?

shopify
widget
facets
#1

How do we customize the look of a facet? Can we get code for a working example (ideally in Shopify which uses metafields)?

Example problem : Diamond Shape & Diamond Color are two widgets I’d like to make.

  1. For Diamond Shape, how can we add a custom class to the element (which would be the metafield value turned into a handle). ie: Value of “Round Brilliant” => which would allow me to then target and replace the label with CSS background images.

  2. For Diamond Color, the widget would assign numerical values to the letters, so we can plot the values on the slider. Then, we can apply labels to the numerical slider values using the original letters.

#2

This is definitely doable!
I’ve tried doing it on my side and realized it was a bit complex, so I’ve simplified the process.

If you haven’t changed anything else yet, I’d ask you to reinstall Algolia in your theme.
Once this is done, you can try again at the location of your test in assets/algolia_instant_search.js.liquid.

First you need to add your attribute to the attributesForFaceting setting of your index. An easy way to do this is to change the facet’s type to hidden by clicking Edit next to the facet name in the Search Options tab.

Then, the documentation for the widgets can be found here: https://community.algolia.com/instantsearch.js/documentation/#refinementlist .

In my test, I’ve used a color field. Here is its code:

    var colorCssClasses = JSON.parse(JSON.stringify(algolia.facetCssClasses)); // Clone the classes object
    // You can modify colorCssClasses if you want here
    instant.search.addWidget(
      instantsearch.widgets.refinementList({
        container: '[class~="ais-facet-options.color"]',
        attributeName: 'options.color',
        operator: 'and',
        limit: 12,
        cssClasses: colorCssClasses,
        templates: {
          header: function () { return 'Color'; },
          item: function (facet) {
            // console.log(facet); // Uncomment to see what are the availables info
            var color = facet.name;
            var refinedStyle = '';
            if (facet.isRefined) {
              refinedStyle = "border: 2px solid #ccc;"
            }
            return '<div style="background: ' + color + '; width: 40px; height: 40px; ' + refinedStyle + '"></div>';
          }
        }
      })
    );

And here is how it displays:

Instead of options.color, you’ll want to use meta.c_f.[p]diamond-clarity. Because it contains . and [], to use it with CSS, you’ll have to use [class~="ais-facet-meta.c_f.[p]diamond-clarity"].

Then, making it fit how you want it to work should just be a matter of some JavaScript and CSS code.

Also, if you want to add it to the currentRefinedValues widget at the top of the page, you’ll need to manually add it to the list of attributes to check:

    // Current refined values
    var attributes = _.map(instant.facets.shown, function (facet) {
      return {
        name: facet.name,
        label: facet.title
      }
    });
    attributes = attributes.concat([{ name: 'options.color', label: 'Color' }]);

I think the slider will probably be more complex to build, and I would recommend you to stick with a single line but without sliding capabilities, just being able to pick which options you want with the operator: "or" param of the refinement list.

1 Like
Shopify Supply Theme - Tag Groups
#3

Thank you @Jerska!

Two questions (actually 1, figured out one of them!):

Question 2)

Do you know why the instantsearch.js URL doesn’t work when a metafield value is selected which uses brackets [ ] in the name?:

[This works] but [Selecting a metafield returns 0 search results when loaded in a new browser tab.]
Do you know why it’s breaking? [solved in next two replies]


[solved] original Question 1) Can I have multiple selections (shows all selected options)?
ie: Show items with “yellow” (5 products) and show items with “black” (10 products) = total of 15 products .

Solution:
Step 1: In Algolia dashboard, change the facet type from “conjunctive” to “disjunctive”
Step 2: In algolia_instant_search.js.liquid change the operator to “OR”

OR: must match any of the combined conditions (disjunction)

#4

I thought we were going to be able to avoid this, but it seems like we can’t.
My guess is that the issue here is the square brackets [] you’re using in your metafield name. My assumption is that our url parser won’t correctly handle those.

Do you think it would be possible for you to remove those brackets from your metafield name? I know this isn’t ideal, but that’s unfortunately my best solution right now. :confused:

#5

You were right. It was a super laborious process to remove the square brackets, but it is done now!

#If anyone else got stuck using the ShopifyFD plugin syntax with the incompatible [brackets] here is the fix:

  1. We need to rename all the metafield keys, and assign all the original values to the renamed keys. There’s no out of the box free way to do it, so: Install paid version of plugin: “Excel-like Spreadsheet Bulk Product Manager”, exporting all metafields as excel file. Renaming all the metafield keys on desktop
    (ie. meta.c_f.[p]diamond-cut >> meta.c_f.diamond-cut), also renaming all the metafield keys in the browser app columns. That way when you reupload and sync the excel spreadsheet back to database, it will sync the new data column to the modified column title. Said another way, syncing will now duplicate all the original metafield values into the newly named keys.

  2. Go into ShopifyFD settings under settings > general > (load the browser plugin on the general page). Create all the newly titled metafield keys (i.e. meta.c_f.diamond-cut), and delete all the old metafield keys. Check that your values have transferred over.

Now the instantsearch.js url will work when the metafield values are selected.

1 Like
#6

Hi @Jerska! I’d like to pursue how to create a custom rangeSlider.

For Diamond Color, where could I convert the following metafield values (letters) to these assigned number values? That way, we can plot on the slider. Would this be done as part of helper JS?
K = 0
J = 1
I = 2
H = 3
G-H = 3.5
G = 4
F-G = 4.5
F = 5
E = 6
D = 7

I tried the below, but rangeSlider doesn’t have the transformData parameter :frowning:

// Diamond Color
var diamondColorCssClasses = JSON.parse(JSON.stringify(algolia.facetCssClasses)); // Clone the classes object
// You can modify diamondCutCssClasses if you want here
instant.search.addWidget(
  instantsearch.widgets.rangeSlider({
    container: '[class~="ais-facet-meta.c_f.diamond-color"]',
    attributeName: 'meta.c_f.diamond-color',
    cssClasses: diamondColorCssClasses,
    step: .5,
    transformData: function(item) {
      console.log(item);
      if(item.name == "K"){
        item.name = 0;
      }else if(item.name == "J"){
        item.name = 1;
      }else if(item.name == "I"){
        item.name = 2;
      }else if(item.name == "H"){
        item.name = 3;
      }else if(item.name == "GH"){
        item.name = 3.5;
      }else if(item.name == "G"){
        item.name = 4;
      }else if(item.name == "FG"){
        item.name = 4.5;
      }else if(item.name == "F"){
        item.name = 5;
      }else if(item.name == "E"){
        item.name = 6;
      }else if(item.name == "D"){
        item.name = 7;
      }else{
        item.name = 0;
      }
      return item;
    },
    templates: {
      header: function () { return 'Diamond Color'; }
    }
  })
);

Or is there any way to write a helper that will map the values for diamond-color? I don’t know how to write a helper at this moment, but i modified the below from this: https://codepen.io/Algolia/pen/LkjwLE

function renderFacets($facets, results) {
  var facets = results.facets.map(function(facet) {
    var name = facet.name;
    var facetValues = results.getFacetValues(name);
    var facetsValuesList = $.map(facetValues, function(facetValue) {
      if(facetValue.name == "K") {
        facetValue.name = 0;
      }
    })
  });
}

Could you provide some guidance? Thank you so much!

#7

The issue with the range slider is that we need integers indexed in Algolia directly. Indeed, the engine sends back some information for a numeric facet like min and max which are useful to this widget and which it can’t do for string facets.

If you could index, at the same time as your diamond_color metafield, the associated number, this would be way easier. Since Shopify metafields only accept integers you would probably need to multiply by 10, or use our named tags feature instead: https://community.algolia.com/shopify/named-tags.html .

I still have a front-end solution only if you accept to use a refinement list instead.
I would actually recommend you to start with this, and if you want to go forward, to actually create your own custom widget to have a slider experience afterwards (this will require to know our libraries internals way more).

With a refinement list, you can use the transformData.item you wanted to use and you can also use a sortBy custom function. I’m not sure if the sort by is applied before or after the transformData, so you might have to duplicate your code between the two, but with those two, you should be able to display them in the right order.

1 Like
#8

Hi @Jerska!
algolia_instant_search.js.liquid

I attempted refinementList as my first step, and attempted to use transformData to convert the name values to numbers still in string format (wasn’t sure at what point they can be converted to numbers):

// Diamond Color
var diamondColorCssClasses = JSON.parse(JSON.stringify(algolia.facetCssClasses)); // Clone the classes object
// You can modify diamondCutCssClasses if you want here
instant.search.addWidget(
  instantsearch.widgets.refinementList({
    container: '[class~="ais-facet-meta.c_f.diamond-color"]',
    attributeName: 'meta.c_f.diamond-color',
    cssClasses: diamondColorCssClasses,
    transformData: {
      item: function (data) {
        console.log(data);
        if(data.name == "K"){
          data.name = "0";
        }else if(data.name == "J"){
          data.name = "1";
        }else if(data.name == "I"){
          data.name = "2";
        }else if(data.name == "H"){
          data.name = "3";
        }else if(data.name == "G-H"){
          data.name = "3.5";
        }else if(data.name == "G"){
          data.name = "4";
        }else if(data.name == "F-G"){
          data.name = "4.5";
        }else if(data.name == "F"){
          data.name = "5";
        }else if(data.name == "E"){
          data.name = "6";
        }else if(data.name == "D"){
          data.name = "7";
        }else{
          data.name = "0";
        }
        return data;
      }
    },
    templates: {
      header: function () { return 'Diamond Color'; }
    }
  })
);

Then directly below that code, I added a custom ion.rangeSlider widget on the same attributeName:

// Diamond Color
var values = [0,1,2,3,3.5,4,4.5,5,6,7];
var values_p = ["K","J","I","H","G-H","G","F-G","F","E","D"];

instant.search.addWidget(
  instantsearch.widgets.ionRangeSlider({
    container: '[class~="ais-facet-meta.c_f.diamond-color"]',
    attributeName: 'meta.c_f.diamond-color',
    type: "double",
    grid: true,
    grid_num: 0,
    ionRangeSliderOptions: {
      prettify_enabled: true,
      prettify: function (n) {
         var ind = values.indexOf(n);
         return values_p[ind];
      },
      prefix: "",
      postfix: "",
      values: values,
      values_separator: " to ",
      force_edges: true,
      min_interval: .5,
      step:.5
    }
  })
);

Ps. I also tried this with Diamond Carats,
and it visually looks correct, it just doesn’t return any results!

// Diamond Carat
var diamondCaratsCssClasses = JSON.parse(JSON.stringify(algolia.facetCssClasses)); // Clone the classes object
instant.search.addWidget(
  instantsearch.widgets.refinementList({
    container: '[class~="ais-facet-meta.c_f.diamond-carats"]',
    attributeName: 'meta.c_f.diamond-carats',
    cssClasses: diamondCaratsCssClasses,
    transformData: {
      item: function (data) {
        console.log(data);
        parseFloat(data.name);
        return data;
      }
    },
    templates: {
      header: function () { return 'Diamond Carats'; }
    }
  })
);

instant.search.addWidget(
  instantsearch.widgets.ionRangeSlider({
    container: '[class~="ais-facet-meta.c_f.diamond-carats"]',
    attributeName: 'meta.c_f.diamond-carats',
    type: "double",
    grid: true,
    step:.1,
    grid_num: 0,
    ionRangeSliderOptions: {
      prettify_enabled: true,
      prefix: "",
      postfix: "cts",
      values_separator: " to ",
      force_edges: true,
      min_interval: .1,
      step:.1
    }
  })
);

Link: https://viranijewelers-dev.myshopify.com/search?q=diamond&hPP=12&idx=shopify_products&p=0&nR[meta.c_f.diamond-color][<%3D][0]=8&nR[meta.c_f.diamond-color][>%3D][0]=1&is_v=1

Password: vjvj

Am i missing a step in here? Thank you!

#9

Sorry for the lack of answer! This seemed like a big chunk to process, and I didn’t find time until now.

You actually created something really great here visually, congrats on that!
When I look at the response from the queries, I’m surprised to see that I don’t find those metafields facet values though!

Just to be sure, in your metafields, are you indexing the value as an integer (values) or a string (values_p)?
Also, do you see those two metafields in https://www.algolia.com/dashboard > Indices > shopify_products > Display ?

To help you more, it would help if you could grant me read access to your index for a week, during the investigation time. You can do this here: https://www.algolia.com/users/access_control .
If you’d rather avoid that, no worries, I’m sure we’ll be able to find the solution without it, but it would help a lot!

#10

Hi @Jerska! (I missed the last reply until just now)

  • I granted you read/write access for 2 weeks!
  • Values are indexed as strings by default. Currently, we input Diamond carats as string “.33” to preserve the decimals because Shopify doesn’t have an option for decimals (floats). (live product view with metafields displayed)
    –Could we convert string to integer/float in Algolia js – so that we maintain the decimals?
    –(small constraint: Shopify metafields plugin requires we manually load another tool to convert string > integer… so less convenient for the team adding the data)

#11

To translate them to numbers using decimals, have you seen our named tags feature? See https://community.algolia.com/shopify/named-tags.html

#12

I guess this is probably the main issue here, because the slider will ask for a numeric refinement using operators (> or <), and Algolia will not know what to do with those since they’re indexed as strings.

1 Like
#13

Hi @Jerska,

Is this possible?

Because, manually adding in the #NamedTags route –for the # of metafields we have, and for thousands of products– is creating too much work for our data entry team to keep up with.

Is a javascript solution to convert string to integer possible?

#14

I’ve just built a feature for this (see Changelog).

If you now go to the Metafields list, you’ll see an “Options” link.
Click on it to be able to specify a conversion type. (Right now, only Number)

You’ll need to run a Reindex for this change to take effect.

#15

Does this new conversion work to convert strings into floating values as well? Or only whole integers?

#16

It works for decimal values too.
You can see more information about it here: https://community.algolia.com/shopify/metafields.html#metafield-options .