Vue-instantsearch: v2 - Conditional refinement lists

Created on 16 Feb 2019  ·  18Comments  ·  Source: algolia/vue-instantsearch

Feedback on the v2 alpha version 🆕

Describe the bug / features

Would appreciate some help with this, I'm not sure if I'm doing anything wrong or if this is intended behavior.

We have around half a dozen facets at the moment with more to come. Due to this, the UI we chose is having refinement lists hidden behind a button representing each facet. When the button is clicked, the refinement list shows below the button (think like a dropdown menu). Clicking on the button again closes the list (same as clicking anywhere outside the "box").

There are 2 main issues with this in v2:

1) Since there is no searchStore anymore, it is not possible to get easy access to _helper.state.disjunctiveFacetsRefinements or _helper.state.numericRefinements. We need this in order to "detect" if a refinement list is "active" (thus changing the button styling indicating it as active).

2) When the refinement list is hidden (we use v-show to control visibility) any enabled refinements get removed automatically.

On a related note since there is no more searchStore now there is also no more toggleFacetRefinement method. What is the recommended approach to use instead now?

How to resolve or implement the fix?

  • Do not remove active refinements when a refinement list is hidden

  • Add disjunctiveFacetsRefinements and numericRefinements (we only use these 2, not sure if there are any others that make sense) to the ais-state-results component so they can be used for conditional styling

  • It would also be nice if there was a isActive property or something similar in the ais-state-results component (basically a boolean indicating where any part of Algolia is active; a query is present, or a refinement etc), though if the above is implemented this is simple enough to do on our own

What is the version you are using?

v2.0.0-beta.3

Feedback

Most helpful comment

Just wanted to report back that not inlining :sort-by per @samouss fixed the second issue. And a custom connector gets access to the underlying helper like before. Cheers.

All 18 comments

  • v-show shouldn't remove a refinement. v-if definitely does however. Can you give a sandbox of what's going on here, so it can be debugged?

  • That's a great point, I thought this was already the case. You can read it from state.disjunctiveFacets for example. Here's one with hierarchical shown:

https://codesandbox.io/s/8kpw4qnjl

  • this is something we plan to add to the ais-panel widget. For now it has access to noRefinement; isRefined was the next thing we were going to add.

On a related note since there is no more searchStore now there is also no more toggleFacetRefinement method. What is the recommended approach to use instead now?

This would be ideally done with a connectRefinementList custom widget and call this.state.refine there in the correct part of the life cycle. Alternatively you can also use connector: true and then use instantSearchInstance.helper for low-level methods as you used before.

Thanks for all your feedback!

I've managed to get the same behaviour as @pix2D when using the sort-by property of ais-refinement-list.

e.g. if a sort by is provided such as :sort-by="['isRefined', 'name:asc']" to ensure the refinements are listed alphabetically, whenever I toggle the refinement list to hide it, the current refinements for that list get cleared. I'm using v-show to do the toggle so in theory all that it should be doing is setting the display property.

If I take out the sort-by override and just let it do the default, the refinements do stay in place.

Hi @tcmorris, the issue you described might be linked to this one: https://github.com/algolia/vue-instantsearch/issues/575. Could you avoid to inline the sort-by attribute? You can store the value on the instance, it should solve the issue.

Cheers @samouss that seems to do the trick!

@Haroenv is it possible to toggle that functionality off for v-if? I'm currently using https://github.com/euvl/vue-js-modal and they use v-if so every time I close the modal, I lose the filter

Just wanted to report back that not inlining :sort-by per @samouss fixed the second issue. And a custom connector gets access to the underlying helper like before. Cheers.

@Haroenv Perhaps I was celebrating a little too soon. So I did get the data I need from my custom connector (which is just an empty component). I have a computed property in this component with the following:

      let instance = this.instantSearchInstance

      if (!instance.helper || !instance.helper.lastResults) {
        return null
      }

      let helper = instance.helper

      return {
        query: helper.lastResults.query,
        hitsLength: helper.lastResults.hits.length,
        disjunctiveFacetsRefinements: helper.lastResults._state.disjunctiveFacetsRefinements,
        numericRefinements: helper.lastResults._state.numericRefinements,
      }

