Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cache remote (ajax) requests to improve performance #110

Closed
butsjoh opened this issue Jun 13, 2012 · 23 comments
Closed

Cache remote (ajax) requests to improve performance #110

butsjoh opened this issue Jun 13, 2012 · 23 comments

Comments

@butsjoh
Copy link
Contributor

butsjoh commented Jun 13, 2012

I think it would be a nice feature to implement something that the jquery tokeninput plugin is also doing. When you have configured select2 to get remote data we could cache the data coming back so if later we issue the same query again the data could be fetched from cache.

I am wondering if it would make sense to do it this way or loading all data in the beginning and configuring select2 with array data. (hence no more server loading) Any thought on this?

I can definitely help implementing this if it make sense to you.

@ivaynberg
Copy link
Contributor

im not sure what a good default way to cache is. every application works differently. select2 also allows paging, which in turn allows the user access to a much bigger data set and makes caching harder.

if you want you can already build caching by implementing it inside the query function.

if you can think of a good default way im all ears, we can build it into the default ajax helper.

@ProLoser
Copy link
Contributor

I vote for having people do it in the query function, any further 'automagic' will clutter the project, and you get full flexibility.

@butsjoh If you DO work out caching using the query function (which should be fairly easy) you should provide a demo function that can bee added to the docs.

@hemantthorat
Copy link

Select2 load data using ajax with caching in-place.

   $("#selIUT").select2({
        cacheDataSource: [],
        placeholder: "Please enter the name",
        query: function(query) {
            self = this;
            var key = query.term;
            var cachedData = self.cacheDataSource[key];

            if(cachedData) {
                query.callback({results: cachedData.result});
                return;
            } else {
                $.ajax({
                  url: '/ajax/suggest/',
                  data: { q : query.term },
                  dataType: 'json',
                  type: 'GET',
                  success: function(data) {
                    self.cacheDataSource[key] = data;
                    query.callback({results: data.result});
                  }
                })
            }
        },
        width: '250px',
        formatResult: formatResult, 
        formatSelection: formatSelection, 
        dropdownCssClass: "bigdrop", 
        escapeMarkup: function (m) { return m; } 
    }); 

I hope you find it helpful.

@jtsternberg
Copy link

Any examples for how to do this in selec2 v4 in the ajax.data callback?

@grekpg
Copy link

grekpg commented Nov 29, 2017

Any examples for how to do this in selec2 v4 in the ajax.data callback?

@alexweissman
Copy link
Contributor

Seems like this is still in demand, so I am reopening it.

@alexweissman alexweissman reopened this Dec 15, 2017
@alexweissman alexweissman changed the title Feature: Ability to cache data to save db hits when using remote data Cache remote (ajax) requests to improve performance Dec 15, 2017
@razvanioan
Copy link

+1

1 similar comment
@M-Yankov
Copy link

+1

@M-Yankov
Copy link

I forgot to share a workaround, how to cache the results with version 4.0.1
Example

@QDCSLG
Copy link

QDCSLG commented Sep 6, 2018

@M-Yankov Thanks for your example, but I found that using the query function will fire every time when user typed in regardless of the delay setting.

Also select2 is going to depreciate query function per document

I think transport function might be another good place to handle cache.

$(element).select2({
  ajax:{
       url:"http://some/url"
      //some other settings
      , transport: function (params, success, failure) {
                            if (params.dataType == "json")
                            {
                               //Build your cache key, typically with url and search term and page number
                                var cacheKey = getYourCacheKey;
                                if (__cache && __cache[cacheKey]) {
                                    var res = __cache[cacheKey];
                                    success(res);
                                    return {
                                        abort: function () {
                                            console.log("ajax call aborted");
                                        }
                                    }
                                }
                                else {
                                    var $request = $.ajax(params);
                                    $request.then(function (data) {
                                        __cache[cacheKey] = data;
                                        return data;
                                    })
                                    .then(success);
                                    $request.fail(failure);
                                    return $request;
                                }
                            }
                            else {
                                var $request = $.ajax(params);
                                $request.then(success);
                                $request.fail(failure);
                                return $request;
                            }
           }
      }
})

@M-Yankov
Copy link

M-Yankov commented Sep 6, 2018

@QDCSLG Nice share !
In my case, when opening the dropdown list without type anything, I wanted to prevent loading results again if they were already loaded.
The logic wasn't dependent on search text, but it can be modified similar to your example using keys:

