问题
I am using select2
with custom data adapter. All of the data provided to select2
is generated locally in web page (so no need to use ajax). As query
method can generate a lot of results (about 5k) opening select box is quite slow.
As a remedy, I wanted to use infinite scroll. Documentation for custom data adapter says that query
method should receive page
parameter together with term
:
@param params.page The specific page that should be loaded. This is typically provided when working with remote data sets, which rely on pagination to determine what objects should be displayed.
But it does not: only term
is present. I tried to return more: true
or more: 1000
, but this didn't help. I guess this is because, by default, infinite scroll is enabled iff ajax is enabled.
I am guessing that enabling infinite scroll will involve using amd.require
, but I am not sure what to do exactly. I tried this code:
$.fn.select2.amd.require(
["select2/utils", "select2/dropdown/infiniteScroll"],
(Utils, InfiniteScroll) =>
input.data("select2").options.options.resultsAdapter =
Utils.Decorate(input.data("select2").options.options.resultsAdapter, InfiniteScroll)
)
This is coffee script, but I hope that it is readable for everyone. input
is DOM
element containing select box - I earlier did input.select2( //options )
My question is basically, how do I enable infinite scroll without ajax
?
回答1:
Select2
will only enable infinite scroll, if ajax
is enabled. Fortunately we can enable it and still use our own adapter. So putting empty object into ajax
option will do the trick.
$("select").select2({
ajax: {},
dataAdapter: CustomData
});
Next, define your own data adapter. Inside it, inn query
push pagination
info into callback.
CustomData.prototype.query = function (params, callback) {
if (!("page" in params)) {
params.page = 1;
}
var data = {};
# you probably want to do some filtering, basing on params.term
data.results = items.slice((params.page - 1) * pageSize, params.page * pageSize);
data.pagination = {};
data.pagination.more = params.page * pageSize < items.length;
callback(data);
};
Here is a full fiddle
回答2:
Expanding on this answer to show how to retain the search functionality that comes with select2. Thanks Paperback Writer!
Also referenced this example of how to achieve infinite scrolling using a client side data source, with select2 version 3.4.5.
This example uses the oringal options in a select tag to build the list instead of item array which is what was called for in my situation.
function contains(str1, str2) {
return new RegExp(str2, "i").test(str1);
}
CustomData.prototype.query = function (params, callback) {
if (!("page" in params)) {
params.page = 1;
}
var pageSize = 50;
var results = this.$element.children().map(function(i, elem) {
if (contains(elem.innerText, params.term)) {
return {
id:[elem.innerText, i].join(""),
text:elem.innerText
};
}
});
callback({
results:results.slice((params.page - 1) * pageSize, params.page * pageSize),
pagination:{
more:results.length >= params.page * pageSize
}
});
};
Here is a jsfiddle
回答3:
I felt the answers above needed better demonstration. Select2 4.0.0 introduces the ability to do custom adapters. Using the ajax: {}
trick, I created a custom dataAdapter jsonAdapter
that uses local JSON directly. Also notice how Select2's 4.0.0 release has impressive performance using a big JSON string. I used an online JSON generator and created 10,000 names as test data. However, this example is very muddy. While this works, I would hope there is a better way.
See the full fiddle here: http://jsfiddle.net/a8La61rL/
$.fn.select2.amd.define('select2/data/customAdapter', ['select2/data/array', 'select2/utils'],
function (ArrayData, Utils) {
function CustomDataAdapter($element, options) {
CustomDataAdapter.__super__.constructor.call(this, $element, options);
}
Utils.Extend(CustomDataAdapter, ArrayData);
CustomDataAdapter.prototype.current = function (callback) {
var found = [],
findValue = null,
initialValue = this.options.options.initialValue,
selectedValue = this.$element.val(),
jsonData = this.options.options.jsonData,
jsonMap = this.options.options.jsonMap;
if (initialValue !== null){
findValue = initialValue;
this.options.options.initialValue = null; // <-- set null after initialized
}
else if (selectedValue !== null){
findValue = selectedValue;
}
if(!this.$element.prop('multiple')){
findValue = [findValue];
this.$element.html(); // <-- if I do this for multiple then it breaks
}
// Query value(s)
for (var v = 0; v < findValue.length; v++) {
for (var i = 0, len = jsonData.length; i < len; i++) {
if (findValue[v] == jsonData[i][jsonMap.id]){
found.push({id: jsonData[i][jsonMap.id], text: jsonData[i][jsonMap.text]});
if(this.$element.find("option[value='" + findValue[v] + "']").length == 0) {
this.$element.append(new Option(jsonData[i][jsonMap.text], jsonData[i][jsonMap.id]));
}
break;
}
}
}
// Set found matches as selected
this.$element.find("option").prop("selected", false).removeAttr("selected");
for (var v = 0; v < found.length; v++) {
this.$element.find("option[value='" + found[v].id + "']").prop("selected", true).attr("selected","selected");
}
// If nothing was found, then set to top option (for single select)
if (!found.length && !this.$element.prop('multiple')) { // default to top option
found.push({id: jsonData[0][jsonMap.id], text: jsonData[0][jsonMap.text]});
this.$element.html(new Option(jsonData[0][jsonMap.text], jsonData[0][jsonMap.id], true, true));
}
callback(found);
};
CustomDataAdapter.prototype.query = function (params, callback) {
if (!("page" in params)) {
params.page = 1;
}
var jsonData = this.options.options.jsonData,
pageSize = this.options.options.pageSize,
jsonMap = this.options.options.jsonMap;
var results = $.map(jsonData, function(obj) {
// Search
if(new RegExp(params.term, "i").test(obj[jsonMap.text])) {
return {
id:obj[jsonMap.id],
text:obj[jsonMap.text]
};
}
});
callback({
results:results.slice((params.page - 1) * pageSize, params.page * pageSize),
pagination:{
more:results.length >= params.page * pageSize
}
});
};
return CustomDataAdapter;
});
var jsonAdapter=$.fn.select2.amd.require('select2/data/customAdapter');
回答4:
I found it was just easier to hijack the ajax adapter rather than creating a whole new CustomAdapter like the above answers. The above answers don't actually seem to support paging because they all start from array, which doesn't support paging. It also doesn't support delayed processing.
window.myarray = Array(10000).fill(0).map((x,i)=>'Index' + i);
let timer = null;
$('select[name=test]')
.empty()
.select2({
ajax: {
delay: 250,
transport: function(params, success, failure) {
let pageSize = 10;
let term = (params.data.term || '').toLowerCase();
let page = (params.data.page || 1);
if (timer)
clearTimeout(timer);
timer = setTimeout(function(){
timer = null;
let results = window.myarray // your base array here
.filter(function(f){
// your custom filtering here.
return f.toLowerCase().includes(term);
})
.map(function(f){
// your custom mapping here.
return { id: f, text: f};
});
let paged = results.slice((page -1) * pageSize, page * pageSize);
let options = {
results: paged,
pagination: {
more: results.length >= page * pageSize
}
};
success(options);
}, params.delay);
}
},
tags: true
});
<link href="//cdnjs.cloudflare.com/ajax/libs/select2/4.0.7/css/select2.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.7/js/select2.full.min.js"></script>
<select name='test' data-width="500px"><option>test</option></select>
回答5:
Here's a shorter, searchable version for Select2 v4 that has paging. It uses lo-dash for searching.
EDIT New fiddle: http://jsfiddle.net/nea053tw/
$(function () {
items = []
for (var i = 0; i < 1000; i++) {
items.push({ id: i, text : "item " + i})
}
pageSize = 50
jQuery.fn.select2.amd.require(["select2/data/array", "select2/utils"],
function (ArrayData, Utils) {
function CustomData($element, options) {
CustomData.__super__.constructor.call(this, $element, options);
}
Utils.Extend(CustomData, ArrayData);
CustomData.prototype.query = function (params, callback) {
var results = [];
if (params.term && params.term !== '') {
results = _.filter(items, function(e) {
return e.text.toUpperCase().indexOf(params.term.toUpperCase()) >= 0;
});
} else {
results = items;
}
if (!("page" in params)) {
params.page = 1;
}
var data = {};
data.results = results.slice((params.page - 1) * pageSize, params.page * pageSize);
data.pagination = {};
data.pagination.more = params.page * pageSize < results.length;
callback(data);
};
$(document).ready(function () {
$("select").select2({
ajax: {},
dataAdapter: CustomData
});
});
})
});
The search loop is originally from these old Select4 v3 functions: https://stackoverflow.com/a/25466453/5601169
回答6:
This is not a direct answer: after struggling a lot with this for a while, I ended up switching to selectize. Select2's support for non-Ajax search, since version 4, is terribly complicated, bordering the ridiculous, and not documented well. Selectize has explicit support for non-Ajax search: you simply implement a function that returns a list.
来源:https://stackoverflow.com/questions/32756698/how-to-enable-infinite-scrolling-in-select2-4-0-without-ajax