I then have a watcher watching this property and submitting an event on change to my parent component where it's stored in the local component data. Until here everything works fine. However if I try to do anything with this data (such as use it in a v-if check) in the <template> of the parent component it creates a loop where the watcher constantly detects changes and keeps emitting an update forever.

I looked at your example using <AisStateResults> however it doesn't seem what I need is present there. The state.disjunctiveFacets only contains a list of the facets, not if they are currently active or not.

Any thoughts?

Whether a facet is active will be in disjunctiveFacetsRefinements

@Haroenv Right, but this is not available through <AisStateResults>.

In any case I have finally solved all my issues now. I've created individual computed properties for each data point I need in my custom connector component (instead of having them all in 1 like in my example above), and I'm no longer trying to pass it up to the parent component via an event.

Now I just wrapped the whole contents of the parent component in the default slot of my connector component, and am passing in the computed props to the parent through the slot (like it's done with <AisStateResults>). The result is no more infinite event loops, and everything works. :tada:

Makes sense, regarding no refinements showing, it's something we were working on in https://github.com/algolia/vue-instantsearch/pull/627, but put temporarily on hold because it would be a breaking change.

@Haroenv Any thoughts on @alexbudure comment above? I am now also at this same issue actually. I have filters in a modal, and anytime the modal is closed any filters set are undone.

This is not something we can do with the way Vue is working, our refinements are solely based on the component tree. If you use v-if, that means the component won't be in the tree

I need the sort-by property. I don't understand why there needs to be some javascript gymnastics to keep the values of the existing fasts when hiding the refinement lists.

How on earth are we to design/developer complicated, multifaceted, UIs if we can't be allowed to hide refinement lists (and other components) without resetting the whole query?

Hi @tcmorris, the issue you described might be linked to this one: #575. Could you avoid to inline the sort-by attribute? You can store the value on the instance, it should solve the issue.

Is there any way to drop an example of this. The one mentioned in #575 does not seem to actually show how to

"save" the attributes inside data to avoid this issue

Here is how with the sort-by widget:

<template>
  <ais-instant-search [...]>
    <ais-sort-by :items="sortByItems" />
  </ais-instant-search>
</template>

<script>
export default {
  data() {
    return {
      // ...
      sortByItems: [
        { value: "instant_search", label: "Featured" },
        { value: "instant_search_price_asc", label: "Price asc." },
        { value: "instant_search_price_desc", label: "Price desc." }
      ]
    };
  }
};
</script>

You can find a live example on CodeSandbox.

@samouss - that doesn't solve the sorting of the refinement lists as per the original issue. That's a sort of the items.

having refinement lists hidden behind a button representing each facet

The solution I've come to is to use :transform-items and then a function to return the items in the order I need them to do in.

<ais-refinement-list attribute="departments" :transform-items="alphabetizeItems"></ais-refinement-list>
...

    alphabetizeItems(items) {
      return _.sortBy(items, ["label"]);
    }

@jessemarple nice that you got a working approach. fyi, in the docs it mentions to define a string array of what to sort your refinement list by:

https://www.algolia.com/doc/api-reference/widgets/refinement-list/vue/#widget-param-sort-by

This is what I'm using to sort alphabetically: sortBy: ["isRefined", "name:asc"]

@jessemarple My bad for the misunderstanding, I thought the problem was related to the inlined value of the sort-by prop. Not about how to apply sort on the ais-refinement-list.

Glad to see that you've solved your issue though! Like @tcmorris said we support the sort-by option with pre-defined tokens to allow a "simple" sort. The solution you came up with is more appropriate when none of our pre-defined sorts apply.

Note that the current thread is related to "Conditional refinement" not about "How to sort a refinement list". Next time don't hesitate to open a new issue otherwise the context and the main purpose of the issue is lost. Thanks!

Was this page helpful?
0 / 5 - 0 ratings