select2 unable to search if data source is remote - jquery-select2

I am using select2 select box to populate and show some server data. Most of it works fine as I am able to get the results and populate the combo box. But when I type in the search box, the selection doesn't narrow down the the closest match.
I found the problem was because the backend URL doesn't support searching based on the string provided, while select2 keeps making multiple search request to backend, based on user entered text. The legacy backend code returns results in one shot, which is populated into the combobox the first time.
My question is, how do I get the the select box to focus to the closest matching result, without making multiple Ajax calls. I want the search to happen in the results which are already fetched.
Thanx to anyone helping me out on this.
My ajax call is like below, if this helps...
select2: {
placeholder: 'Select Next Plan Id',
allowClear: true,
id: function (item) {
return item.id;
},
ajax: {
type: 'GET',
dataType: "jsonp",
url: "http://172.16.7.248:8480/app/gui?action=1&type=11",
data: function (term, page) {
return { search: term };
},
results: function (data, page) {
return { results: data.aaData };
},
success : function(data, status, xhr) {
var html = "<option value=''>None</option>";
$.each(data.aaData, function(i, item) {
html += "<option data=" + JSON.stringify(item.id) + " value=" + item.id + "'>" + item.id + "</option>";
});
$('#nextplanid').html(html);
self.prop('data-loaded', 'true');
},
error : function(data, status, xhr) {
}
},
formatResult: function (item) {
return item.id;
},
formatSelection: function (item) {
return item.id;
},
initSelection: function (element, callback) {
return $.get('/getText', { query: element.val() }, function (data) {
callback(data);
});
}
},

Related

JqGrid searchoptions with select2 existing value

