Lukas Naujokaitis
Lukas Naujokaitis

Reputation: 421

JQuery autocomplete doesn't work on the first keypress event (the source is an array)

I'm creating a search results autocomplete like Google has.

The results (or source) of the autocomplete is gonna be a dynamic array (the array was created using object accessed from the URL property in the $.ajax call) and the elements change very quickly on each keypress event.

What I mean is pretty simple:

  1. We have a variable autocompleteResults
  2. In the $.ajax success callback function we assign this variable to the empty array: autocompleteResults = []
  3. After this, we're executing the $.each() function:

    $.each(d.query.pages, function(i) {
      autocompleteResults.push(d.query.pages[i].title);
    });  
    
    // d.query.pages - JSON object from the success callback (success: function(d) {...}
    
  4. We're calling callback(autocompleteResults) at the end of the success callback

  5. Calling this function works and the array is returned correctly:

    getAutocompleteResults(function() {
      console.log(autocompleteResults);
    });
    
  6. Then in the same scope:

    $("#search").autocomplete({source: autocompleteResults},...);
    

    And it works only for the second time I type something.


Quick Overview using images:

  1. First look at the page after it was just reloaded: enter image description here
  2. Let's enter something into the input field ("a", for example): enter image description here
  3. As you can see in the previous image, we've got no autocomplete results ("a" result is just a table created by JS and has nothing to do with this problem). Let's try removing "a" character using a backspace button on the keyboard: enter image description here
  4. We got the same results as in the first image. But wait, something has changed. Let's try entering the same key as in the second image and see what we got: enter image description here
  5. Miracle! In the previous image, everything is how it should look like. The problem that this result is achieved only after the second try to input something into the field. It should work on the first try.

All my research results about this problem:

  1. Autocomplete works perfectly if it isn't placed in the keypress event function. However, it that case, we get the results once and then it stops responding to the future types (entering "a" - 5 results, entering "aa" - same results as it was "a" and it doesn't respond.

    I thought I should change the source again using the $( ".selector" ).autocomplete( "search", "" ); as stated here but I'm not sure if it would fix this problem.

  2. My code somehow stacks up AJAX requests and the same request is repeated many times. How could I fix this?: enter image description here

  3. Threads like these didn't help: jQuery UI autocomplete with JSON, jQuery autocomplete with callback ajax json, Jquery Autocomplete Search with Ajax Request as sourcedata, https://forum.jquery.com/topic/autocomplete-with-ajax-and-json-not-working.


To see the full project code go here: https://codepen.io/Kestis500/pen/PErONL?editors=0010

Or use a small snippet of the code (so you can see the most important part where the problem happens):

$(function() {
  var autocompleteResults;
  var changeText2 = function(e) {
    var request = $("input").val() + String.fromCharCode(e.which);
    $("#instant-search").text(request);

    var getAutocompleteResults = function(callback) {
      $.ajax({
        url: "https://en.wikipedia.org/w/api.php?action=query&format=json&gsrlimit=5&generator=search&origin=*&gsrsearch=" + $('#instant-search').text(),
        success: function(d) {

          autocompleteResults = [];
          $.each(d.query.pages, function(i) {
            autocompleteResults.push(d.query.pages[i].title);
          });

          callback(autocompleteResults);
        },
        datatype: "json",
        cache: false
      });
    };

    getAutocompleteResults(function() {
      console.log(autocompleteResults);
    });




    $("#search").autocomplete({
      source: autocompleteResults,
      response: function() {
        if (
          $("#instant-search")
          .text()
        ) {
          $("table").css("display", "table");
        }
      },
      close: function() {
        if (!$(".ui-autocomplete").is(":visible")) {
          $(".ui-autocomplete").show();
        }
      },
      appendTo: ".input",
      focus: function(e) {
        e.preventDefault();
      },
      delay: 0
    });


  };

  var changeText1 = function(e) {
    if (
      /[-a-z0-90áãâäàéêëèíîïìóõôöòúûüùçñ!@#$%^&*()_+|~=`{}\[\]:";'<>?,.\s\/]+/gi.test(
        String.fromCharCode(e.which)
      )
    ) {
      $("input").on("keypress", changeText2);
    }



    // THIS PART HAS NOTHING TO DO WITH THIS PROBLEM AND DELETING THIS WOULD MAKE A TABLE CREATED BY THE HTML TO NOT FUNCTION
    var getInputSelection = function(input) {
      var start = 0,
        end = 0;
      input.focus();
      if (
        typeof input.selectionStart == "number" &&
        typeof input.selectionEnd == "number"
      ) {
        start = input.selectionStart;
        end = input.selectionEnd;
      } else if (document.selection && document.selection.createRange) {
        var range = document.selection.createRange();
        if (range) {
          var inputRange = input.createTextRange();
          var workingRange = inputRange.duplicate();
          var bookmark = range.getBookmark();
          inputRange.moveToBookmark(bookmark);
          workingRange.setEndPoint("EndToEnd", inputRange);
          end = workingRange.text.length;
          workingRange.setEndPoint("EndToStart", inputRange);
          start = workingRange.text.length;
        }
      }
      return {
        start: start,
        end: end,
        length: end - start
      };
    };

    switch (e.key) {
      case "Backspace":
      case "Delete":
        e = e || window.event;
        var keyCode = e.keyCode;
        var deleteKey = keyCode == 46;
        var sel, deletedText, val;
        val = this.value;
        sel = getInputSelection(this);
        if (sel.length) {
          // 0 kai paprastai trini po viena o 1 ar daugiau kai select su pele trini
          $("#instant-search").text(
            val.substr(0, sel.start) + val.substr(sel.end)
          );
        } else {
          $("#instant-search").text(
            val.substr(0, deleteKey ? sel.start : sel.start - 1) +
            val.substr(deleteKey ? sel.end + 1 : sel.end)
          );
        }
        break;
      case "Enter":
        if (
          $("#instant-search")
          .text()
          .trim()
        ) {
          console.log("Redirecting...");
        }
        break;
    }

    if (!$("#instant-search")
      .text()
      .trim()
    ) {
      $("table, .ui-autocomplete").hide();
    }
  };

  $("input").on("keydown", changeText1);










  $("input").on("input", function(e) {
    $("#instant-search").text($("#search").val());
    if (
      $("#instant-search")
      .text()
      .trim()
    ) {
      $('table').css('display', 'table');
    } else {
      $("table").hide();
    }
  });
});
html,
body {
  height: 100%;
  width: 100%;
}

