Select2 not showing results in dropdown with AJAX - javascript

I'm working on creating an interface for some system I am working on and I am required to write the Javascript around pre-existing PHP AJAX functions that are used elsewhere in the system (purely as the person who does our DB stuff is too busy to adapt the code to my needs).
Using Select2 I need to make a select field where the user searches for their name in the database and selects the one matching it so it can then be posted to another AJAX function later. I have done this with the following code:
//<select id="sltMyName" style="width: 100%;"></select>
$("#sltMyName").select2({
ajax: {
type: "POST",
url: "includes/php/report_searchPlayers.php",
delay: 250,
data: function (params) {
return {
q: params.term // search term
};
},
processResults: function (data) {
console.log({ results: data.split(",") });
return { results: data.split(",") };
}
},
minimumInputLength: 1
});
This should turn a returned string in the format of name1,name2,name3,name... into the required format for Select2 to display, however, it does not currently work. The dropdown just appears blank.
I've seen some questions referring to a text and id attribute, however, they are all in the context of a JSON string being returned, so I am not sure if they are required here and how to utilise them in this context. Furthermore, I cannot find any of this in the documentation.
Any suggestions? Thanks in advance.
Data returned:
Tommy,Jak_Tommy_Lee_Jones,Tommy_Shelby,Tommy_Balboner,TommyCZ,GA_Tommy,VA_Tommy,Tommy_Skrattar,Tommy_Knocker,Tommy_of_Elektro,Tommy_the_Destroyer,Old_Tommy,tommy_of_house_shelby,TommyDermo,TommyC,TommyCash_CZ,Tommyb69k,SA_Tommy,tommyfaster,Tommy_See,Tommy_de_Destroyer,Tommy_of_Whiteroses,TommyShelby,Templar_Intiate_Tommy,Templar_Initiate_Tommy,tommysuckspp,Tommy_the_Overweight

I think I've figured it out. The main issue is that the data that Select2 wants to use needs to be an array of objects with at least a property named id and a property named text. In the processResults function, just create those objects & then the dropdown will populate with the data from your URL!
I've noticed that it tries to filter the data as i type, but since the url I passed in does not accept the parameters, it just returns the same data each time. Let me know if you need a more robust example.
See my fiddle at https://jsfiddle.net/yp0rp2kw/3/ for a working solution.

Related

Select2 - Fetch data from backend and add options to selectbox