I'm trying to integrate select2 for JqGrid filter form. I'm using JqGrid min 4.6 & Select2 min 4.0.1. The filter works fine but I'm unable to retrieve the value that has been set through select2 once the filter form is closed and reopened. i.e. dataInit e1 does not return the existing value of the select input. I must be doing something wrong?
JqGrid Column Model:
{
name: 'CurrencyID', hidden: true, search: true, stype: 'select', searchtype: 'number', searchoptions: {
searchhidden: true,
sopt: ['eq', 'ne'],
dataInit: function (el) {
intiGridFilterSelecr2Field(el, paramFromView.CurrencyOptions);
}
},
searchrules: { required: true }
},
Parameters:
#section scripts{
<script>
var paramFromView = {
CurrencyOptions: {
searchURL: '#Url.Action("GetCurrency", "Controller")',
detailURL: '#Url.Action("CurrencyDetailsJson", "Controller")',
idField: 'CurrencyID',
txtField: 'Description'
}
};
</script>
}
Select2 Helper:
function intiGridFilterSelecr2Field(element, options) {
var comboPageSize = 15;
var quietMillis = 200;
var placeHolderText = 'Choose...'
var defaults = {
searchURL: '',
detailURL: '',
idField: '',
txtField: ''
};
var options = $.extend({}, defaults, options);
var select2Element = $(element);
select2Element.select2({
width: 'element',
minimumInputLength: 1,
placeholder: placeHolderText,
ajax: {
url: options.searchURL,
dataType: 'json',
quietMillis: quietMillis,
cache: false,
data: function (params) {
return {
name: params.term,
page: params.page,
pageSize: comboPageSize
};
},
processResults: function (data) {
var more = (data.page * comboPageSize) < data.total;
var resultsArr = [];
for (var i = 0; i < data.result.length; i++) {
resultsArr.push({ id: data.result[i][options.idField], text: data.result[i][options.txtField] });
}
return { results: resultsArr, more: more };
}
},
}).each(function (index, element) {
var idCombo = $(this);
// The problem is that idCombo.val() is always empty.
// element:select2-hidden-accessible
if (idCombo.val() != null && idCombo.val().length > 0) {
$.ajax(options.detailURL, {
data: {
id: idCombo.val()
},
dataType: 'json',
cache: false
}).done(function (data) {
var optselected = select2Element.find('option').filter(function () { return this.value == data[idField] && this.text == data[txtField] && this.selected })
if (optselected == undefined || optselected.length == 0) {
var $optionContact = $("<option selected></option>").val(data[idField].toString()).text(data[txtField]);
var toBeRemoved = select2Element.find('option').filter(function () { return this.value == data[idField] });
if (toBeRemoved != undefined) {
toBeRemoved.remove();
}
select2Element.append($optionContact).trigger('change.select2');
}
});
}
});
}
When the filter is being set...
When Loading the existing filter. How do I pass this CurrencyID = 1 to select2 helper?
Update:
With Oleg's answer, I updated my code as below.
{
name: 'CurrencyID', hidden: true, searchtype: 'number', search: true,
stype: "select", searchoptions: {
searchhidden: true,
sopt: ["eq", "ne"],
dataUrl: paramFromView.CurrencyOptions.searchURL,
buildSelect: function (data) {
var obj = jQuery.parseJSON(data);
var i, options = [];
for (i = 0; i < obj.result.length; i++) {
options.push("<option value='" + obj.result[i][paramFromView.CurrencyOptions.idField] + "'>" +
obj.result[i][paramFromView.CurrencyOptions.txtField] + "</option>");
}
return "<select>" + options.join("") + "</select>";
},
noFilterText: "Any",
selectFilled: function (options) {
setTimeout(function () {
$(options.elem).select2({
width: 'element',
});
}, 0);
}
},
searchrules: { required: true }
},
I'm almost there with what I wanted to achieve. However I'm still facing some difficulties.
When the filter is initially loaded, value is selected on the dropdown but query value is empty. i.e. if the user clicks on the find button soon after the filter form is loaded, no filter will be set.
I still cannot get select2 styles working.
I can demonstrate how to use select2 with free jqGrid fork of jqGrid, which I develop. I get the demo from the README of the old version 4.14.1 (the current released version is 4.15.3) and modified it to demonstrate the usage of select2.
The main part of the code could be
stype: "select",
searchoptions: {
sopt: ["eq", "ne"],
...
selectFilled: function (options) {
setTimeout(function () {
$(options.elem).select2({
width: "100%"
});
}, 0);
}
}
See https://jsfiddle.net/Lae6kee7/2/. You can try to choose an option in the filter toolbar in "Shipped via" column and the open the search dialog. You will see that the select2 will have the same option selected.
If you would load the data via Ajax request posted by select2 than your code will be much more complex as it could be. It's important to understand that such way is really required only for really large set of possible value. I means the number of items larger as 100000 items for example. On the other side, the most use cases required less as 1000 options. In the case it would be more effective to load all the data as options of select and then convert the select to select2. select2, which uses local select works much more quickly from the users point of view.
The code will be easier in my opinion if you will use dataUrl instead of ajax option of select2. You can use dataUrl to return from the server all different values, which can be used in select2 and to use buildSelect to build <select> from JSON data returned from the server. The demo https://jsfiddle.net/Lae6kee7/23/ demonstrates that. I made the demo for JSFiddle, which supports Echo service (see here), which allows to simulate server responses. Your real code should contains mostly only dataUrl, buildSelect and the code of selectFilled, which I included above.
Additionally, I'd recommend you to consider to use <datalist> (see here for example), which could be good alternative to select2. All modern web browsers contains native support of <datalist> and thus <datalist> works very quickly. Try to search in the first Client column of my demos. You will see control, which will be very close to select2. Additional advantage of <datalist>: one will be able not search for only exact predefined values like test10, test11 or test12, but for substrings like 1. Compare
with
or
with

jquery event.preventDefault() issues with IE 8

In my jquery autocomplete select function, I need to use the event.preventDefault() method to prevent the default ui.item.value from populating the input text box the autocomplete is wired too. This works great in Chrome, however in IE 8 (which is in use by a majority of our users) the .preventDefault() line throws the following error:
Unexpected call to method or property access
Here is the jQuery for good measure. Does anyone know of a work-around for this method in IE 8?
var tempResults = [];
$(function () {
$('#DRMCompanyName').autocomplete({
source: function (request, response) {
$.ajax({
url: '#Url.Action("compSearchByName", "AgentTransmission")',
type: 'GET',
dataType: 'json',
data: request,
success: function (data) {
tempResults = data;
response($.map(data, function (value, key) {
return {
label: value + " " + key,
value: key
};
}));
},
});
},
minLength: 2,
select: function (event, ui) {
event.preventDefault(); // <-Causing a problem in IE 8...
$('#DRMCompanyName').val(tempResults[ui.item.value]);
$('#DRMCompanyName').text(tempResults[ui.item.value]);
if ($('#DRMCompanyId').text() == '') {
$('#DRMCompanyId').val(ui.item.value);
$('#DRMCompanyId').text(ui.item.value);
}
}
});
});
You could use return false instead but as i said in comment: return false = event.preventDefault() + event.stopPropagation() But in your case, should fit your needs.

