Layer callbacks 101: Two examples

or send an email to support@doofinder.com

This article will give some clues on how to go a little further with layer use. If you have some code skills, you greatly increase your layer functionality. In this article, we're about to see two exampes of that.

Doofinder Script has plenty of configuration parameters to modify default behavior. But it also has events and related callbacks that may be customized

Doofinder Layer triggers some events on certain situations. Those situations are:

  • loaded: when the layer is in place and account options have been loaded successfully
  • resultsReceived: When, after a search action, results has been received by the layer
  • hit: When some result is clicked inside the results list shown

The good news is that you can attach your own handlers to them. Those are the so called callbacks.

Callbacks in script

This is how they should look like once set in your script

 dfClassicLayers = [{
     // your previous script options
     callbacks: {
         resultsReceived: function(res){
             console.log(res);
         },
         hit: function(event, url){
             console.log(url);
         },
         loaded: function(config){
             console.log(config);
         }
     }
 }]

Now, as you can see, once you can add your own functions, your possibilities increase substantially. If you don't see it yet, here are more elements.

Example #1: Adding extra content to the results layer

What you want

Suppose you want to show your customers not just products you sale but also extra links you think they could be also interested in. You don't want to mix them all as they could get confused. Besides, page links don't have the same fields that products do and you want to give your layer a nice look.

How you would do it

The first thing that should come to your mind is that you require two feeds to look in. Let's say a product feed and an articles feed so what you did initially was to set two different SearchEngines and the first solution that comes to your mind is

"It would be great if I could put both groups of results inside the same layer but visibly apart"

Doofinder Library to the rescue

Here is where combined efforts of Doofinder JavaScript Library and callbacks can make the trick!

Notice: Doofinder layer already uses this library so it is already there for you.

The solution

This is the basic code required to make this idea start working. First you need to define a custom layer template so you can modify it as you prefer. This would be a Mustache template to be put inside your page.

<script type="text/x-mustache-template" id="custom-layer-template">
  <div class="df-classic df-hidden" id="{{ mainContainerId }}">
    <div class="df-classic__content df-layer__content">
      <div class="df-aside">
        <div class="df-aside__header">
          <a class="df-icon df-icon--close" href="#" data-role="close">
            <svg fill="#000000" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
                <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
                <path d="M0 0h24v24H0z" fill="none"/>
            </svg>
          </a>
        </div>
        <div class="df-aside__content">
          <div id="df-aside__content__{{ mainContainerId }}"></div>
        </div>
      </div>
      <div class="df-main">
        {{#showHeader}}
          <div class="df-header" id="df-header__{{ mainContainerId }}"></div>
        {{/showHeader}}
        <div class="df-results" id="df-results__{{ mainContainerId }}">
          <div id="df-results__content__{{ mainContainerId }}" class="df-results__content"></div>
        </div>
        <!-- EXTRA CONTENT STARTS -->
        <div id="df-extra-content"></div>
        <!-- EXTRA CONTENT ENDS -->
        <div id="doofinderLogo">
          <a href="http://www.doofinder.com?mktcod=REF1" target="_blank">
            Powered&nbsp;by&nbsp;<i class="doofinder-logo-small">Doofinder</i>
          </a>
        </div>
      </div>
   </div>
  </div>
</script>

Notice: In that template, there is an additional empty div #df-extra-content included for the purpose of this example. There you will insert the results list from the second SearchEngine

You need also to tell Doofinder layer to use this custom template instead of the default one. This is done with another layer option

 dfClassicLayers = [{
     // your previous script options

     display:{
         template: document.getElementById('custom-layer-template')
     }
 }]

Now the layer template is in place, lets add our custom callback to fill it. The event selected to include this function will be loaded as it will be executed once Doofinder is properly set. Lets include it in our layer options.

 dfClassicLayers = [{
     // your previous script options

     display:{
         template: document.getElementById('custom-layer-template').innerHTML
     }
     callbacks:{
         loaded: function(config){
                  // A new library client, similar to the one already living in your layer
                  var second_client = new doofinder.core.Client('theHashidOfSecondSerchEngine', config.layerOptions.zone);
                  // A new place to put results on. The empty container we set inside the template
                  var second_resultsWidget = new doofinder.core.widgets.ScrollResults('#df-extra-content');
                  // Another Controller to rule the new elements.
                  var second_controller = new doofinder.core.Controller(second_client, [config.layer.queryInputWidget, second_resultsWidget]);
                  }
     }
 }]

Notice: The second_controller is "listening" to the same input as the initial layer so it will react on same search words but will show results on a different place.

It's enough for our example. Now, if you do some test search you will find how results from second SearchEngine are attached below the first ones. Now go and play a little more to make it look prettier. Keep the layer reference close, it could be useful.

Example #2: Displaying a custom message when search is not accurate.

What you want

In order to obtain the best possible search results, Doofinder tries several modes of searching. One of those is the "fuzzy" searching. This is a "last resource" searching mode that Doofinder tries when none of the standard search modes are successful. The "fuzzy" search mode tries to find "close enough" results, rather than perfect matches, and you may want to let the user know - when this is the chosen search mode by Doofinder - that search results are more "diffuse".

How you would do it

You would need to accomplish two things:

  • Know when Doofinder's search has been done in fuzzy mode: That information can be obtained from Doofinder's server response.
  • When fuzzy mode has been used, display a message inside the layer saying search is not accurate. You probably want a custom template for this.

The solution

First thing first: let's create a custom results template with a placeholder for our custom message.

<script type="text/x-mustache-template" id="custom-results-template">
{{#is_first}}
  {{#banner}}
    <div class="df-banner">
      <a {{#blank}}target="_blank"{{/blank}} href="{{link}}" data-role="banner" data-banner="{{id}}">
        <img src="{{#remove-protocol}}{{image}}{{/remove-protocol}}">
      </a>
    </div>
  {{/banner}}
{{/is_first}}
{{#total}}
  <p id="df-query-name" hidden>The results yielded for &#34;<span id="df-query"></span>&#34; are not very accurate</p>
  {{#results}}
    <div class="df-card" data-role="result">
      <a class="df-card__main" href="{{#url-params}}{{{link}}}{{/url-params}}" data-role="result-link" data-dfid="{{dfid}}">
        {{#image_link}}
          <figure class="df-card__image">
            <img src="{{#remove-protocol}}{{image_link}}{{/remove-protocol}}" alt="{{title}}">
          </figure>
        {{/image_link}}
        <div class="df-card__content">
          <div class="df-card__title">{{title}}</div>
          <div class="df-card__description">{{{description}}}</div>
          {{#price}}
          <div class="df-card__pricing">
            <span class="df-card__price {{#sale_price}}df-card__price--old{{/sale_price}}">
              {{#format-currency}}{{price}}{{/format-currency}}
            </span>
            {{#sale_price}}
              <span class="df-card__price df-card__price--new">
                {{#format-currency}}{{sale_price}}{{/format-currency}}
              </span>
            {{/sale_price}}
          </div>
          {{/price}}
          {{#df_rating}}
          <div>
            <div class="df-rating" title="{{df_rating}}">
              <div class="df-rating__value" style="width: {{#rating-percent}}{{df_rating}}{{/rating-percent}}">
                <i></i><i></i><i></i><i></i><i></i>
              </div>
              <div class="df-rating__placeholder">
                <i></i><i></i><i></i><i></i><i></i>
              </div>
            </div>
          </div>
          {{/df_rating}}
        </div>
      </a>
    </div>
  {{/results}}
{{/total}}
{{^total}}
  {{#noResultsHTML}}{{{noResultsHTML}}}{{/noResultsHTML}}
  {{^noResultsHTML}}
    <p class="df-no-results">{{#translate}}Sorry, no results found.{{/translate}}</p>
  {{/noResultsHTML}}
{{/total}}

</script>

Notice: You can see the place holder for the message at the p#df-query-name css selector. That element is hidden by default

As with the previous example, you need to tell Doofinder to use this custom results layer instead of the default results layer.

dfClassicLayers = [{
   // your previous script options

  display: {
    // some display options
    results: {
      template: document.getElementById('custom-results-template')
    }
  }
}]

Now, we just have to make that placeholder element visible whenever the search results are from a "fuzzy" query. We use for that the resultsReceived callback, which called every time search results are received, and the handler is provided with a response object with details about the obtained search results.

 dfClassicLayers = [{
     // your previous script options

     display:{
        results: {
          template: document.getElementById('custom-results-template').innerHTML
        }
     }
     callbacks:{
         resultsReceived: function(response){
             // are there results??
              if (response.total > 0) {
                  // is this a "fuzzy" query??
                  if (response.query_name === "fuzzy") { // yes: show the message!!!
                      document.getElementById("df-query").innerHTML = response.query;
                      document.getElementById("df-query-name").removeAttribute("hidden");
                  }
                  else { // nope: hide it.
                      document.getElementById("df-query-name").setAttribute("hidden", "");
                  }
              }
         }
     }
 }]

This is just one way of showing the message, you can do whatever you want with your DOM structure. The point here is that callback is called each time results are received, and response info is provided to it.