Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use materialize autocomplete plugin with ajax?

I am trying to make work together the materializecss autocomplete plugin with my ajax call in order to dynamically load data according to what is typed on the input field.

My ajax request is called inside a keydown event. All data fetched are automatically pushed into a key/value object array.

Then, i put the autocomplete function in the ajax's success function and the value of the key "data" is the object array built right before.

It's seems i am on the good way but when i am testing in the browser, each time i type something, the suggestion dropdown shows up as expected with results, but rather than be renewed after each keydown, another dropdown list overlap the previous one and so one...

So this is my problem : How to do to avoid the dropdown suggestion list to overlap and rather make it renew each time i press a key ?

Thank you for helping.

var dat = {};
$("input").on("keydown", function(e) {
    var d = {
        query: {
            prefix: {
                body: e.target.value
            }
        }
    };

    $.ajax({
        url: "https://xxxxxxxxxxxxxxx.eu-west-1.es.amazonaws.com/xxxxxxxxxxxxx",
        type: "POST",
        contentType: "application/json",
        crossDomain: true,
        data: JSON.stringify(d),
        dataType: "JSON",
        async: true,
        success: function(da) {
            var c = da.hits.hits.length;
            for (var i = 0; i < c; i++) {
                dat[da.hits.hits[i]._source.body] = null;
            }

            $("input").autocomplete({
                data: dat
            });
        },
        error: function(jqXHR, errorStatus, errorThrown) {
            console.log(jqXHR);
            console.log(errorStatus);
            console.log(errorThrown);
        }
    });
});
like image 404
John doe Avatar asked Aug 01 '16 02:08

John doe


4 Answers

Here you go, a much cleaner example.

  • It's based on the Materialized.js original function
  • Cancels existing ajax requests as you type so you don't get double results
  • If you remove the 'timeout' commented lines, this will only call an ajax call after 'x' time has elapsed after a keypress. Might be useful when you're typing fast to avoid ajax calls on every single key press (even though they get cancelled).

See below:

initAutoComplete({inputId:'autocomplete-input',ajaxUrl:'/search/my-auto-complete-results'})


function initAutoComplete(options)
{  
  var defaults = {
    inputId:null,
    ajaxUrl:false,    
    data: {}
  };

  options = $.extend(defaults, options);
  var $input = $("#"+options.inputId);

  if (options.ajaxUrl !== false)
  {
    var $autocomplete = $('<ul id="myId" class="autocomplete-content dropdown-content"></ul>'),   
        $inputDiv = $input.closest('.input-field'),
        //timeout,
        runningRequest = false,
        request;

    if ($inputDiv.length) {
      $inputDiv.append($autocomplete); // Set ul in body
    } else {      
      $input.after($autocomplete);
    }

    var highlight = function(string, $el) {
      var img = $el.find('img');
      var matchStart = $el.text().toLowerCase().indexOf("" + string.toLowerCase() + ""),
          matchEnd = matchStart + string.length - 1,
          beforeMatch = $el.text().slice(0, matchStart),
          matchText = $el.text().slice(matchStart, matchEnd + 1),
          afterMatch = $el.text().slice(matchEnd + 1);
      $el.html("<span>" + beforeMatch + "<span class='highlight'>" + matchText + "</span>" + afterMatch + "</span>");
      if (img.length) {
        $el.prepend(img);
      }
    };

    $autocomplete.on('click', 'li', function () {
      $input.val($(this).text().trim());
      $autocomplete.empty();
    });

    $input.on('keyup', function (e) {

      //if(timeout){ clearTimeout(timeout);}
      if(runningRequest) request.abort();      

      if (e.which === 13) {
        $autocomplete.find('li').first().click();
        return;
      }

      var val = $input.val().toLowerCase();
      $autocomplete.empty();

      //timeout = setTimeout(function() {

        runningRequest=true;

        request = $.ajax({
          type: 'GET', // your request type
          url: options.ajaxUrl,        
          success: function (data) {
            if (!$.isEmptyObject(data)) {
              // Check if the input isn't empty
              if (val !== '') {
                for(var key in data) {
                  if (data.hasOwnProperty(key) &&
                      key.toLowerCase().indexOf(val) !== -1 &&
                      key.toLowerCase() !== val) {
                    var autocompleteOption = $('<li></li>');
                    if(!!data[key]) {
                      autocompleteOption.append('<img src="'+ data[key] +'" class="right circle"><span>'+ key +'</span>');
                    } else {
                      autocompleteOption.append('<span>'+ key +'</span>');
                    }
                    $autocomplete.append(autocompleteOption);

                    highlight(val, autocompleteOption);
                  }
                }
              }
            }                    
          },
          complete:function(){
            runningRequest = false;
          }        
        });
      //},250);
    });
  }
  else 
  {
    $input.autocomplete({
      data: options.data
    });
  }
}
like image 102
friek108 Avatar answered Nov 15 '22 08:11