add image to jQuery UI autocomplete remote source with cache

I am using jQuery autocomplete remote source with caching. As per demo by jQUery UI website javascript of remote source with caching is
<script>
$(function() {
var cache = {},
lastXhr;
$( "#birds" ).autocomplete({
minLength: 2,
source: function( request, response ) {
var term = request.term;
if ( term in cache ) {
response( cache[ term ] );
return;
}
lastXhr = $.getJSON( "search.php", request, function( data, status, xhr ) {
cache[ term ] = data;
if ( xhr === lastXhr ) {
response( data );
}
});
}
});
});
</script>
Now I want to add some code to above javascript to display image in autocomplete list. There is an excellent example for displaying image in jQuery autocomplete such as:
http://jsfiddle.net/3zSMG/ but it does not include caching part.
I have been trying but NOT able to integrate this example into the existing remote with caching javascript. Any help would be very much appreciated. Many thanks.
FOLLOW-UP: As suggested by Andrew Whitaker, I have updated my script as follow:
<script>
$(function() {
var cache = {},
lastXhr;
$( "#birds" ).autocomplete({
minLength: 2,
source: function( request, response ) {
var term = request.term;
if ( term in cache ) {
response(cache[term]);
return;
}
lastXhr = $.getJSON( "search.php", request, function( data, status, xhr ) {
var results;
if ( xhr === lastXhr ) {
results = $.map(data, function(item) {
return {
value: item.value,
avatar: item.avatar
};
});
cache[term] = results;
response(results);
}
});
},
}).data("autocomplete")._renderItem = function (ul, item) {
if ( item.value != null ) {
if ( item.avatar != null) {
return $("<li/>")
.data("item.autocomplete", item)
.append("<a><img src='images/" + item.avatar + "' />" + item.value + "</a>")
.appendTo(ul);
}
else {
return $("<li/>")
.data("item.autocomplete", item)
.append("<a>" + item.value + "</a>")
.appendTo(ul);
}
}
};
});
And content of search.php:
<?php
require_once "config.php";
$term = trim(strip_tags($_GET['term']));//retrieve the search term that autocomplete sends
$qstring = "select id, subject as value, avatar from Suggests where subject LIKE '%".$term."%'";
$result = mysql_query($qstring);//query the database for entries containing the term
while ($row = mysql_fetch_array($result,MYSQL_ASSOC))//loop through the retrieved values
{
$row['value']=htmlentities(stripslashes($row['value']));
$row['id']=(int)$row['id'];
$row['avatar']=$row['avatar'];
$row_set[] = $row;//build an array
}
echo json_encode($row_set);//format the array into json data
?>
I am able to see images in autocomplete list. But there is an issue: After keying in few letters, autocomplete list is shown up, if I continue adding some more random letters to expect autcomplete list to disappear (because the userinput no longer matches) but it does not. Andrew's example work well. Is there anything wrong with my javascript? Trying to debug by Firebug, I got error: TypeError element is null.
The key to the image portion of the demos is overriding the _renderItem function to change the li's that are generated to suggest items. This usually means that your source is an array with objects that have a label and/or value property, as well as a property that allows you to display the image correctly inside of _renderItem.
The caching code just stores the search term and results in a hash and looks in that hash before hitting the server.
Using the JSFiddle you linked as an example, here's something using StackOverflow's API that does caching and images:
$(document).ready(function() {
var cache = {},
lastXhr;
$("#auto").autocomplete({
source: function(request, response) {
var term = request.term;
if (term in cache) {
response(cache[term]);
return;
}
lastXhr = $.ajax({
url: "http://api.stackoverflow.com/1.1/users",
data: {
filter: request.term,
pagesize: 10
},
jsonp: "jsonp",
dataType: "jsonp",
success: function(data, status, xhr) {
var results;
if (xhr === lastXhr) {
results = $.map(data.users, function(el, index) {
return {
value: el.display_name,
avatar: "http://www.gravatar.com/avatar/" + el.email_hash
};
});
cache[term] = results;
response(results);
}
}
});
},
delay: 500
}).data("autocomplete")._renderItem = function(ul, item) {
return $("<li />")
.data("item.autocomplete", item)
.append("<a><img src='" + item.avatar + "' />" + item.value + "</a>")
.appendTo(ul);
};
});​
Notes:
This example is more complex than the examples on jQueryUI's website because the data from the API has to be transformed (to have avatar/value properties). The important thing to note is that we're caching the received values after the $.map call transforms the result array.
This looks complicated but just remember the two guidelines at the top of the answer and you should be good.
Example: http://jsfiddle.net/rK7TS/
Here's what I did:
lastXhr = $.getJSON( action, request, function( data, status, xhr ) {
if( data && data !== null ) {
var results;
if( xhr === lastXhr ) {
results = $.map(data, function(item) {
return {
value: item.value,
avatar: item.avatar
};
});
cache[term] = results;
response(results);
}
} else {
response([{ value: 'No results found.', avatar: null}]);
}
});