I'm currently trying to get select2 to work, however, I'm struggling a little bit right now. What I want to achive, is fairly simple: I fetch a JSON from a webserver which consists out of key/value pairs (one ID to one string). After fetching those, I want to create options out of those and add them to select2 so they user can select one of them.
I kept as close to the code in the documentation as humanly possible:
$('#catSearchBox').select2({
width: '500px',
ajax: {
url: "url/to/data",
dataType: 'json',
delay: 250,
cache: false,
data: function (params) {
return {
searchKey: params.term
};
},
processResults: function (data, params) {
$.each(data, function(catName, catId) {
var newOption = new Option(catId, catName, false, false);
$('#catSearchBox').append(newOption).trigger('change');
});
}
},
placeholder: 'Suche nach Kategorie ...',
minimumInputLength: 3
});
Now, everything here works up until the append. When I search for anything, the options are generated correctly, however, the append seems to fail as no options are displayed at all. It looks like this:
However, it's not because of an empty or invalid response, because the options are definitely created:
I'm kinda at a loss here, I don't see why my code is not working, especially since I kept as close as possible to the code in the documentation (https://select2.org/data-sources/ajax and https://select2.org/programmatic-control/add-select-clear-items).
Does anyone have any idea on how to solve this problem? If you need any additional information I might have missed out, please let me know.
processResults Is not used to append options to the select2. It is used to transform the response from the API. It should return a valid array of objects to feed select2.
Something like:
var newData = [];
$.each(data, function(catName, catId) {
newData.push({ id: catId, text: catName });
});
return { results: newData };
I believe the main problem is in your processResults function and Select2's expectations of it.
From what I was able to partly read and partly deduce from the documentation at https://select2.org/data-sources/ajax#transforming-response-data is that processResults is supposed to return an object containing a key results and those results should be an array of objects. Further down there were some examples and one of them shows that those objects contain 2 keys - id and text.
Moreover, the info box at the top of the docs it says that select2 will create <option> only for element that was chosen (it's done for performance reasons). This implies that you are not supposed to create the <option> elements yourself, but just return the data in the expected format and select2 will take care of the rest.
This is a fiddle based on your code: https://jsfiddle.net/mkilmanas/jkx2mwv5/1/ and it doesn't work, it throws an error of trying to access results of undefined.
This is another fiddle with analogous behaviour but done according to those findings from the documentation: https://jsfiddle.net/mkilmanas/3s0kupaw/5/
I'm not sure if this is the solution but there are 2 things in your code that I don't like so much.
Try this:
processResults: function (data, params) {
// First, remove all previous elements
$('#catSearchBox').html('');
$.each(data, function(catName, catId) {
var newOption = new Option(catId, catName, false, false);
$('#catSearchBox').append(newOption);
});
// Second, fire change when all elements are appended
$('#catSearchBox').trigger('change');
}

Multiple tables with individual search inputs in serverSide mode using DataTables

Application is using:
DataTables 1.10.18
jquery 3.2.1
PHP back-end
lodash 4.17.4
The application contains a web page which consists of multiple DataTables. Each of these uses serverSide: true (server-side mode) to obtain the data via an ajax endpoint which returns JSON data.
The tables are initialised as follows:
On page load several <table>'s are rendered. I'm using a jquery .each() to initialise the DataTable for each one:
$.each($('table'), function () {
$(this).DataTable({
processing: true,
serverSide: true,
searching: false,
ajax: {
data: {
table_id: $(this).attr('id')
},
url: '/get-data.json',
},
...
});
Each <table> has an ID. This is passed via ajax in the data: attribute. The endpoint /get-data.json returns data based on the table ID. In other words it knows "which table" the data should be obtained for based on this ID.
I want to be able to do searching on tables, but it must be done server-side. For this reason my initialisation code in (1) sets searching: false because this effectively disables the client-side search facility that DataTables provides (which we can't use in this instance as searching must be done server-side).
The problem I'm facing is how to create search inputs for each table, make an ajax call and update the appropriate table. I want the search to work in realtime after >=3 characters have been entered. Critical to this question is that 1 search input is responsible for searching 1 DataTable - it's not a search feature where the input can update "any/every table on the page" which is a commonly described pattern in other questions. 1 input : searching 1 table in this case.
My plan has been as follows - each table referenced in point (2) has an ID. I need to create unique inputs. So if I have tables with ID's #table1, #table2, #table3 I can easily create:
<input type="text" name="table1_search" id="table1_search">
<input type="text" name="table2_search" id="table2_search">
<input type="text" name="table3_search" id="table3_search">
I then detect if any changes have occurred on inputs:
$('input[type="text"]').bind("keyup change input",
function (e) {
// Ignore tab key for keyup event otherwise it'll fire an ajax request that does nothing useful.
if (e.which !== 9) {
processSearch.call(this);
} else {
e.preventDefault();
}
});
var prev_value = {};
function processSearch() {
var obj = $(this),
search_id = obj.attr('id'), // ID of input
search_value = obj.val(); // Value of input
// There's been no change to the field, ignore.
if (prev_value[search_id] === search_value) {
return;
}
prev_value[search_id] = search_value;
/* Wait until at least 3 characters have been entered, or user has cleared the input */
if (search_value.length >= 3 || (!search_value)) {
debouncedDraw({search_id: search_id, search_value: search_value});
}
}
The above code does what I need in terms of waiting for >=3 characters to be entered. I'm then executing a function called debouncedDraw which passes an object containing search_id and search_value. These refer to the input ID and value respectively, e.g. if I type "foo" into #table1_search then the object is:
{search_id: 'table1_search', search_value: 'foo'}
The debouncedDraw function looks like this. This is using lodash to limit the rate at which the function can fire. The point here is to stop it making needless ajax requests based on a question I asked a few years ago here: DataTables - kill ajax requests when a new one has started:
var debouncedDraw = _.debounce(function (opts) {
console.log(opts);
}, 500);
At the moment this will just console.log the object given above.
I'm unsure of the best way to proceed at this point. I need to re-run /get-data.json via ajax and then update the appropriate table.
I could access the request data and split the search_id based on the underscore to work out which table ID the data is for (e.g. table1_search targets #table1). I then need to write this data back to the appropriate table (#table1 in this case).
I can't help but think I'm going about this in a convoluted way and wondered if DataTables itself has any better ways of supporting this? It seems quite a basic requirement (multiple searchable tables in serverSide mode). But I can't find any posts which refer how to do this specifically.
All the "gotchas" I've experienced over the years is encapsulated in the snippet below. This is the basic template I always use when creating a new datatable. You can create as many datatables on a page as you need using this pattern.
Personally I would use a different ajax url path/route for each table so that table logic is in separate files in the backend... but it is possible to have all the data logic in a single backend file. I modified my usual template to suit that.
<script> //I usually put the script section in the head tag
var table_1; //declare your table var here and initialize as a datatable inside document ready below.
$(document).ready(function() {
table_1 = $('#table_1').DataTable( {
dom: "Bfrtip",
ajax: {
url: "/get-data.json?table=table_1", //add query string var for backend routing
type: "POST" //use POST to not have to deal with url encoding various characters
},
serverSide: true,
searchDelay: 2000, // use this instead of custom debounce
processing: true, // optional visual indicator that a search has been sent to backend
lengthMenu: [ 10, 25, 50, 75, 100 ], // define per page limits. first value will be the default
buttons: [
"pageLength" // per page drop down button. i usually override/extend the default button
],
columns: [ // column definitions of json data fields
{ data: "col_1", title: "ID", width: "1%" }, // width: 1% makes col width as small as possible
{ data: "col_2", title: "Label 2", visible:false }, //visible: false allows you access to field data without displaying to user
{ data: "col_3", title: "Label 3", render: function ( data, type, row ) { //render allows combining of fields into single column
return data + ' <small>('+row.col_2+')</small>'; // data will be col_3 value. row.col_2 is how you reference col_2 value
} },
{ data: "col_4", title: "Label 4", searchable:false }, //searchable: false set this field to not be used in search
],
rowId: 'col_1' //sets the tr row id to the value in this column. useful for DOM and other manipulation later
} );
}
</script>
<table id="table_1" class="table table-striped table-bordered table-sm" style="width:100%"></table>
<!-- If you define title attributes in col definitions above you don't need to create html table headers/footers. Just an empty table tag will do. -->
With this pattern you can utilize the built-in search input that comes with datatables for your use case with server-side processing on all tables.
There's a method behind my madness which I tried to document in the script comments on each line. Let me know if you have a question on something. I'm thinking this is bounty worthy.
For reference, when developing a new app using datatables I basically live on this page https://datatables.net/reference/option/
Edit 1
Inside your existing debounced drawTable function you could do something like this:
function drawTable(id) {
$('#'+id).DataTable().ajax.url( 'get-data.json?table_id='+id+'&foo=bar' ); //update ajax url of existing dt - if necessary
$('#'+id).DataTable().search(search_input_val).draw(); // fire ajax request with value from your custom search input
}
I'm fairly certain you will need to set "searching" to true though for this method to work.
Edit 2
Another way I just thought of, without using dt search. Pass all your data through modified url and load/reload.
$('#'+id).DataTable().ajax.url( 'get-data.json?table_id='+id+'&search=foo' ).load();
You could then get rid of all the debounce stuff if you use a button click listener or an onblur listener on the input field and fire the same command above.
Have you seen this? https://datatables.net/reference/api/%24.fn.dataTable.util.throttle()
I've never used it before, but it looks like a debounce. The example on the page shows it being used for .search()
I've implemented the following but would prefer a better solution as I don't think this is efficient and it definitely isn't elegant!
Taking the code from the question I modified the debounce function as follows:
var debouncedDraw = _.debounce(function (opts) {
// Destroy the existing DataTable.
$('#' + opts.search_region).DataTable().destroy();
// Re-run the drawTable method to get the new DataTable with the search results
drawTable(opts.search_region);
}, 500);
I introduced a function called drawTable which takes the ID of a <table> and runs the DataTables initialisation code. The ajax object was also modified to take into account anything entered into the search keywords input for the given table ID:
function drawTable(id) {
$id = $('#'+id); // Convert string ID to jquery identifier
$id.DataTable({
// DataTable initialisation code as per question
ajax: {
data: {
table_id: id,
keywords: $('input[name="keywords_' + id + '"]').val() // search keywords for table_id
},
url: '/get-data.json',
},
// ... code as per question
});
}
The $.each() was modified so that it detects the ID of each <table> on page load and calls drawTable:
$.each($('table'), function () {
drawTable($(this).attr('id'));
});
This "works" in that it creates each of the required DataTable's on page load, and also handles the search. The search input names were modified to the format: keywords_ plus the ID of the table, e.g. keywords_table1.
I don't think this is efficient because I'm having to call destroy on my DataTable. As per the docs:
This has a very significant performance hit on the page, since a lot of calculations and DOM manipulation is involved, so if you can avoid this, and use the API, that is very strongly encouraged!
However the reason I'm doing this is also as given in the same docs:
DataTables does not allow initialisation options to be altered at any time other than at initialisation time. Any manipulation of the table after initialisation must be done through the API
Well, I'm not using the client-side search feature as I'm having to do searching server-side. So I'm unsure whether manipulating the table via the API would actually help in this instance anyway.
Are there better ways of achieving this?

Select2 with remote data and pre-selecting custom options

I'm using Select2 4.0.3 with remote data and server paging and it is working fine. The data returned from the api has additional fields, something like this:
var data = {
id: 12345,
text: 'John Doe',
email: 'abc#123.com'
}
According to the documentation here is the example of how to pre-select an option when using remote data, which also works fine (with one issue as noted below):
$.ajax({
type: 'GET',
url: '/api/students/s/' + studentId
}).then(function (data) {
// create the option and append to Select2
var option = new Option(data.full_name, data.id, true, true);
studentSelect.append(option).trigger('change');
// manually trigger the `select2:select` event
studentSelect.trigger({
type: 'select2:select',
params: {
data: data
}
});
});
The documentation explains about the last bit:
Notice that we manually trigger the select2:select event and pass
along the entire data object. This allows other handlers to
access additional properties of the selected item.
In my case the additional property is the email field.
My question/problem is that for the above 'manually' appended option the select2 JSON data has only the id and text fields. If the user clicks the list and allows it to populate the data from the server then the data has the id, text, and email fields as it should.
The documentation statement explaining why the trigger was needed indicates to me that the data object returned from the api would be passed on (and presumably used), but this does not seem to be the case.
So in my case leaving out or including the trigger portion has no effect of the control, so am I misunderstanding the purpose of that code or is select2 not working as documented?
For Example, in the templateSelection function used to format the control selection, which is called when the append and trigger code is executed, the object passed to the function has only the id and text properties. In fact if the original data object had completely different properties, say name, value, and email, the data object passed to the function still has id and text only, which is the default object used by select2. This behaviour forced me to modify the object to conform to the {id, text} pattern.

Return all remote matches with typeahead without extra filtering

Can't seem to figure out a way to disable filtering with typeahead. Basically I just need the autocomplete (or rather drop-down search hint) functionality of it. I am doing a zip code search and resulting postal codes don't necessarily match the queried ones. How do I make it show all matches without doing extra filtering on those again?
Below is the code I have:
var dealers = new Bloodhound({
datumTokenizer: function (d) {
return Bloodhound.tokenizers.whitespace(d);
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
remote: {
url: '/form/find-dealer?postalCode=',
prepare: function (query, settings) {
settings.url += encodeURIComponent(query);
settings.type = 'POST';
settings.contentType = "application/json; charset=UTF-8";
return settings;
}
}
});
$('input[name=postalCode]').typeahead({
minLength: 3
}, {
name: 'dealers',
display: function (data) {
return data.title;
},
source: dealers.ttAdapter()
});
Note: I know it seems a bit awkward to do a zip code search that way, but the purpose of the designer was for users to search interchangeably by dealer name and zip code.
Additional Info: typeahead.bundle.js - v0.11.1
It seems showing all without any (matching) query isn't possible:
https://github.com/twitter/typeahead.js/issues/1308
Though some are trying it with minlength=0 like this:
https://github.com/twitter/typeahead.js/issues/1251
And it looks it was possible in an older version:
https://github.com/twitter/typeahead.js/pull/719
Btw the plugin is no longer being developed and the manual is incomplete. An improved one can be found at this fork: https://github.com/corejavascript/typeahead.js/tree/master/doc
Having said that, you may be better off with another autosuggest, or a plugin like select2, which does show results by default and can use external sources.
If you want send a query to AJAX & get data from your database & add all JSON result for result of typehead (You have a data filter with database & send clean data with AJAX & JSON, But type head has extra filtering & don't show anything or some of your data), You must do it :
Open bootstrap-typeahead.js & find
item.toLowerCase().indexOf(this.query.toLowerCase())
And replace it to :
item.toLowerCase().indexOf(item.toLowerCase())
Will you can show all results from Ajax JSON ...
Not pretty, since the library has been forked and no longer oficially supported by the creator, but this fix did it for me https://github.com/twitter/typeahead.js/pull/1212 . Basically when in remote mode, it returns all matches, which is actually the proper behavior as I see it.
This SO thread helped twitter typeahead ajax results not all shown

Select2 issue more than one result

I have a problem with Select2 where it will only allow one section in the <input>. I cannot use <select> with the multiple as it is a limitation of select2 apparently when using ajax for data results.
Sample code (json data is at the bottom) http://jsfiddle.net/YeEmP/
id: function(data){
return {
product_id: data.product_id
};
}
I suspect the problem is with the above code but can't be sure. When searching for a model eg D7000 it appears correctly like the example shown here
However if I search for another model number i.e D7100 it will say no results found but the ajax request returns the model just as if it was D7000.
If I search for the model it didn't recognise first it works, vice versa.
I'm not sure what I am doing wrong but my complete code can be found in the jsfiddle link, it might not work as my datasource is ajax in the example but I have passed the json array as a commented out section.

Categories

Resources