friek108


Building on top of @friek108's excellent answer, let's add the following features.

  • Close the autocomplete widget when clicking outside it.
  • Allow scrolling results with arrow keys and selecting with enter key.
  • Make AJAX calls only after a predefined minimum number of characters are entered.
  • Stop certain keys from triggering AJAX calls.

This adopts the timeout & ajax call cancelling features from @friek108's answer. You might want to check it first.

ajaxAutoComplete({inputId:'autocomplete-input',ajaxUrl:'/search/my-auto-complete-results'})

function ajaxAutoComplete(options)
{

    var defaults = {
        inputId:null,
        ajaxUrl:false,    
        data: {},
        minLength: 3
    };

    options = $.extend(defaults, options);
    var $input = $("#" + options.inputId);


    if (options.ajaxUrl){


        var $autocomplete = $('<ul id="ac" class="autocomplete-content dropdown-content"'
            + 'style="position:absolute"></ul>'),
        $inputDiv = $input.closest('.input-field'),
        request,
        runningRequest = false,
        timeout,
        liSelected;

        if ($inputDiv.length) {
            $inputDiv.append($autocomplete); // Set ul in body
        } else {
            $input.after($autocomplete);
        }

        var highlight = function (string, match) {
            var matchStart = string.toLowerCase().indexOf("" + match.toLowerCase() + ""),
            matchEnd = matchStart + match.length - 1,
            beforeMatch = string.slice(0, matchStart),
            matchText = string.slice(matchStart, matchEnd + 1),
            afterMatch = string.slice(matchEnd + 1);
            string = "<span>" + beforeMatch + "<span class='highlight'>" + 
            matchText + "</span>" + afterMatch + "</span>";
            return string;

        };

        $autocomplete.on('click', 'li', function () {
            $input.val($(this).text().trim());
            $autocomplete.empty();
        });

        $input.on('keyup', function (e) {

            if (timeout) { // comment to remove timeout
                clearTimeout(timeout);
            }

            if (runningRequest) {
                request.abort();
            }

            if (e.which === 13) { // select element with enter key
                liSelected[0].click();
                return;
            }

            // scroll ul with arrow keys
            if (e.which === 40) {   // down arrow
                if (liSelected) {
                    liSelected.removeClass('selected');
                    next = liSelected.next();
                    if (next.length > 0) {
                        liSelected = next.addClass('selected');
                    } else {
                        liSelected = $autocomplete.find('li').eq(0).addClass('selected');
                    }
                } else {
                    liSelected = $autocomplete.find('li').eq(0).addClass('selected');
                }
                return; // stop new AJAX call
            } else if (e.which === 38) { // up arrow
                if (liSelected) {
                    liSelected.removeClass('selected');
                    next = liSelected.prev();
                    if (next.length > 0) {
                        liSelected = next.addClass('selected');
                    } else {
                        liSelected = $autocomplete.find('li').last().addClass('selected');
                    }
                } else {
                    liSelected = $autocomplete.find('li').last().addClass('selected');
                }
                return;
            } 

            // escape these keys
            if (e.which === 9 ||        // tab
                e.which === 16 ||       // shift
                e.which === 17 ||       // ctrl
                e.which === 18 ||       // alt
                e.which === 20 ||       // caps lock
                e.which === 35 ||       // end
                e.which === 36 ||       // home
                e.which === 37 ||       // left arrow
                e.which === 39) {       // right arrow
                return;
            } else if (e.which === 27) { // Esc. Close ul
                $autocomplete.empty();
                return;
            }

            var val = $input.val().toLowerCase();
            $autocomplete.empty();

            if (val.length > options.minLength) {

                timeout = setTimeout(function () { // comment this line to remove timeout
                    runningRequest = true;

                    request = $.ajax({
                        type: 'GET',
                        url: options.ajaxUrl + val,
                        success: function (data) {
                            if (!$.isEmptyObject(data)) { // (or other) check for empty result
                                var appendList = '';
                                for (var key in data) {
                                    if (data.hasOwnProperty(key)) {
                                        var li = '';
                                        if (!!data[key]) { // if image exists as in official docs
                                            li += '<li><img src="' + data[key] + '" class="left">';
                                            li += "<span>" + highlight(key, val) + "</span></li>";
                                        } else {
                                            li += '<li><span>' + highlight(key, val) + '</span></li>';
                                        }
                                        appendList += li;
                                    }
                                }
                                $autocomplete.append(appendList);
                            }else{
                                $autocomplete.append($('<li>No matches</li>'));
                            }
                        },
                        complete: function () {
                            runningRequest = false;
                        }
                    });
                }, 250);        // comment this line to remove timeout
            }
        });

        $(document).click(function () { // close ul if clicked outside
            if (!$(event.target).closest($autocomplete).length) {
                $autocomplete.empty();
            }
        });
    }
}