// the type of the cacheData should be changed to object
var cacheData = this.options.get('cacheDataSource')
if (!cacheData[key]) {
   cacheData[key] = listItems;
   this.options.set('cacheDataSource', cacheData);
}

return cacheData[key];

The query will be removed, but using the transport function causes for me some unexpected UI behavior.

@pedrofurtado
Copy link
Contributor

This is much related with each application that uses select2. Some apps are good to cache request, others not. This kind of feature makes more sense to be a responsability of developer. There are some workarounds provided by community to handle with it, if you need this in your application 👍

@dmimms
Copy link

dmimms commented Sep 1, 2019

It sure would be nice if there was a flag to configure select2 to simply not destroy the ajax results once the user selects an option. Select2 is nice but I may have to stick with Selectize.js for this one select box.

@leenooks
Copy link

leenooks commented Mar 5, 2020

I cant find a working solution to keep the "last query" results. I agree, it would be great if the last query results could be retained even after the select list dialogue has been closed.

@fyrye
Copy link

fyrye commented Mar 5, 2020

@leenooks here is an adaptation of the transport method provided that will cache results "per query" based on the submitted term. Returning the previously queried term results from the cache.

jQuery(function($) {
   //initial cache storage as array
  var __cache = [];
  $(selector).select2({
    ajax: {
      url: url, //your url
      dataType: 'json',
      //cache result transport
      transport: function(params, success, failure) {
        //retrieve the cached key or default to _ALL_
        var __cachekey = params.data.q || '_ALL_';
        if ('undefined' !== typeof __cache[__cachekey]) {
          //display the cached results
          success(__cache[__cachekey]);
          return; /* noop */
        }
        var $request = $.ajax(params);
        $request.then(function(data) {
          //store data in cache
          __cache[__cachekey] = data;
          //display the results
          success(__cache[__cachekey]);
        });
        $request.fail(failure);
        return $request;
      }
    }
  });
});

If you want only the "last queried" results to be cached, removing the rest, change the _cachekey to unset all other caches.

jQuery(function($) {
   //initial cache storage as array
  var __cache = [];
  var __lastQuery = null;
  $(selector).select2({
    ajax: {
      url: url, //your url
      dataType: 'json',
      //cache result transport
      transport: function(params, success, failure) {
        //retrieve the cached key or default to _ALL_
        var __cachekey = params.data.q || '_ALL_';
        if (__lastQuery !== __cachekey) {
          //remove caches not from last query
          __cache = [];
        }
        __lastQuery = __cachekey;
        if ('undefined' !== typeof __cache[__cachekey]) {
          //display the cached results
          success(__cache[__cachekey]);
          return; /* noop */
        }
        var $request = $.ajax(params);
        $request.then(function(data) {
          //store data in cache
          __cache[__cachekey] = data;
          //display the results
          success(__cache[__cachekey]);
        });
        $request.fail(failure);
        return $request;
      }
    }
  });
});

You can also adapt these approaches to cache multiple element results or expire different caches based on a specific duration.

If you want to restore the last searched value when the select option is opened, you have to handle the closing event, so that it retains the searched value and reapplies the value during the open event.

$(document).on('select2:closing', '.select2-hidden-accessible', function() {
  var v = $('.select2-search__field').val();
  $(this).data('select2LastTerm', v);
});
$(document).on('select2:open', '.select2-hidden-accessible', function(evt) {
  var lastTerm = $(this).data('select2LastTerm');
  window.setTimeout(function() {
    //fix timing issue
    if (lastTerm && lastTerm.length) {
      var s = $('.select2-search__field');
      s.focus();
      s.val(lastTerm);
      s.trigger('keyup');
      s.trigger('input');
    }
  }, 0);
});

@leenooks
Copy link

leenooks commented Mar 6, 2020

Thank you for this - I probably wasnt clear what I was hoping for.

I dont necessarily want to cache queries (negating hitting the backend) - I'm hoping for the following:

  • Click in select box, and search - the search yields some results
  • Select a result, and the drop down box closes (to here, its normal functionality)

Then,

  • Realised that the wrong item was clicked, open up the dialog to select the correct item, without having to do a new search

IE: When you re-open the dialog (or bring the select into focus), you are currently presented with "Please enter x or more chars...", and the results of the last search are gone. Instead I would like to see the results of the last search still there so I can select from it, without having to type in a near search. (But optionally being able to perform a new search if required.)

Is there a way to achieve that?

@fyrye
Copy link

fyrye commented Mar 6, 2020