body {
  margin: 0;
  padding: 0;
  font-family: sans-serif;
  background-color: #000428;
  /* fallback for old browsers */
  background-image: -webkit-linear-gradient(to right, #004e92, #000428);
  /* Chrome 10-25, Safari 5.1-6 */
  background-image: linear-gradient(to right, #004e92, #000428);
  /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
}

.v-container {
  display: table;
  height: 100%;
  width: 100%;
}

.v-content {
  display: table-cell;
  vertical-align: middle;
}

.text-center {
  text-align: center;
}

h1 {
  color: #fff;
}

.input {
  overflow: hidden;
  white-space: nowrap;
}

.input input#search {
  width: calc(100% - 30px);
  height: 50px;
  border: none;
  font-size: 10pt;
  float: left;
  color: #4f5b66;
  padding: 0 15px;
  outline: none;
}

.input button.icon {
  border: none;
  height: 50px;
  width: 50px;
  color: #4f5b66;
  background-color: #fff;
  border-left: 1px solid #ddd;
  margin-left: -50px;
  outline: none;
  cursor: pointer;
  display: none;
  -webkit-transition: background-color .5s;
  transition: background-color .5s;
}

.input button.icon:hover {
  background-color: #eee;
}

.ui-autocomplete {
  list-style: none;
  background-color: #fff;
  -webkit-user-select: none;
  user-select: none;
  padding: 0;
  margin: 0;
  width: 100% !important;
  top: auto !important;
  display: table;
  table-layout: fixed;
}

.ui-helper-hidden-accessible {
  display: none;
}

.autocomplete-first-field {
  width: 15%;
  display: inline-block;
}

.autocomplete-second-field {
  width: 85%;
  display: inline-block;
  text-align: left;
  vertical-align: middle;
}

.three-dots {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

table {
  width: 100%;
  border-spacing: 0;
  border-collapse: collapse;
  display: none;
  table-layout: fixed;
}

table tr {
  background-color: #fff;
  -webkit-user-select: none;
  user-select: none;
}

tr:first-child {
  background-color: #ffc800;
  color: #fff;
}

table td,
.ui-menu-item-wrapper {
  padding: 10px 0;
}

td:nth-child(2) {
  width: 85%;
  text-align: left;
}

.ui-menu-item,
table {
  cursor: pointer;
}

:focus {
  outline: 0;
}

a {
  text-decoration: none;
  color: #fff;
  position: relative;
}

#random-article {
  margin-bottom: 5px;
}

.search-results {
  background: #fff;
  margin-top: 50px;
  border-left: 0 solid;
  cursor: pointer;
  -webkit-transition: border-left .5s;
  transition: border-left .5s;
}

.search-results h4,
.search-results p {
  margin: 0;
  padding: 10px;
}

.search-results:not(:first-child) {
  margin-top: 25px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!-->
<html class="no-js">
<!--<![endif]-->
<div class="v-container">
  <div class="v-content text-center">
    <div id="random-article"><a href="https://en.wikipedia.org/wiki/Special:Random">Click here for a random WikiSearch article! :)</a></div>
    <div class="input">
      <input type="text" id="search" placeholder="Search...">
      <button class="icon"><i class="fa fa-search"></i></button>

      <table>
        <tr>
          <td class="fa fa-search">
            <td id="instant-search" class="three-dots"></td>
        </tr>
      </table>
    </div>
  </div>
</div>

If you have any ideas how to fix this problem your help would be very appreciated :)

Upvotes: 1

Views: 2171

Answers (1)

Punith Jain
Punith Jain

Reputation: 1677

You have kept source: autocompleteResults, where you autocompleteResults is dynamically changing you should have done some thing like this

source: function (request, response) {
    getAutocompleteResults(function(data){
      response(data);
    });
  },

working code https://codepen.io/anon/pen/OzeQvw?editors=0010

Upvotes: 2

Related Questions