TarekRaafat / autoComplete.js

Simple autocomplete pure vanilla Javascript library.
https://tarekraafat.github.io/autoComplete.js
Apache License 2.0
3.95k stars 238 forks source link

data.src is called 2 times on keypress #72

Closed srinivas025 closed 5 years ago

srinivas025 commented 5 years ago

Hi, i found an issue when querying in the input box. When i do a keypress, my api is called 2 times which is present in data.src async function. this makes the user experience slow since the search results are not rendered unless i get response from the second call. Please let me know how i can fix this. Thanks

TarekRaafat commented 5 years ago

Hello @srinivas025,

Please try the below attached file, it has a quick fix and let me know how it went.

autoComplete.js.zip

argebynogame commented 5 years ago

Hi, firstly thanks for the great package. The file you sent lastly works for me to fix duplicated api calls. But 'afterend', 'ul' elements rendering more than once. For example if I type 4 times , it renders 4 different ul elements with id is 'autoComplete_results_list'. Any idea ?

TarekRaafat commented 5 years ago

Hello @argebynogame,

Thanks, man!

Great, I'm glad that we've solved the first issue api multiple calls.

Find the fixed build here

Let's check the second one.

Just kindly provide the below details to help replicate your issues.

Cheers! :)

argebynogame commented 5 years ago

Hello again @TarekRaafat , I will drop some screenshots and code snippet , I hope it works :) When I typed 'germ' , it finds Germany but every time when i type a letter new 'ul' element created. And there is no console error. Thanks in advance :)

Screen Shot 2019-09-17 at 09 22 31 Screen Shot 2019-09-17 at 09 15 45

