Can jqgrid formatter call an ajax/json method? - jqgrid

I'm using a formatter on one of my columns in jqGrid. The formatter uses some logic to decide what to display, and in some cases, it calls an ajax method. I've verified in Fiddler that the proper data comes back from my ajax calls. I've also verified in Chrome degbugging tools, that my variables are getting set properly. However, the sequence is all out of sorts, so the returned value in my column is "undefined". I can see in debugging tools' timeline that my getJSON calls are getting called after the jqgrid is already loaded.
I tried this first:
function myFormatter(cellvalue, options, rowObject)
{
if (rowObject[0] == something) {
$.getJSON('#Url.Action("MyAction", "MyController"), function (myResult) {
var myObject = myResult[0];
return myObject.myID;
});
}
else {
return "";
}
}
I also tried using an ajax call w/ async=false and I've tried different values for type and dataType, but the results are all the same:
function myFormatter(cellvalue, options, rowObject)
{
if (rowObject[0] == something) {
$.ajax({
type: 'POST',
url: '#Url.Action("MyAction", "MyController"),
async: false,
success: (function(result) {
var myObject = myResult[0];
return myObject.myID;
})
});
}
else {
return "";
}
}

It seems like you should be able to get this to work by using a synchronous AJAX call and a closure:
function myFormatter(cellvalue, options, rowObject)
{
var value = "";
if (rowObject[0] == something) {
$.ajax({
type: 'POST',
url: '#Url.Action("MyAction", "MyController"),
async: false,
success: (function(result) {
var myObject = myResult[0];
value = myObject.myID;
})
});
}
return value;
}
In general it is bad practice to use synchronous AJAX calls though, because they can block the UI for an undetermined amount of time. This case could be especially bad if you have a lot of rows in your grid, because one AJAX call will be made per row.
If you can, a better approach might be to either retrieve the ID's ahead of time and cache them locally on the page, or just do the formatting server-side. As always, use your best judgement.

Related

$.ajax in a for loop pulling from a php file

