Ivan
Ivan

Reputation: 433

How to create a cache system for a live search while typing

Im trying to make a search engine on key up to return results from DB but also if phrase has been typed to pull results from object not from DB

For example : when you type "BMW 300" will pull data from DB and will save phrase and results in object , and object will look like this

object {
b: (4) […]
  0: Object { name: "BMW"}
  1: Object { name: "BMW 300"}
  2: Object { name: "BMW XS"}
  3: Object { name: "BMW Z 500"}

bm: (4) […]
  0: Object { name: "BMW"}
  1: Object { name: "BMW 300"}
  2: Object { name: "BMW XS"}
  3: Object { name: "BMW Z 500"}

bmw: (4) […]
  0: Object { name: "BMW"}
  1: Object { name: "BMW 300"}
  2: Object { name: "BMW XS"}
  3: Object { name: "BMW Z 500"}

"bmw 3": (1) […]
​  0: Object { name: "BMW 300"}

"bmw 30": (1) […]
​  0: Object { name: "BMW 300"}

"bmw 300": (1) […]
​  0: Object { name: "BMW 300"}
}

if you delete 300 phrase is become "BMW" now should pull results from object because phrase "BMW" has been typed .

I code both logic and they work perfect , but the problem is how to swap when to read from object and when to read from DB ?

// create object
var hold_results = {};

$('input[name="search"]').on('keyup', function(){

var phrase = jQuery.trim($(this).val());

if (phrase.length > 0) {
  $.ajax({
    type: "POST",
    url: 'index.php?route=product/newsearch',
    data: 'phrase='+phrase,
    success: function(data) {
      if (data.length != 0) {

      // push phrase and currect results in object
        hold_results[phrase] = data;


          $.each(data , function(key, value){
            if (value != '') {
              html = '<li><a href="'+value.href+'">'+value.name+'</a></li>';
              $('#search-results').append(html);
            }
        });
      }
    }
  });
}

// should pull results from object here if phrase exist
$.each(hold_results , function(key , value){

  if (key == phrase) {
    $.each(value, function(k , v){
      html = '<li><a href="'+v.href+'">'+v.name+'</a></li>';
      $('#search-results').append(html);
    })
  }
});
});

Upvotes: 4

Views: 620

Answers (2)

Shlomi Hassid
Shlomi Hassid

Reputation: 6606

There is a simple approach to achieve that - But first you need to be aware that your implementation will fire too many times and may not work as expected. I would consider adding several things:

  1. Aborting previously fired async ajax requests before firing the new search - In your code it will just keep firing and receive many results that may not be relevant any more.
  2. Add a Caching mechanism - you want to control how much records it will store and you want a good way to match required search terms (Exact match OR subsets OR both).

Here is Demo in JSfiddle - I will walk you through

First we create a variable that will hold an active ajax request otherwise will be null:

//this is the variable that will store the ajax call
var request = null;

Now we can create an Object that will be our Cache system with all the required methods:

var cache = {
  storageFIFO: [], // The array that will hold our results 
  maxResults: 20, // max enteries in the storedResultFifo array - will shift once when exceeded
  pushResults: function(phrase, data) {
    cache.storageFIFO.push({
      term: phrase,
      data: data
    });
    if (cache.storageFIFO.length > cache.maxResults) {
      cache.storageFIFO.shift();
    }
  },
  getCachedResults: function(phrase) {
    //First try exact match against cached search terms:
    for (var i = 0; i < cache.storageFIFO.length; i++)
      if (cache.storageFIFO[i].term === phrase)
        return cache.storageFIFO[i].data;
    //try phrase as a substring of the terms stored in the cache
    for (var i = 0; i < cache.storageFIFO.length; i++)
      if (cache.storageFIFO[i].term.includes(phrase))
        return cache.storageFIFO[i].data;
    return false;
  }
};

A few notes about the Cache:

  1. FIFO (first in first out) : When storage limit is reached the oldest record is removed.
  2. .shift(); Will pop the first element (index 0 which is the oldest) to achieve the FIFO like data structure.
  3. getCachedResults : this method will try to match the phrase twice - Once for an exact match (for better results) otherwise will try to match the phrase as a subset of the cached terms.
  4. string1.includes(string2) this is the way that I match subsets.

Now that the cache mechanism is set - I put the logic inside the keyup event:

//Bind the event to search:
$('input[name="search"]').on('keyup', function() {
  var phrase = jQuery.trim($(this).val());
  if (phrase.length <= 1) return; // From length of two
  //This is just for testing and generating random data:
  var randomData = [];
  for (var i = 0; i < Math.floor(Math.random() * (20 - 5 + 1)) + 5; i++)
    randomData.push({
      phrase: phrase,
      href: "#",
      name: Math.random().toString(36).substring(7)
    });
  //This will trigger an abort of previous unfinished ajax request
  //When aborted the ajax `complete` will set it back to null
  if (request != null) request.abort();

  //try to load from Cache first:
  var fromCache = cache.getCachedResults(phrase);
  if (fromCache !== false) {
    drawResults(fromCache, "Cached Results");
    return;
  }

  //If we got here that means that the cache does not has matching phrases:
  //Send a request to db:
  request = $.ajax({
    url: '/echo/json/', // A jsfiddle async request endpoint
    type: 'POST',
    contentType: 'application/json; charset=utf-8',
    dataType: 'json',
    data: { //the correct data structure for jsfiddle async requests
      json: JSON.stringify(randomData),
      delay: 0.15
    },
    success: function(data) {
      if (data.length != 0) {
        //Cache it:
        cache.pushResults(phrase, data);
        //Draw it: 
        drawResults(data, "DataBase");
      }
    },
    complete: function() {
      request = null;
    }
  });

});

Here you can see some additional stuff that is for the jsfiddle created (to get results from a dummy async request server).

A few notes about the Logic and Ajax request:

  1. if (phrase.length <= 1) return; : will prevent queries that are not 2 chars and longer.
  2. if (request != null) request.abort(); : will abort previously running requests. As you can see in the Ajax complete callback it will set it back to null when finished (success, abort, error etc...)
  3. cache.getCachedResults(phrase); : First try from cache - Will return false if no match.
  4. After no hits againts the cache - then an ajax is fired (note that the url, and data is modified to the required values to work with jsfiddle).

Final Notes Drawing the results is up to you - I just included a small quick function to visualize the results and to see where they came from.

Upvotes: 1

ktad
ktad

Reputation: 301

You can simply check whether a phrase already exists in holds_results object and if it doesn't fetch it from db

  // create object
  var hold_results = {};

  $('input[name="search"]').on('keyup', function(){
    var phrase = jQuery.trim($(this).val());

    if (phrase.length > 0) {
      if (hold_results[phrase]) {
        // should pull results from object here if phrase exist
        $.each(hold_results , function(key , value){

          if (key == phrase) {
            $.each(value, function(k , v){
              html = '<li><a href="'+v.href+'">'+v.name + '</a></li>';
              $('#search-results').append(html);
            })
          }
        });
      }
      else {
        $.ajax({
          type: "POST",
          url: 'index.php?route=product/newsearch',
          data: 'phrase=' + phrase,
          success: function (data) {
            if (data.length != 0) {

              // push phrase and currect results in object
              hold_results[phrase] = data;


              $.each(data, function (key, value) {
                if (value != '') {
                  html = '<li><a href="' + value.href + '">' + value.name + '</a></li>';
                  $('#search-results').append(html);
                }
              });
            }
          }
        });
      }
    }
  });

Upvotes: 0

Related Questions