document.querySelector("#autoComplete").addEventListener("keyup", function(event) {
            const query = document.querySelector("#autoComplete").value;
            if (query !== '') {
                const autoCompletejs = new autoComplete({
                    data: {                              // Data src [Array, Function, Async] | (REQUIRED)
                        src: async () => {
                            // API key token

                            // const token = "this_is_the_API_token_number";
                            // User search query

                            const query = document.querySelector("#autoComplete").value;
                            // Fetch External Data Source
                            const source = await fetch(`https://restcountries.eu/rest/v2/name/${query}`);
                            // Format data into JSON
                            let data = await source.text();
                            // console.log(data);
                            data = JSON.parse(data);
                            // Return Fetched data
                            return data;
                        },
                        key: ["name"],
                        cache: true
                    },
                    // query: {                               // Query Interceptor               | (Optional)
                    //     manipulate: (query) => {
                    //         return query.replace("pizza", "burger");
                    //     }
                    // },
                    sort: (a, b) => {                    // Sort rendered results ascendingly | (Optional)
                        if (a.match < b.match) return -1;
                        if (a.match > b.match) return 1;
                        return 0;
                    },
                    placeHolder: "Please type something ?",     // Place Holder text                 | (Optional)
                    selector: "#autoComplete",           // Input field selector              | (Optional)
                    threshold: 1,                        // Min. Chars length to start Engine | (Optional)
                    debounce: 600,                       // Post duration for engine to start | (Optional)
                    searchEngine: "strict",              // Search Engine type/mode           | (Optional)
                    resultsList: {                       // Rendered results list object      | (Optional)
                        render: true,
                        container: (source) => {
                            const resultsListID = "search_List";
                            source.setAttribute("id", "autoComplete_results_list");
                            return resultsListID;
                        },
                        destination: document.querySelector("#autoComplete"),
                        position: "afterend",
                        element: "ul"
                    },
                    maxResults: 10,                         // Max. number of rendered results | (Optional)
                    highlight: true,                       // Highlight matching results      | (Optional)
                    resultItem: {                          // Rendered result item            | (Optional)
                        content: (data, source) => {
                            let html = `<div class="d-flex align-items-center flex-row"><span><img width="60" alt="${data.match}" src="${data.value.flag}"></span> <span style="margin-left: 2em;">${data.match}</span></div>`;
                            $(source).html(html);
                        },
                        element: "li"
                    },
                    // noResults: () => {                     // Action script on noResults      | (Optional)
                    //     const result = document.createElement("li");
                    //     result.setAttribute("class", "no_result");
                    //     result.setAttribute("tabindex", "1");
                    //     document.querySelector("#autoComplete_results_list").appendChild(result);
                    // },
                    // onSelection: feedback => {             // Action script onSelection event | (Optional)
                    //     console.log(feedback.selection.value.image_url);
                    // }
                });
            }

        });```
TarekRaafat commented 5 years ago

Try removing the below lines and let me know how it went.

document.querySelector("#autoComplete").addEventListener("keyup", function(event) {
            const query = document.querySelector("#autoComplete").value;
            if (query !== '') {
argebynogame commented 5 years ago

Hey, if I remove these lines autoComplete try to run without a search query then it fails . it is a data.json() error :) And then I try to search nothing happen because of error.

Screen Shot 2019-09-17 at 10 37 38
srinivas025 commented 5 years ago

Hi @TarekRaafat, I used the used that you sent above and seeing an error on key press. Uncaught TypeError: _this2.dataSrc.then is not a function at run (autocomplete.js:365) at HTMLInputElement. (autocomplete.js:379) at autocomplete.js:315 I dont see any api call being called.. when i console logged _this2, i saw cached values in dataSrc array even though i have set cache to false. Please let me know if you need more details from me. Appreciate your effort. Thanks

TarekRaafat commented 5 years ago

Hello @srinivas02,

If you're using this.dataSrc it's changed to this.dataStream. If not please provide the below details to help replicate your issue.

Cheers! :)

srinivas025 commented 5 years ago

@TarekRaafat I used the new fixed code and getting the error Uncaught TypeError: Cannot read property 'then' of undefined at run (autocomplete.js:365) at HTMLInputElement. (autocomplete.js:379) at autocomplete.js:315 I dont see any api call being called here. This is my code

var autoCompletejs = new autoComplete({
                data: {
                    src: async function() {
                        // Loading placeholder text
                        if($("#autoComplete").is(':focus')){
                            console.log("focus");
                            $areaDocsSearch.find('.img-loader').show();
                        }
                        const query = document.querySelector("#autoComplete").value;
                        if (query.length == 1){
                            $areaDocsSearch.find('#autoComplete_results_list').removeClass('back-drop');
                        }
                        document
                            .querySelector("#autoComplete")
                            .setAttribute("placeholder", "Loading...");

                        const source = await fetch(
                            "/Help/searchReadme?search="+query
                        );
                        let results = source.results;
                        const data = await source.json();
                        // Post loading placeholder text
                        document
                            .querySelector("#autoComplete")
                            .setAttribute("placeholder", "Search help docs");
                        return data.results;
                    },
                    key: ["title"],
                    cache: false
                },
                sort: (a, b) => {
                    if (a.match < b.match) return -1;
                    if (a.match > b.match) return 1;
                    return 0;
                },
                placeHolder: "Search help docs",
                selector: "#autoComplete",
                threshold: 0,
                debounce: 300,
                searchEngine: "strict",
                highlight: true,
                maxResults: 10,
                resultsList: {
                    render: true,
                    container: source => {
                      source.setAttribute("id", "autoComplete_results_list");
                    },
                    destination: document.querySelector("#form_docs_search"),
                    position: "afterend",
                    element: "ul"
                },
                resultItem: {
                    content: (data, source) => {
                        $areaDocsSearch.find('.img-loader').hide();
                        source.innerHTML = data.match;
                        if ($areaDocsSearch.find('#autoComplete_results_list li').length == 0){
                            $areaDocsSearch.find('#autoComplete_results_list').removeClass('back-drop');
                        }
                        else{
                            $areaDocsSearch.find('#autoComplete_results_list').addClass('back-drop');
                        }
                    },
                    element: "li"
                },
                noResults: () => {
                    const result = document.createElement("li");
                    $areaDocsSearch.find('#autoComplete_results_list').removeClass('back-drop');
                    result.setAttribute("class", "no_result");
                    result.setAttribute("tabindex", "1");
                    result.innerHTML = "No Results";
                    document.querySelector("#autoComplete_results_list").appendChild(result);
                    $areaDocsSearch.find('.img-loader').hide();
                },
                onSelection: feedback => {
                    console.log(feedback)
                    const selection = feedback.selection.value.url;
                    window.open(selection, '_blank');
                    // Clear Input
                    document.querySelector("#autoComplete").value = "";
                }
            });

when i console logged _this2, i got this as response

data: cache: false key: ["title"] src: ƒ src() proto: Object dataStream: undefined dataType: true debounce: 300 highlight: true maxResults: 10 noResults: () => {…} onSelection: feedback => {…} placeHolder: "Search help docs" query: undefined resultItem: {content: ƒ, element: "li"} resultsList: {render: true, shadowRoot: document, view: ul#autoComplete_results_list, navigation: false} searchEngine: "strict" selector: "#autoComplete" sort: (a, b) => {…} threshold: 0 trigger: {event: Array(1), condition: false}

srinivas025 commented 5 years ago

Hi @TarekRaafat I finally made it to work by changing the run function to this

var run = function run(event) {
          if (!_this2.data.cache) {
            _this2.dataStream = _this2.data.src();
            _this2.dataType = _this2.dataStream instanceof Promise;
            if (_this2.dataType) {
              _this2.dataStream.then(function (response) {
                _this2.dataStream = response;
                exec(event);
              });
            } else {
              _this2.dataStream = _this2.dataStream;
              exec(event);
            }
          } else {
            exec(event);
          }
        };
TarekRaafat commented 5 years ago

@argebynogame try this code and let me know how it went.

const autoCompletejs = new autoComplete({
  data: {
    src: async function () {
      // Fetch External Data Source
      const source = await fetch("https://restcountries.eu/rest/v2/all");
      // Format data into JSON
      const data = await source.json();
      // Return Fetched data
      return data;
    },
    key: ["name"],
  },
});
TarekRaafat commented 5 years ago

@srinivas025 did you try first to switch the cache to true?

srinivas025 commented 5 years ago

@TarekRaafat i dont want to cache the data because i dont have a call to get all results.

TarekRaafat commented 5 years ago

Hello @srinivas025 & @argebynogame,

I've entirely re-factored the data handling section.

Now the data.src never calls theAPI if set not to be cached until you start typing in-order to provide the API a query.

Previously it used to initiate empty API calls that caused that issue in addition to a bug that existed in the non-cached data.src that has been solved as well thanks to @braco.

Kindly check the new build it should work as expected and let me know how it went.

Cheers! :)