I have a key_gen.php file that contains a function to generate a random key. When executed, the php file gives back one key (tested and it works).
In my javascript file I have a click event on a button (that works), something like this:
$('#kg_btn').click(function(){});
Then, inside my click event I have a functions:
var get_key = function(){
for(var i = 0; i < kg_quantity; i++) {
$.ajax ({
url: "../keygen/gen_rkey.php",
type: "GET",
datatype: "json",
success: function(data) {
var j_obj = JSON.parse(data);
//alert("Success!");
prs = j_obj.test;
console.log(prs);
//add_key();
},
error: function(error) {
alert("Error! Can't send the data");
}
}); //ajax call ends
}
}
When I run this function once (by setting up the "Kg_quantity" variable to 1), every time I click my button I get a correct behavior. The result is a different key on the console.log per click.
If I set up the "kg_quantity" to any other number than 1 (for example: 3,9,10), I do get the console.log messages back, but the number generated is the same.
I was hoping that by inserting the ajax object into a for-loop would execute the ajax call several times. I tried to put the ajax call within a prototype function, as well, but I get the same result.
Edit: I tried adding a closure (as Ross suggested), but I get the exact same result.
Thanks.
AJAX is asynchronous. Your loop will finish before the first AJAX response most likely.
I would restructure that so the success response from the AJAX call will trigger the next iteration. Or if you're lazy you can just set async:false:
$.ajax ({
url: "../keygen/gen_rkey.php",
type: "GET",
async: false,
....
Or even better, put the strain on the server and reduce the back-and-forth so you get all your keys in one response:
url: "../keygen/gen_rkey.php?qty=" + kg_quantity,
UPDATE: Async design method:
function getKeys(max,cur) {
cur = cur || 1;
if (cur == max) {
return;
}
$.ajax({
....
success(function(data) {
// do stuff....
// recursive iteration
getKeys(max,cur + 1);
}
});
}
getKeys(5);

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);
}

Retain the context of JQuery ajax function

I have an old code which is dependant on JQuery 1.3.2 that uses the following ajax call
function ChangeContent(url, somepageobject) {
var xhrobj = $.ajax({
url: url,
context: somepageobject,
callback: doFurtherStuff,
success: function(data) {
somepageobject.html($(data));
this.callback.call(this.context[0], data, "ok"); // >> Code breaks here
}
});
return xhrobj;
}
Problem with the code above is that this.callback is null when I upgraded to JQuery 1.8.1, most importantly the ChangeContent function is being used in different places and is outside my control (its used as as an API for external users...etc). An example of the usage of the above is like this:
xhr_object = ChangeContent("/someurl, $("#resultContainer"));
function doFurtherStuff(responseText, statusText, XMLHttpRequest)
{
var identifier = '#' + this.id;
...
}
Notice that the doFurtherStuff must have the correct "this" object value which is the context specified in ChangeContent function. When I tried to use different deferred then() ...etc. functions in JQuery 1.8.1 to solve the above this.callback.call(this.context[0], data); problem after the upgrade the "this" object in the callback function had different value since I guess the new JQuery library handles that differently.
Is there anyway to fix the error above while limiting the change to ChangeContent function only as I try to avoid asking all users to change the way they call and handle call backs from that function?
When you add the context option, you are telling jQuery what this should be inside of the success callbacks. That means you can't access the options passed into the ajax request. Either don't supply a context, or pass in the callback manually.
function ChangeContent(url, somepageobject) {
var callback = doFurtherStuff;
var xhrobj = $.ajax({
url: url,
context: somepageobject,
success: function(data) {
somepageobject.html($(data));
callback.call(this[0], data, "ok"); // >> Code breaks here
}
});
return xhrobj;
}
Update:
If you want to instead continue using your code as-is, simply rename the context property.
function ChangeContent(url, somepageobject) {
var xhrobj = $.ajax({
url: url,
thecontext: somepageobject,
callback: doFurtherStuff,
success: function(data) {
somepageobject.html($(data));
this.callback.call(this.thecontext[0], data, "ok"); // >> Code breaks here
}
});
return xhrobj;
}

jQuery - AJAX request using both native XHR and flXHR (Flash) fallback - how to minimize code duplication?

I need to retrieve data via cross-domain XMLHttpRequest. To make this work in (almost) all browsers, I use native XHR first and, if that fails, flXHR.
The (working) code I currently have for this is as follows:
jQuery.support.cors = true; // must set this for IE to work
$.ajax({
url: 'http://site.com/dataToGet',
transport : 'xhr',
success: function(data) {
console.log('Got data via XHR');
doStuff(data);
},
error: function(xhr, textStatus, error) {
console.log('Error in xhr:', error.message);
console.log('Trying flXHR...');
$.ajax({
url: 'http://site.com/dataToGet',
transport : 'flXHRproxy',
success: function (data) {
console.log('Got data via flXHR');
doStuff(data);
},
error: function (xhr, textStatus, error) {
console.log('Error in flXHR:', error.message);
console.log('Both methods failed, data not retrieved.');
}
});
}
});
This feels like a lot of code duplication to me, especially in the success handlers. Is there a more efficient way to do this? I'd really prefer to make one $.ajax call that would try both transports in turn, instead of having to use the error handler to make the call a second time. It's not too bad in this example, but rapidly gets more complicated if the success handler is longer or if the success handler has to itself issue another $.ajax call.
I've created a jquery-specific and slimmed-down fork of flxhr that simplifies your code sample above. You can see an example of usage in the "Usage" section in the README.
https://github.com/b9chris/flxhr-jquery-packed
In particular, you don't want to waste time waiting for a standard CORS request to fail. It's easy to determine whether flxhr is necessary by testing $.support.cors upfront (no need to override it). Then just use flxhr explicitly where necessary.
Why don't you just wrap this in a function by itself? That's after all, how you end up reusing code. You can even pass functions as arguments to make sure that you don't have to repeat this code more than once.
To me this is pretty straight forward but maybe I've misunderstood.
function xhr(success) {
$.ajax({
success: success,
error: function() {
$.ajax({ success: success })
}
});
}
Then just pass the success handler once
xhr(function(data){/*magic*/});
Or if you wanna basically avoid redundant configuration of the ajax call use the first object as a template, like this:
function xhr(success) {
var ajaxParams = { success: success };
ajaxParams.error = function() {
$.ajax($.extend(ajaxParams, { transport: 'xhr' }));
}
$.ajax(ajaxParams);
}
I simplified the whole thing a bit, but I hope you get the point.
Edit
Reading that last bit, maybe this will give you some ideas... it's a variation of that last snippet.
function xhr(success) {
var ajaxParams = { success: success };
ajaxParams.error = function() {
var newParams = $.extend(ajaxParams, { transport: 'xhr' });
newParams.success = function() {
// do something
// arguments is a special array, even if no parameters were
// defined in any arguments where passed they will be found
// in the order they were passed in the arguments array
// this makes it possible to forward the call to another
// function
success.apply(this, arguments);
}
$.ajax(newParams);
}
$.ajax(ajaxParams);
}

Using jqGrid's inline-editing with RESTful urls?

I'm using jqGrid and would like to be able to use its built-in editing functions to make ajax calls to add/edit/delete. Our API uses RESTful verbs and urls like so:
verb url action
--------------------------------------------------------------
GET /api/widgets get all widgets (to populate grid)
POST /api/widgets create new widget
PUT /api/widgets/1 update widget 1
DELETE /api/widgets/1 delete widget 1
Is it possible to use the built-in ajax handling with these restrictions, or do I have to use local data (as outlined here & here) and manage the ajax calls myself? If it is possible, what properties do I set on the grid?
(ajaxRowOptions looks promising, but the documentation is a bit thin on how to use it.)
The usage of POST in Add form is by default.
The main idea for customizing jqGrid for RESTfull backend you can find in the old answer.
To use 'DELETE' in form editing if you use the Delete button of the navigator toolbar. Look at here or here. So you should use about the following settings:
$("#grid").jqGrid('navGrid', '#pager',
{edit: false, add: false, search: false}, {}, {},
{ // Delete parameters
mtype: "DELETE",
serializeDelData: function () {
return ""; // don't send and body for the HTTP DELETE
},
onclickSubmit: function (params, postdata) {
params.url = '/api/widgets/' + encodeURIComponent(postdata);
}
});
I use in the example above the encodeURIComponent function to be sure that if the id will have some special characters (spaces for example) if will be encoded so that the server part automatically received the original (decoded) data. Probably you will need to set some additional settings for the $.ajax call used during sending Delete request to the server. You can use for it ajaxDelOptions property.
You can make the above settings as your default settings. You can do this with respect of the following
$.extend($.jgrid.del, {
mtype: "DELETE",
serializeDelData: function () {
return ""; // don't send and body for the HTTP DELETE
},
onclickSubmit: function (params, postdata) {
params.url = '/api/widgets/' + encodeURIComponent(postdata);
}
});
The method onclickSubmit from the example above can be used for the Edit operations (in case of form editing) to modify the URL dynamically to /api/widgets/1. In many cases the usage of onclickSubmit in the above form is not possible because one need to use different base urls ('/api/widgets') different grids. In the case one can use
$.extend($.jgrid.del, {
mtype: "DELETE",
serializeDelData: function () {
return ""; // don't send and body for the HTTP DELETE
},
onclickSubmit: function (params, postdata) {
params.url += '/' + encodeURIComponent(postdata);
}
});
Then the usage of navGrid should be with explicit setting of url
$("#grid").jqGrid('navGrid', '#pager',
{edit: false, add: false, search: false}, {}, {},
{ // Delete parameters
url: '/api/widgets'
});
and
To use 'PUT' in inline editing you can set the following default jqGrid settings:
$.extend($.jgrid.defaults, {
ajaxRowOptions: { contentType: "application/json", type: "PUT", async: true },
serializeRowData: function (data) {
var propertyName, propertyValue, dataToSend = {};
for (propertyName in data) {
if (data.hasOwnProperty(propertyName)) {
propertyValue = data[propertyName];
if ($.isFunction(propertyValue)) {
dataToSend[propertyName] = propertyValue();
} else {
dataToSend[propertyName] = propertyValue;
}
}
}
return JSON.stringify(dataToSend);
}
});
The setting contentType: "application/json" is not required in general, but it could be required for some server technologies. The callback function serializeRowData from the example above sent the data as JSON. It is not required for RESTfull, but it's very common. The function JSON.stringify is native implemented in the most recent web browsers, but to be sure that it work in old browsers to you should include json2.js on your page.
The code of serializeRowData could be very simple like
serializeRowData: function (data) {
return JSON.stringify(data);
}
but I use above code to be able to use functions inside of the extraparam of the method editRow (see here and the problem description here).
The usage of the RESTfull URL (like /api/widgets/1) in the editRow is very simple:
$(this).editRow(rowid, true, null, null, '/api/widgets/' + encodeURIComponent(rowid));
To use it in case of the form editing you should use
grid.navGrid('#pager', {},
{ mtype: "PUT", url: '/api/widgets' });
and
$.extend($.jgrid.edit, {
ajaxEditOptions: { contentType: "application/json" }, // can be not required
onclickSubmit: function (params, postdata) {
params.url += '/' + encodeURIComponent(postdata.list_id);
}
});
It is important to remark that to get id from the postdata inside of onclickSubmit and need use postdata.list_id instead of postdata.id, where 'list' is the id of the grid. To be able to use different grid (<table>) ids one can use new non-standard parameter. For example, in the code below I use myGridId:
var myEditUrlBase = '/api/widgets';
grid.navGrid('#pager', {},
{ mtype: "PUT", url: myEditUrlBase, myGridId: 'list' },
{ // Add options
url: myEditUrlBase },
{ // Delete options
url: myEditUrlBase });
and the default setting defined as
$.extend($.jgrid.del, {
mtype: "DELETE",
serializeDelData: function () {
return ""; // don't send and body for the HTTP DELETE
},
onclickSubmit: function (params, postdata) {
params.url += '/' + encodeURIComponent(postdata);
}
});
$.extend($.jgrid.edit, {
ajaxEditOptions: { contentType: "application/json" }, // can be not required
onclickSubmit: function (params, postdata) {
params.url += '/' + encodeURIComponent(postdata[params.myGridId + '_id']);
}
});
In case of the usage of formatter:'actions' (see here and here) with inline or form editing (or a mix) you can use the same technique as described before, but forward all needed Edit/Delete option using editOptions and delOptions formatoptions.
The last your question was the usage of GET as /api/widgets. The classical RESTfull services will returns just array of all items as the response on /api/widgets. So you should just use loadonce: true and jsonReader which used methods instead of properties (See here and here).
loadonce: true,
jsonReader: {
repeatitems: false,
root: function (obj) { return obj; },
page: function () { return 1; },
total: function () { return 1; },
records: function (obj) { return obj.length; }
}
You should in some way include information which item property can be used as the id of grid rows. The id must be unique on the page. It your data has no id I would recommend you to use
id: function () { return $.jgrid.randId(); }
as an additional jsonReader method because per default the current version of jqGrid use sequential integers ("1", "2", "3", ...) as the row ids. In case of having at least two grids on the same page it will follow to the problems.
If the size of the data returned by 'GET' are more as 100 rows I would you recommend better to use server side paging. It means that you will add an additional method in the server part which support server side sorting and paging of data. I recommend you to read the answer where I described why the standard format of the input data are not RESTfull array of items and has page, total and records additionally. The new method will be probably not strange for the classical RESTful design, but the sorting and paging data in native or even SQL code can improve the total performance from the side of enduser dramatically. If the names of the standard jqGrid input parameters (page, rows, sidx and sord) you can use prmNames jqGrid parameter to rename there.
Also check out this excellent general tutorial for how to set-up jqGrid for RESTful URL's here, which also includes how the corresponding Spring MVC server portion would look.
I have managed to achieve it by implementing beforeSubmitCell event handler:
beforeSubmitCell: function(rowId) {
jQuery("#grid-HumanResource-table").jqGrid(
'setGridParam',
{
cellurl: s.getBaseModule().config.baseAPIUrl + "humanResource/" + rowId
}
);
},
I am using jqGrid 4.6 version.

Resources