How to build a Single Facet Refinement Toggle Component in Algolia Instant Search with VueJS

How to build a Single Facet Refinement Toggle Component in Algolia Instant Search with VueJS


Oct 10 2018, 13:00 in Web Development

One the biggest struggle I've had while building Find A Maker was the implementation of the roles toggle button that filters the jobs thanks to Algolia Instant Search with VueJS.

The defaut Facet Refinement component allow you to build a list of checkbox based on a "facet" that you define in your index settings in Algolia.

Context

In my case, I have a list of Job with 4 different booleans attributes like this

class Job
{
    // ...
    public bool $builder;
    public bool $organizer;
    public bool $seller;
    public bool $designer;
    // ...
}

So when I asked Algolia to register Facets based on those attributes, things were messy and impossible to control.

After searching and asking the Algolia team on Github, I finally re-created a VueJS component to extend their own.

Building the solution

The goal was to have one big button "Builder?" on the page, and when you click on it, it becomes "active" and the list of jobs is refined to show only the jobs that have the $builder === true attribute.

And of course I wanted to customize the button so it has an image and specific html syntax.

Here's the final code

<template>
    <div :class="bem()" class="is-inline-block">
        <img
            src="/img/icons/builder.svg"
            title="I'm a builder"
            alt="Builder Icon"
            :class="{'is-active': isRefined}"
            @click="toggleRefinement"
        >
    </div>
</template>

<script>
import { FACET_OR, FACET_AND, Component } from 'vue-instantsearch';

export default {
    mixins: [Component],
    data() {
        return {
            isRefined: false,
            blockClassName: 'ais-refinement-list',
            facetName: 'builder',
        }
    },
    created() {
        this.searchStore.addFacet(this.facetName, this.operator);
    },
    destroyed() {
        this.searchStore.stop();
        this.searchStore.removeFacet(this.facetName);
        this.searchStore.start();
    },
    methods: {
        toggleRefinement() {
            this.isRefined = !this.isRefined;
            return this.searchStore.toggleFacetRefinement(
                this.facetName,
                true
            );
        },
    },
    watch: {
        operator() {
            this.searchStore.addFacet(this.facetName, this.operator);
        },
    },
};
</script>

Improvements

As you can see, this component is "hardcoded" for the $builder attribute. You can easily extract this data and make the component more abstract thanks to VueJS props system.

<template>
    <div :class="{'is-active': isRefined}"
        class="box is-inline-block is-capitalized has-text-centered"
        v-tooltip="'I\'m a' + name"
        @click="toggleRefinement"
    >
        <div class="icon">
            <img
                :src="icon"
                :title="'I am a' + name"
                :alt="name + 'Icon'"
            >
        </div>
        <div class="">{{name}}</div>
    </div>
</template>

<script>
import { FACET_OR, FACET_AND, Component } from 'vue-instantsearch';

export default {
    props: ['name', 'icon'],

    mixins: [Component],

    data() {
        return {
            isRefined: false,
            blockClassName: 'ais-refinement-list',
        }
    },
    created() {
        this.searchStore.addFacet(this.name, this.operator);
    },
    destroyed() {
        this.searchStore.stop();
        this.searchStore.removeFacet(this.name);
        this.searchStore.start();
    },
    methods: {
        toggleRefinement() {
            this.isRefined = !this.isRefined;
            return this.searchStore.toggleFacetRefinement(
                this.name,
                true
            );
        },
    },
    watch: {
        operator() {
            this.searchStore.addFacet(this.name, this.operator);
        },
    },
};
</script>

Share:
Like:

Disable AdBlock on this domain and offer me a cup of coffee :)