Instead of appending results to the autocomplete widget one by one, I've appended all of them together with one single, long string to make the process faster. (Read a wonderful analysis of jQuery .append() method here).

like image 37
merovingienne Avatar answered Nov 15 '22 08:11

merovingienne


A little late to the party but thought this might help some people struggling with the same issue.

One way I found was to make a copy of the object returned by autocomplete() then using the built in data() function on the copy you iterate through results and add them in. A new copy is needed otherwise it just adds the extra values into the object (I'm sure there is some way to cleanup the object but all the usual methods failed for me).

var data = [{
  key : 'One'
}, {
  key : 'Two'
}, {
  key : 'Three'
}, {
  key: 'Twenty One'
}, {
  key: 'Thirty Five'
}, {
  key: 'Three Thousand'
}]; // Dummy data to emulate data from ajax request

function autocompleteData(field) {
  window.acSearch = $.ajax({
      url: 'somescript.php',
      type: 'GET',
      data: {
        key: function() {
          return $(field).val().trim();
        }
      },
      success: function(data) {
        $('.autocomplete-content').remove(); // Clear the old elements
        var newData = $.extend({}, $(field).autocomplete()); // Create copy of autocomplete object
        for (var i = 0; i < 20 && i < data.length; i++) {
          newData.data((data[i]["key"]), null); // Iterate through results and add to the copied autocomplete object (I set the limit to 20 as this is the limit I set below for the autocomplete)
      	}

        $(field).autocomplete({
        data: newData.data(),
        limit: 20, // Limit the number of results
      });
      $(field).keyup(); // This is just to get it to show the updated autocomplete results
    },
    error: function(){ // Ajax request will error as the URL is invalid so we will use the dummy data var created earlier and process the same function on error as we would on success - THIS IS NOT NEEDED (it's just for demonstrative purposed)
      $('.autocomplete-content').remove();
        var newData = $.extend({}, $(field).autocomplete());
        for (var i = 0; i < 20 && i < data.length; i++) {
          newData.data(data[i]["key"], null);
      	}

        $(field).autocomplete({
        data: newData.data(),
        limit: 20,
      });
      $(field).keyup();
    },
    complete: function(data) {
      setTimeout(function() {
        $(field).keyup()
      }, 250);

    }
  });
}

// Event handler on input field to trigger our function above and to clear any pending ajax requests
$('#autocompleteInput').on('input', function(e) {
  if (typeof acSearch != 'undefined') {
    acSearch.abort();
  }
  autocompleteData(this);
});
<head>
<link href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.98.0/css/materialize.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.98.0/js/materialize.min.js"></script>
<head>
<body>
<div class="container">
  <label for="autocompleteInput">Example Autocomplete</label>
  <div class="input-field">
    <input id="autocompleteInput" class="autocomplete">
  </div>
</div>
</body>
like image 22
Tim B Avatar answered Nov 15 '22 09:11

Tim B


I was able to get https://github.com/devbridge/jQuery-Autocomplete working with Materialize CSS. It seems to be actively maintained and has some nice features such as client-side caching of searches and it's quite fast.

The implementation was simpler and smoother than the other libraries I tried.

HTML element:

<div class="row">
  <div class="input-field col s12">
    <i class="material-icons prefix">textsms</i>
    <input id="autocomplete-input" type="text" class="validate">
    <label for="autocomplete-input">Autocomplete</label>
  </div>
</div>

In a <script></script> element at the bottom of your HTML file:

$("#autocomplete-input").devbridgeAutocomplete({
  serviceUrl:"/api/url",
  // Called when an item is selected from the list of results
  onSelect: function (selected) {
    console.log('You selected: ' + selected.value + ', ' + selected.data);
  },
  showNoSuggestionNotice: true,
  noSuggestionNotice: 'Sorry, no matching results',
});

By default the request is a GET, the querystring is query=<value of input field>, and your API should return an array of data in a JSON object as the value of an element named suggestions:

{
  "suggestions": ["Java", "Javascript"]
}

Full documentation: https://www.devbridge.com/sourcery/components/jquery-autocomplete/

like image 20
liquidki Avatar answered Nov 15 '22 09:11

liquidki