@leenooks this thread is specifically about caching the remote response (ajax).
However, my last code example provides exactly what you're looking for, by storing the searched value in the element's data-select2LastTerm and reapplies it when it is opened again. It does however use ES6, so you may want to convert let to var depending on your application requirements for browser support.

@leenooks
Copy link

leenooks commented Mar 6, 2020

Ahh, ok, I did try it, and it wasnt doing it. (And I'm using Brave as the browser.) I'll try again, thanks.

@fyrye
Copy link

fyrye commented Mar 6, 2020

@leenooks functional demo using standard JavaScript + jQuery https://jsfiddle.net/v10kwzL2/

@leenooks
Copy link

leenooks commented Mar 6, 2020

My bad, @fyrfe - it works perfectly, thank you :)

@fyrye
Copy link

fyrye commented Mar 6, 2020

@leenooks updated my example to fix a timing issue on faster systems and to support browsers that do not recognize the input event.

@PORRIS
Copy link

PORRIS commented Sep 17, 2020

Thank you very much @fyrye helped me a lot.
Add a seeker in the transform if they need it (ajax)

function matchCustom(params, data) {
    if ($.trim(params.data.search) === '') {
        return data;
    }
    // Do not display the item if there is no 'text' property
    if (typeof params.data.search === 'undefined') {
        return null;
    }
    var searching = $.map(data.data, function (obj) {
        var index = obj.descripcion.indexOf(params.data.search.toUpperCase());//change for you item obj.descripcion
        if(index >= 0) {
            return obj;
        }
    });

    if (typeof searching[0] === 'undefined') {
        searching[0]='';
    }
    return {data:[searching[0]],page:1};
}

var __cache = [];
var __lastQuery = null;
$(selector).select2({
    language: 'es',
    ajax: {
        url: url_resource,
        dataType: 'json',
        data: function (params) {
            return {
                search: params.term
            };
        },
        //cache result transport
        transport: function(params, success, failure) {
            //retrieve the cached key or default to _ALL_
            var __cachekey = params.data.q || '_ALL_';
            if (__lastQuery !== __cachekey) {
                //remove caches not from last query
                __cache = [];
            }
            __lastQuery = __cachekey;
            if ('undefined' !== typeof __cache[__cachekey]) {
                if('undefined' !== typeof params.data.search){
                    success(matchCustom(params,__cache[__cachekey]));
                    return;
                }
                //display the cached results
                success(__cache[__cachekey]);
                return; /* noop */
            }
            var $request = $.ajax(params);
            $request.then(function(data) {
                //store data in cache
                __cache[__cachekey] = data;
                //display the results
                success(__cache[__cachekey]);
            });
            $request.fail(failure);
            return $request;
        },
        processResults: function (data) {
            data.page = data.page || 1;
            return {
                results: data.data.map(function (item) { //my var is data.data
                    return {
                        id: item.id, //change for yoi item DB
                        text: item.descripcion//change for yoi item DB
                    };
                }),
                pagination: {
                    more:false
                }
            }
        },
        cache: true,
        delay: 300
    },
    placeholder: 'placeholder',
    disabled: false
});

@huwiler
Copy link

huwiler commented Oct 26, 2023

Thanks to @fyrye for his implementation. Here's mine that uses localstorage to cache results for 24 hours...

// how long to cache queries in user's browser
const cacheExpire = 24; // in hours
$(selector).select2({
    ajax: {
        url: url,
        dataType: 'json',
        delay: 300,
        transport: function(params, success, failure) {
            // custom transport to add 24 hour caching in user's browser
            const cacheKey = params.data.q || '_ALL_';
            const flatVal = localStorage.getItem(cacheKey) ?? '';
            const [query, strVal, dateStr] = flatVal.split('|');
            if (query && strVal && dateStr) {
                const date = new Date(dateStr);
                const expireDate = Date.now() - cacheExpire*1000*60*60;
                if (date?.getMonth && date > expireDate) {
                    const value = JSON.parse(strVal);
                    if (value) success(value); // return cached value
                    return;
                }
                localStorage.removeItem(cacheKey); // remove expired
            }
            const request = $.ajax(params);
            request.then(function(data) {
                const cacheVal = `${cacheKey}|${JSON.stringify(data)}|${(new Date()).toISOString()}`;
                localStorage.setItem(cacheKey, cacheVal);
                success(data);
            });
            request.fail(failure);
            return request;
        }
    },
    placeholder: 'placeholder',
    minimumInputLength: 3
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests