30 Mar 2018

Infinite Scroll

Introduction

For very visual websites, such as those built around photo sharing, infinite scroll can be a nice experience for users, and implementing it with Algolia is straightforward.

In this tutorial we’ll see how we can:

  • go further than 1000 hits
  • to an infinite scroll

Dataset

[
  {
    "name": "Catherine Missal",
    "rating": 4875,
    "image_path": "/g3fsRgEoMxaqPayIMtGDWERqJ6A.jpg",
    "alternative_name": null,
    "objectID": "551486300"
  },
  [...]
]

In this tutorial we make use of the name and image_path only.

You can download the dataset here. Have look at how to import it in Algolia here

Initialization

<?php
// composer autoload
require __DIR__ . '/vendor/autoload.php';

// if you are not using composer
// require_once 'path/to/algoliasearch.php';

$client = new \AlgoliaSearch\Client('YourApplicationID', 'YourAdminAPIKey');

$index = $client->initIndex('your_index_name');
require 'rubygems'
require 'algoliasearch'

Algolia.init application_id: 'YourApplicationID',
             api_key:        'YourAPIKey'
index = Algolia::Index.new("your_index_name")
// var algoliasearch = require('algoliasearch');
// var algoliasearch = require('algoliasearch/reactnative');
// var algoliasearch = require('algoliasearch/lite');
// import algoliasearch from 'algoliasearch';
//
// or just use algoliasearch if you are using a <script> tag
// if you are using AMD module loader, algoliasearch will not be defined in window,
// but in the AMD modules of the page

var client = algoliasearch('YourApplicationID', 'YourAPIKey');
var index = client.initIndex('your_index_name');
from algoliasearch import algoliasearch

client = algoliasearch.Client("YourApplicationID", 'YourAPIKey')
index = client.init_index('your_index_name')
import "github.com/algolia/algoliasearch-client-go/algoliasearch"

func main() {
    client := algoliasearch.NewClient("YourApplicationID", "YourAPIKey")
    index := client.InitIndex("your_index_name")
}

Configuring the index

We are not going to focus on configuring the relevance of the index as the goal is just to do an infinite scroll.

By default Algolia limit the number of hits you can retrieve for a query to 1000; when doing an infinite scroll, you usually want to go over this limit.

<?php
$index->setSettings([
  'paginationLimitedTo' => 0 // Disable the limit
]);

Disabling the limit does not mean that we will be able to go until the end of the hits, but just that Algolia will go as far as possible in the index to retrieve results in a reasonable time.

HTML

<!DOCTYPE HTML>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/instantsearch.js@2.3/dist/instantsearch.min.css">
<script src="https://cdn.jsdelivr.net/npm/instantsearch.js@2.3/dist/instantsearch.min.js">
<!-- Always use `2.x` versions in production rather than `2` to mitigate any side effects on your website,
Find the latest version on InstantSearch.js website: https://community.algolia.com/instantsearch.js/v2/guides/usage.html -->
</script>
<script src="https://cdn.jsdelivr.net/jquery/3.2.1/jquery.min.js"></script>

<input type="text" id="search" />


<div id="hits-container"></div>

Javascript

We start by initializing instantsearch and the search input widget:

var search = instantsearch({
  appId: 'YourApplicationID',
  apiKey: 'YourSearchOnlyApiKey',
  indexName: 'indexName'
});

search.addWidget(
  instantsearch.widgets.searchBox({
    container: '#search',
    placeholder: 'Search for actors'
  })
);

Since InstantSearch.js does not come with an infinite scroll widget, we will write our own.

A custom widget is a object that define at least one of the three following method:

  • init
  • getConfiguration
  • render

For our infinite scroll widget we are going to define init and render:

  • init will take care of binding the scroll event; when reaching the end of the page, it will load the next page in the index
  • render will render the results; when updating the query, the page is reset to 0 and will make sure to empty our results container before rendering the hits
var readyToFetchMore = true;
 
var hitsContainer = $('#hits-container');

search.addWidget(
  {
    init: function (params) {
      params.helper.setQueryParameter('hitsPerPage', 10);

      function scrollhandler() {

        var isAtBottomOfPage = $(window).scrollTop() + $(window).height()
                                > $(document).height() - 500;

        if (readyToFetchMore && isAtBottomOfPage) {
          readyToFetchMore = false;
          params.helper.nextPage().search();
        }
      }

      $(window).bind("scroll", scrollhandler);
    },

    render: function (params) {

      readyToFetchMore = true;

      var hits = params.results.hits;


      if (params.state.page === 0) { // because '0' means we changed the query
        hitsContainer.html('');
      }

      var html = '';

      if (params.results.nbHits > 0) {

        html = hits.map(function (hit) {

          return '<div class="hit">'
                    + '<img src="http://image.tmdb.org/t/p/w300/' + hit.image_path + '" />'
                    + '<div class="actor_name">' + hit.name + '</div>'
                  + '</div>';

        });

      } else {
          html = ['No results'];
      }

      hitsContainer.append(html.join(''));
    }
  }
);

A few details about this custom widget:

  • Our dataset is extracted from TMDB and the image_path stored in the index need to be prefixed by http://image.tmdb.org/t/p/w300/

  • We introduced a readyToFetchMore boolean to avoid fetching the next page before the previous one is rendered

Then we make sure to start our search UI:

search.start();
© Algolia - Privacy Policy