Concise syntax for jquery-ui autocomplete when calling WCF web service - ajax

This example of using jquery-ui autocomplete with a remote web service has a nice concise syntax:
$( "#birds" ).autocomplete({
source: "search.php",
minLength: 2
});
Here search.php returns an array as follows:
[
{ "id": "Passer domesticus", "label": "House Sparrow", "value": "House Sparrow" },
...
]
I want to use a WCF web service, but the same syntax doesn't work because the array returned is wrapped in a 'd' container object:
{"d":
[
{ "id": "Passer domesticus", "label": "House Sparrow", "value": "House Sparrow" },
...
]
}
Of course I can get round this by writing code to look into the "d" container, something like the following (untested - could have typos):
$( "#birds" ).autocomplete({
minLength: 2
source: function (request, response) {
$.getJSON("search.svc/GetBirds", request, function (data, status, xhr) {
if (status == "success") response(data.d);
}
}
});
Is this the best I can do or is there some more concise syntax?
Ideally I'd like to be able to specify "source" as a url and have it work with responses that are returned with or without the "d" container.

In my mind you have two options.
The first is to create a helper function that will map the results for you. This is probably the best/easiest solution. Simple code:
$( "#birds" ).autocomplete({
minLength: 2
source: function (request, response) {
$.getJSON("search.svc/GetBirds", request, function (data, status, xhr) {
if (status == "success")
handleResponse(data); //you write this function
}
}
});
The second option is you can "monkeypatch" the AutoComplete plugin functions to override the default behavior.
So in your case you want to override the $.ui.autocomplete.prototype._initSource function. Fair warning here that you are basically overriding a core function in the UI library and if that library is ever updated your function will always override it.
// Create a closure so that we can define intermediary
// method pointers that don't collide with other items
// in the global name space.
function monkeyPatchAutocomplete() {
// don't really need this, but in case I did, I could store it and chain
var oldFn = $.ui.autocomplete.prototype._renderItem;
var requestIndex = 0;
$.ui.autocomplete.prototype._initSource = function() {
// whatever
console.log("Override method");
var self = this,
array, url;
if ($.isArray(this.options.source)) {
array = this.options.source;
this.source = function(request, response) {
response($.ui.autocomplete.filter(array, request.term));
};
} else if (typeof this.options.source === "string") {
url = this.options.source;
this.source = function(request, response) {
if (self.xhr) {
self.xhr.abort();
}
self.xhr = $.ajax({
url: url,
data: request,
dataType: "json",
autocompleteRequest: ++requestIndex,
success: function(data, status) {
console.log("Override success function, handling request");
if (this.autocompleteRequest === requestIndex) {
response(data); //you handle both types of data here
}
},
error: function() {
console.log("Override error function, handling request");
if (this.autocompleteRequest === requestIndex) {
response([]);
}
}
});
};
} else {
this.source = this.options.source;
}
};
}
// When DOM is ready, initialize.
$(document).ready(function() {
monkeyPatchAutocomplete();
$("#birds").autocomplete({
source: "http://jqueryui.com/demos/autocomplete/search.php",
minLength: 2
});
});
Then your code doesn't need to execute anything different, it just handles the differences and passes it along to the success method.
Here is a jsFiddle for this: http://jsfiddle.net/lemkepf/DAQ6s/5/ Note: the actual autocomplete wont work as the cross domain security is in place. You can open up firebug and see the console.debug lines fire when you start typing in the box.

Related

Kendo get data from remote service, do paging locally

Code:
var url = base_url + "/api/v1/users/getUsers";
var dataSource = new kendo.data.DataSource({
transport: {
read: function (options) {
$.ajax({
type: 'GET',
url:url,
dataType: 'json',
data: { searchTerm: $("#searchTerm").val().trim() },
success: function (result) {
options.success(result);
},
error: function (result) {
options.error(result);
}
});
}
},
schema: {
data: function (result) {
return result.model;
},
total: function (result) {
return result.model.length;
},
},
pageSize: 5
});
$("#matches").kendoListView({
dataSource: dataSource,
autoBind: false, // if set to false the widget will not bind to the data source during initialization.
template: kendo.template($("#matchesListViewTemplate").html())
});
$("#pager").kendoPager({
dataSource: dataSource,
autoBind: false
});
$(document).keypress(function (e) {
if (e.which == 13) {
e.preventDefault();
var searchTerm = $("#searchTerm").val().trim();
if (searchTerm.length < 1)
return;
dataSource.read();
dataSource.page(1); // makes another call to the remote service
}
});
Because data source is remote, when we call dataSource.page(1), kendo issues another call to the remote service. This behaviour is described in this so post:
If you are doing server side paging it should be enough doing grid.dataSource.page(1) since this will invoke the read exactly as you already realized.
What must I change so that after I search with new searchTerm, API call would be done only once and pager would go to page 1 without making another call?
I tried with dataSource.query() but still no luck? I hope I demonstrated enough.
Solution is to call dataSource.page(1) when dataSource.read() gets data / is done.
$(document).keypress(function (e) {
if (e.which == 13) {
e.preventDefault();
var searchTerm = $("#searchTerm").val().trim();
if (searchTerm.length < 1)
return;
dataSource.read().done(function() {
// in case remote service returns empty result set (but still http 200 code)
// page() makes another request (if data() is empty it makes another request)
// therefore we must check data length/total
if( dataSource.total() > 0)
dataSource.page(1);
}
});
If the read request's response have not arrived yet or if an error occurs, another read request is allowed (in order to fetch data). DataSource.read() makes asynchronously request and then dataSource.page(1) starts to execute. DataSource.page(1) function checks if there is any data read, if it's not it executes again read method - therefore we got 2 calls as you mentioned it. Because of asynchronously call this scenario may happen.

What is callback function in AJAX? How to implement a callback function in PreSaveAction function of SP2013 list NewForm?

I want to store the value of a variable and use it outside the ajax call. But being an asynchronous call it is giving me initial value of that variable. I am implementing my custom code for some validations in PreSaveAction function since I have to do validations on Save button click of SharePoint NewForm,Following is my code,
<script type="text/javascript">
var titleItem;
var flg=0;
var dataFromServer;
function PreSaveAction()
{
titleItem = $("input[title='Title']").val();
$.ajax({
url:"http://sp13dev:4149/Appraisals/_api/web/lists/GetByTitle('SkillMaster')/items?$select=Id,Title&$filter=Title eq '"+titleItem+"'" ,
type: "GET",
async: false,
headers: { "Accept": "application/json; odata=verbose",
"content-type": "application/json;odata=verbose",
"X-RequestDigest": $("#__REQUESTDIGEST").val() },
success: function (data) {
if(data.d.results.length>=1)
{
flg=1;
$("#labelTitle").html("Skill already exists. Please enter another name.");
}
else
{
flg=0;
$("#labelTitle").html("");
}
},
error: function (error) {
alert(JSON.stringify(error));
}
});
if(flg==1)
{
// $("#labelTitle").html("Skill already exists. Please enter another name.");
return false;
}
return true;
}
</script>
It seems like you have misplaced your return call. You should have your return placed inside your
if(data.d.results.length>=1)
statement inside the success callback function.

Kendo DataSource - Add additional data not working

My data source:
var asGridDataSource = new kendo.data.DataSource({
transport: {
read: {
url: function (options) {
DataService.Securities()
.done(function (secs) {
// notify the data source that the request succeeded
options.success(secs);
})
.fail(function (secs, testStatus, err) {
// notify the data source that the request failed
options.error(stats);
});
},
data: function () {
return {
id: $($searchCriteria).val(),
searchBy: $(searchByDDL).val(),
};
}
}
...
And the DataServices.Securities function:
Securities = function () {
return $.ajax({
url: "/api/securities"
});
};
If I replace the read url function with a simple url string (url: "/api/securities"), the additional data is added properly. However, when I do it as above, I get two Http requests:
/api/securities
/?id=5&searchBy=3
instead of a single request that should look like this:
/api/securities?id=5&searchBy=3
The only Kendo documentation I can find has an example of how to add data like this using the url string as described above, but not when using a function. Why am I not getting a single request?
Edit:
After working with this a bit more, I think I understand. I started out with this:
var asGridDataSource = new kendo.data.DataSource({
transport: {
read: {
url: "/api/securities",
data: function () {
return {
id: $($searchCriteria).val(),
searchBy: $(searchByDDL).val()
};
}
}
}
});
Doing this, and only this, results in a request like this: /api/securities?id=6&searchBy=3. I wanted to modify the above to use a function for the url instead of a hardcoded string. This is what the very top part of the original question is an attempt to do. However, the specification of the data function does not work. In the hardcoded url example, Kendo knows to get the id and searchBy from the page and append it to the url. But it has no way of knowing what to do with that same data when I use a function for the url. Or this is my guess.
I think what you want to be doing is make transport.read a function because you want to manually retrieve the data, not transport.read.url, which is for returning a url string when using the built-in transport:
transport: {
read: function (options) {
var params = {
id: $($searchCriteria).val(),
searchBy: $(searchByDDL).val(),
};
DataService.Securities(params)
.done(function (secs) {
// notify the data source that the request succeeded
options.success(secs);
})
.fail(function (secs, testStatus, err) {
// notify the data source that the request failed
options.error(stats);
});
}
}
To add optional parameters to the Securities method, you could do something along the lines of this:
DataService.Securities = function (params) {
var options = {
url: "/api/securities"
};
if (params) {
options.data = params;
}
return $.ajax(options);
};

jQuery autocomplete not displaying data returned from Ajax call

Below an Ajax call wrapped inside a jQuery autocomplete source function. checking the return value in Fiddler and also in Chrome's Network console, I can see that the data is being returned to the view and in the correct format.
However, the normal list of items that occur when the user starts typing do not appear. You can type as fast/slow for as little/long as you want and nothing will appear.
I've set a breakpoint in the controller method (this is an ASP MVC site) just to make sure that part of the program was functioning properly, and it fires every time.
I'm only a few weeks new to jQuery so any help would be greatly appreciated. Thanks!
$(function () {
$('#DRMCompanyId').autocomplete({
source: function (request, response) {
$.ajax({
url: '#Url.Action("compSearch", "AgentTransmission")',
type: 'GET',
dataType: 'json',
data: request,
success: function (data) {
alert(data);
response($.map(function (value, key) {
alert(value);
return {
label: value,
value: key
};
}));
}
});
},
minLength: 1
});
});
EDIT
I added a couple alerts to the code. The alert(data) will fire but the alert(value) will not.
Here is a copy of the returned json from the Chrome's debugging console
And here is the controller method that returns the key/value pair in the form of a Dictionary object.
XmlNamespaceManager nsmgr = new XmlNamespaceManager(xmlDoc.NameTable);
nsmgr.AddNamespace("m", "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata");
nsmgr.AddNamespace("d", "http://schemas.microsoft.com/ado/2007/08/dataservices");
Dictionary<string, string> companies = new Dictionary<string, string>();
foreach (XmlNode childNode in parentNode)
{
if (!String.IsNullOrWhiteSpace(childNode["content"].InnerText))
{
try
{
string name = childNode["title"].InnerText;
string id = childNode["content"].InnerText.Substring(0, 6);
companies.Add(id, name);
}
catch (Exception ex)
{
}
}
}
return Json(companies, JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
results = ex.InnerException.ToString();
}
return Json(results, JsonRequestBehavior.AllowGet);
The $.map function expects an array/object to enumerate on, as first argument. ref jQuery.map.
try changing
$.map(function (value, key) {
to
$.map(data, function (value, key) {
Regards.
The jQuery documentation: http://api.jquery.com/jQuery.map/ says that the $.map function expects two parameters; the first being an array. I think that you need to use the $.each method instead.
I also believe that in this context, response is a callback function that you are supposed to invoke with the data from AJAX as the parameter, as in response(data).
Shooting from the hip here, I think that your success handler should look about like this:
success: function (data) {
var x, array = [];
for(x in data) {
array.push({
label: data[x].value,
value: data[x].key
};
}
response(data);
}

Can a jQuery $.post call itself again from the callback?

The use case would be if the response it gets isn't what it wanted it can call itself again.
$.post(qrLoginAjax.ajaxurl, {
userID : '11234324'
},function( response ) {
if (response.userID != 'undefined'){
//do stuff
} else {
// call $.post again
}
});
How would I do that?
Thanks
You could do something like this:
var sendAjax = function() {
$.post('/foo', function(result) {
if (response.userID != 'undefined') {
// do stuff
} else {
// resend the AJAX request by calling the sendAjax function again
sendAjax();
}
});
};
sendAjax();
But sending AJAX requests like this seems like a bad design decision in my opinion. You should ensure that you don't get into an infinite recursion by using for example a number of retries counter.
You can make it a function, and call itself. For example with $.ajax, you can do this:
function do_stuff(){
$.ajax({
url: 'ajax.php',
success: function(data){
// Stuff
},
error: function(){
do_stuff();
}
});
}
This is the general principle of a recursive function, but it is highly recommended that you test conditions or set a maximum number of tries so it doesn't get stuck in an infinite loop.
Use this keyword of Javascript. As we all know, that refers to the object it belongs to. For example:
$.post(qrLoginAjax.ajaxurl,
{
userID : '11234324'
},
function( response ) {
if (response.userID != 'undefined') {
//do stuff
}
else {
$.post(this);
}
}
);

Resources