Gruber
Gruber

Reputation: 2308

Show attribute in search result template only if contains highlighted matches

I have several attributes set as attributesToRetrieve. In my template though only some of these are displayed. Roughly something like this:

<div id="results">
  <div>{{_highlightResult.attr1.value}}</div>
  <div>{{_highlightResult.attr2.value}}</div>
  <div>{{_highlightResult.attr3.value}}</div>
</div>

This way the attributes will be rendered in any case, and highlighted if they contain a matched word.

Now I'd like to add another section where all other attributes can be displayed but only if they contain a matched word to be highlighted, something like:

<div id="results">
  <div>{{_highlightResult.attr_1.value}}</div>
  <div>{{_highlightResult.attr_2.value}}</div>
  <div>{{_highlightResult.attr_3.value}}</div>
<!-- 
The following div will be rendered and displayed only 
if any of these special attributes contain an highlighted word. 
Only an attribute containing a matched word will be displayed 
-->
  <div class="other-attr">
    {{_highlightResult.Long_SpecialAttr_1.value}}
    {{_highlightResult.SpecialAttr_2.value}}
    {{_highlightResult.SpecialAttr_3.value}}
  </div>
</div>

As mentioned in the comment, this section will be rendered and displayed only if any of these special attributes contain an highlighted word, also only an attribute containing a matched word will be displayed.

Plus as you can see there is a Long_SpecialAttr_1, it's a long text attribute, wich I'd like to have it displayed as a snippeted attribute.

To give a better idea (maybe) what I'm trying to achieve for this additional section is something like the one Google display below every search results, a sort of text blob text with ellipsis containing the marked words of these attributes.

Is this possible? I'm using algolia instasearch.js, thank you!

UPDATE

Thanks to @Jerska for his answer, unfortunately a small bit of code wasn't working in my case, specifically:

['highlight', 'snippet'].forEach(function (type) {
  data['_' + type + 'Result'].forEach(function (elt) {
    elt.display = elt.matchLevel !== 'none';
  });
});

giving me an error in the console stating data._snippetResult.forEach() is undefined. So I modified that bit with this:

for(var el in d._snippetResult)
{
  // create new property with bool value, true if not "none"
  d._snippetResult[el].display = d._snippetResult[el].matchLevel !== 'none';
};

Upvotes: 3

Views: 474

Answers (1)

Jerska
Jerska

Reputation: 12042

First of all, just to clarify the settings of your index before going forward, Algolia also highlights the attributes in attributesToSnippet.
Also, to have an ellipsis on the attributes you snippet, you can set snippetEllipsisText.
So you might want to use these settings in your index:

attributesToHighlight: ['attr_1', 'attr_2', 'attr_3', 'SpecialAttr_2', 'SpecialAttr_3'],
attributesToSnippet: ['Long_SpecialAttr_1:3'], // Snippet should contain max 3 words
snippetEllipsisText: '…' // This is the utf-8 "Horizontal ellipsis" character

On the front-end side, in instantsearch.js you can use the transformData parameter on almost any widget to be able to access and/or modify the data passed to the template.

In this specific example, we'll want to have a look at transformData.item on the hits widget.

The first step would be to log the data:

search.addWidget(
  instantsearch.widgets.hits({
    transformData: {
      item: function (data) {
        console.log(data);
        return data;
      }
    }
  })
);

This will allow you to see that kind of response:

_highlightResult: {
  attr_1: {
    value: 'lorem <em>ipsum</em> dolor <em>sit</em>',
    matchLevel: 'full',
    matchedWords: ['ipsum', 'sit']
  },
  attr_2: {
    value: 'lorem <em>ipsum</em> dolor',
    matchLevel: 'partial',
    matchedWords: ['ipsum']
  },
  attr_3: {
    value: 'lorem',
    matchLevel: 'none',
    matchedWords: []
  },
  // ...
},
_snippetResult: {
  Long_SpecialAttr_1: {
    value: 'lorem <em>ipsum</em> dolor …', // Let's assume Long_SpecialAttr_1 was equal to 'lorem ipsum dolor sit'
    matchLevel: 'full'
  }
}

Unfortunately here, the API is a bit inconsistent since as you can see, snippeted attributes don't have the matchedWords attribute that highlighted attributes have. You can choose to set it both in attributesToSnippet and attributesToHighlight if you really want the info.

However, for your use-case, we just need matchLevel. What we want, is to display elements only if matchLevel !== 'none'. Unfortunately, Hogan.js, the underlying template engine of instantsearch.js doesn't allow for much flexibility, so you can't just put this comparison in your template.

A solution could be to precompute these conditions inside the transformData:

transformData: {
  item: function (data) {
    ['highlight', 'snippet'].forEach(function (type) {
      var group = data['_' + type + 'Result'];
      for (var attr in group) {
        if (!group.hasOwnProperty(attr)) continue;
        var elt = group[attr];
        elt.display = elt.matchLevel !== 'none';
      };
    });
    data.displaySpecial = false ||
      data._snippetResult.Long_SpecialAttr_1.display ||
      data._highlightResult.SpecialAttr_2.display ||
      data._highlightResult.SpecialAttr_3.display;
    return data;
  }
}

And then use these new attributes in your template:

<div id="results">
  <div>{{{_highlightResult.attr_1.value}}}</div>
  <div>{{{_highlightResult.attr_2.value}}}</div>
  <div>{{{_highlightResult.attr_3.value}}}</div>
<!-- 
The following div will be rendered and displayed only 
if any of these special attributes contain an highlighted word. 
Only an attribute containing a matched word will be displayed 
-->
  {{#displaySpecial}}
    <div class="other-attr">
      {{#_snippetResult.Long_SpecialAttr_1.display}}
        {{{_highlightResult.Long_SpecialAttr_1.value}}}
      {{/_snippetResult.Long_SpecialAttr_1.display}}

      {{#_highlightResult.SpecialAttr_2.display}}
        {{{_highlightResult.SpecialAttr_2.value}}}
      {{/_highlightResult.SpecialAttr_2.display}}

      {{#_highlightResult.SpecialAttr_3.display}}
        {{{_highlightResult.SpecialAttr_3.value}}}
      {{/_highlightResult.SpecialAttr_3.display}}
  </div>
  {{#displaySpecial}}
</div>

(By the way, to render HTML, you should use {{{ ... }}} instead of {{...}}, I've replaced them here)

Upvotes: 4

Related Questions