jQuery UI Autocomplete how to implement Must Match in existing setup?

I have the following code and am curious as how to force the input to match the contents of the autocomplete:
$("#foo").autocomplete({
source: function( request, response ) {
$.ajax({
url: "index.pl",
dataType: "json",
data: {
type: 'foo',
term: request.term
},
success: function( data ) {
response( $.map( data.items, function( item ) {
return {
value: item.id
}
}));
}
});
},
minLength: 1
});
Answering this question for the benefit of anyone who stumbles upon this problem in 2013(yeah right!)
$("#my_input").autocomplete({
source: '/get_data/',
change: function(event, ui) {
var source = $(this).val();
var temp = $(".ui-autocomplete li").map(function () { return $(this).text()}).get();
var found = $.inArray(source, temp);
if(found < 0) {
$(this).val(''); //this clears out the field if non-existing value in <select><options> is typed.
}
}
});
Explanation:
The map() method creates a jQuery object populated with whatever is returned from the function (in this case, the text content of each <li> element).
The get() method (when passed no argument) converts that jQuery object into an actual Array.
Here is the original link of where I saw the solution.
I hope this helps. Thanks!

JQuery tiered Autocomplete that does not set data for the next tier

I have an Autocomplete for state and province names running. I want to pass the state abbreviation to the next cascade of Autocomplete. I created a hidden input and I want to set it to the abbreviation when the state name is selected, then I'll select the abbreviation for use in the next tier Autocomplete to limit the city query to one state only. At a prior step, I have country radio buttons setting a state limit in the .ajax data option, so you can see (generally) what I want to do in this state/city tier. I've tried several things, done a lot of Google searching and reading jQuery books, but I need some help. The stateProvince input box autocompletes and selects the full state name. The hidden input does not get set with the abbreviation. The alert is empty. I'm not sure about either the ajax success or the ajax select functions. How do I get the abbreviation (not the name) to be the value of the hidden input? and how can I then select it in the next tier's .ajax data option? Here is the relevant code.
selected HTML without tag symbols:
input type="text" id="stateProvince" name="stateProvince" size="50" maxlength="50"
input type="hidden" id="hiddenState" name="hiddenState"
Autocomplete source ajax;
$("#stateProvince").autocomplete
({
source: function( request, response )
{
//Pass the selected country to the php to limit the stateProvince selection to just that country
$.ajax(
{
url: "getStateProvince.php",
data: {
term: request.term,
country: $('input[name=country]:checked').val() //Pass the selected country to php
},
type: "POST",
dataType: "json",
success: function( data )
{
response( $.map( data, function( item )
{
return{
label: item.stateName,
value: item.name,
abbrev: item.stateAbbrev
}
}));
},
select: function( event,ui )
{
$(this).val(ui.item.value);
$("#hiddenState").$(this).val(ui.item.abbrev);
}
});
alert('|' + $("#hiddenState").val() + '|2nd'); //doesn't trigger here; shows no content when placed in the next tier Autocomplete
},
minLength: 2
});
JSON example returned from php:
[{"stateName":"Alabama","name":"Alabama","stateAbbrev":"AL"},{"stateName":"Alaska","name":"Alaska","stateAbbrev":"AK"}]
You've almost got it! I see the following problems:
select is an event on the autocomplete widget. Your code as it is right now is defining select as an option to the $.ajax({...}); call.
The alert statement that's failing is inside the source function to the autocomplete widget. I'm guessing it pops up right after you start typing. The reason it's empty is because it is not inside the select function (and it takes place before your AJAX call comes back successfully).
You can tweak your code as follows:
$("#stateProvince").autocomplete
({
source: function( request, response )
{
//Pass the selected country to the php to limit the stateProvince selection to just that country
$.ajax(
{
url: "getStateProvince.php",
data: {
term: request.term,
country: $('input[name=country]:checked').val() //Pass the selected country to php
},
type: "POST",
dataType: "json",
success: function( data )
{
response( $.map( data, function( item )
{
return{
label: item.stateName,
value: item.name,
abbrev: item.stateAbbrev
}
}));
}
});
},
select: function( event,ui )
{
$(this).val(ui.item.value);
$("#hiddenState").val(ui.item.abbrev);
alert('|' + $("#hiddenState").val() + '|2nd'); //doesn't trigger here; shows no content when placed in the next tier Autocomplete
},
minLength: 2
});
Note the location of the alert statement and the select event handler.
Here is the code for both the 2d state tier and 3d city tier of the Autocomplete.
//2d tier - stateProvince: autocomplete selection
//set the country for the 2nd tier to select stateProvince from only the country selected in the 1st tier
$("#stateProvince").autocomplete
({
source: function( request, response )
{
//Pass the selected country to the php query manager to limit the stateProvince selection to just that country
$.ajax(
{
url: "getStateProvince.php",
data: {
term: request.term,
country: $('input[name=country]:checked').val() //Pass the selected country to php
},
type: "POST", // a jQuery ajax POST transmits in querystring format in utf-8
dataType: "json", //return data in json format
success: function( data )
{
response( $.map( data, function( item )
{
return{
label: item.stateName,
value: item.name,
abbrev: item.stateAbbrev
}
}));
}
});
},
select: function( event,ui )
{
$(this).val(ui.item.value);
$("#hiddenState").val($(this).val(ui.item.abbrev));
//alert('|' + $("#hiddenState").val() + '|1st'); //shows [object,Object]
},
minLength: 2
});
//3d tier - city: autocomplete selection //
$( "#city" ).autocomplete
({
source: function( request, response )
{
$.ajax(
{
url: "http://ws.geonames.org/searchJSON",
dataType: "jsonp",
data:
{
featureClass: "P",
country: $('input[name=country]:checked').val(),
adminCode1: $("#hiddenState").val(), //"AK", //works-delivers only Alaska towns.
style: "full",
maxRows: 10,
name_startsWith: request.term
},
success: function( data )
{
response( $.map( data.geonames, function( item )
{
return{
label: item.name + (item.adminName2 ? ", " + item.adminName2 : "") + (item.adminName1 ? ", " + item.adminName1 : "") + ", " + item.countryName,
value: item.name + (item.adminName2 ? ", " + item.adminName2 : "") + (item.adminName1 ? ", " + item.adminName1 : "")
}
}));
}
});
},
select: function( event, ui )
{
$(this).val(ui.item.value);
//ui.item.option.selected = true;
//self._trigger( "selected", event, {item: ui.item.option});
}, //combo box demo uses this to bar illegal input. Other things are needed also. item.option is not defined
minLength: 2,
open: function()
{
$( this ).removeClass( "ui-corner-all" ).addClass( "ui-corner-top" );
},
close: function()
{
$( this ).removeClass( "ui-corner-top" ).addClass( "ui-corner-all" );
}
});

Resources