jqGrid, disabled edited row - jqgrid

In our application the user is able to edit multiple rows at the same time. When he clicks the save button all the rows are saved to DB, one by one.
The problem I have is that when a user fills in something wrong for example skipping a required field I want to cancel the saving from there and the user should be able to correct the error.
What I am missing in my solution is that I don't know how to disable a row in edit mode to original state after the row is saved with success. If I do restoreRow then the old values before editing are displayed.
My code (I keep my jqGrid in a variable called grid):
function saveGrid() {
var saveCollection = [];
//Collect all rows in editmode, the rows to be saved.
$.each(grid.getDataIDs(), function (index, id) {
var row = grid.getInd(id, true);
if (row !== false) {
if ($(row).attr('editable') === '1') {
saveCollection.push(id);
}
}
});
//Function to save single row.
function saveSingleRow(rowId) {
var success = false;
grid.saveRow(rowId, function () {
//Row successfully saved.
success = true;
//Here I want to restore the row somehow but not with the old values.
});
//If everything worked out, save next row if there are any left.
if (success) {
saveCollection = $.grep(saveCollection, function (obj) {
return obj !== rowId;
});
if (saveCollection.length > 0) {
return saveSingleRow(saveCollection[0]);
}
}
//If error or the last row, return success status.
return success;
}
//Check if there are any rows in editmode, then begin to save each row.
if (saveCollection.length > 0) {
if (!saveSingleRow(saveCollection[0])) {
//If something went wrong during saving cancel and return from this function.
return;
}
}
//Every row was successfully saved. Reload grid.
grid.trigger('reloadGrid');
}
As you can see, once an error occurs, will it be from the server or a validation error the save process stops and return false. But if this happens on lets say row number four then row one to three are already saved successfully but still in edit mode. The user will then have the feeling that these rows are not saved.
This is how I open up my rows for editing (inline editing). It happens when the user clicks the edit button in the toolbar:
var rows = $.grep(grid.getGridParam('selarrrow'), function () { return true; });
if (rows !== null && rows.length > 0) {
$.each(rows, function (i1, gr) {
if (gr !== null) {
grid.editRow(gr, false);
}
});
}
So is there a kind of "de-edit" function? I can't come up with a better main solution right now. Is there a way to check if there are any validation errors before starting the save process? Then I could check that and after that send all my rows to the server and save them within a transaction.
Appreciates any help! Thank you.
UPDATED
The solution is that there is no function that lock rows again after saving. This should be done automatically when saves are successful. In this case the problem is that when adding the callback function that runs after the save comes back to the client successfully you have to return true for this to happen.
So this is what was missing (the statement at the bottom: return true;):
grid.saveRow(rowId, function () {
//Row successfully saved.
success = true;
//Here I want to restore the row somehow but not with the old values.
return true;
});

It seems to me you should just add "not-editable-row" class to the row (<tr> element) after successful saving of the changes. See the answer for details and the demo.
UPDATED based on the discussion from comments: What you describe shows me that saving the rows works incorrect in your demo. After successful saving the saveRow closes the editing mode of the saved row. I created the demo which do what you describe, but uses the local editing only. In case of saving the data on the server all should work exactly in the same way.
In the comment you wrote "the grid is locked for editing if another user is currently in edit mode on another computer". In case of web application the locking of data in the database is a bad practice in my opinion. In breaking of connection to the client or in case of other errors you can easy have locking data in the database and no client connected. One use typically optimistic concurrency control instead. See here and here for details.

Related

jqGrid beforeProcessing stop loading and ReloadGrid

This is my example: enter link description here
How to reload automatically?
beforeProcessing: function (data, status, xhr) {
if (data.rows === '') {
$('#jqGridPreparate').jqGrid('clearGridData');
return false;
}
if (data.inputCol) {
$("td.ui-search-input input#id_prep").val('');
$("td.ui-search-input input#id_opisanie").val(data.inputCol);
var rplc = $.parseJSON($("#jqGridPreparate")[0].p.postData.filters);
for (var i=0; i < rplc.rules.length; i++) {
if (rplc.rules[i].field === 'prep') {
rplc.rules[i].field = 'opisanie';
}
}
$.extend($("#jqGridPreparate")[0].p.postData,{filters:JSON.stringify(rplc)});
$("#jqGridPreparate")[0].triggerToolbar(); // not WORK
}
}
I'm not sure that I full understand what you want to implement. I can guess that you want to apply the filter if some additional information will returned by from the server.
It seems that you implemented some custom agreement between jqGrid and the server about returned data. rows part of the server response should be array of items. In your case the server could set empty string as the value of rows instead. You try to use inputCol property of the server response to force filtering of previously loaded data. You makes some custom modifications of existing filter, you fill some fields of the filter toolbar and finally you try to use triggerToolbar. The code is very unclear and it contains a lot of assumptions which you don't described directly. It's not clear for me whether you want to start local filtering or jqGrid should send new request to the server with modified filter.
In any way, if you want to start some kind of reloading/filtering inside of other callback function, you shuld take in consideration some basic rules. First of all the method triggerToolbar or triggering reloadGrid works synchronously. It means that jqGrid first makes triggerToolbar, which makes reloadGrid, till the end and then will be executed the next line of code after $("#jqGridPreparate")[0].triggerToolbar();. Because of that it's strictly recommended to place all calls of triggerToolbar or reloadGrid inside of setTimeout body. It allows to process the current callback til the end and only then to make reloading. If the response from the server contains your custom information instead of typical jqGrid data then you should return false from beforeProcessing to stop the standard processing.
If you can construct postData.filters then you don't need to use triggerToolbar to apply the filter. Instead of that you need just set search: true parameter of jqGrid additionally and just trigger reloadGrid.
The corresponding code could be about the following:
beforeProcessing: function (data, status, xhr) {
var $self = $(this), p = $self.jqGrid("getGridParam");
if (data.rows === "") {
$self.jqGrid("clearGridData");
return false; // don't process the data by jqGrid
}
if (data.inputCol) {
var i, rplc = $.parseJSON(p.postData.filters);
for (i = 0; i < rplc.rules.length; i++) {
if (rplc.rules[i].field === "prep") {
rplc.rules[i].field = "opisanie";
}
}
p.postData.filters = JSON.stringify(rplc);
setTimeout(function () {
// apply the filter
p.search = true;
$self.trigger("reloadGrid", [{page: 1}]);
}, 50);
return false; // don't process the data by jqGrid
}
}

Kendo DataSource Single-Row Read

I have a mobile app that receives a push notification, when it does, I know there is a row that that needs to be updated. I have tried two methods for retrieving just that one:
var row = data_source.get(<id>);
row.dirty = true;
data_source.sync();
This tries to fire an UPDATE, which I could shoe-horn into doing what I want, but it's conceptually wrong.
The other option that I have tried:
data_source.read( { id : <id> } );
Which fires off a READ request, with the ID in options.data. When I try to hook this up, kendo complains about not getting an array back, and when I make the response into an array, it doesn't seem to work either.
How am I supposed to do this? GET it outside the context of the DataSource, and set the relevant parts, and then set the dirty bit to false?
Kendo doesn't do single row read out of the box. If its not feasible to do full read(), you have to do what you proposed. Try following :
1) make your server side read method to accept requests with or without id, without to read all items, with to read your one row.
2) use parameter map together with isPushNotificaton flag to control the read request params
parameterMap: function(data, type) {
if (type == "read") {
if (isPushNotificaton)
return { id : yourId };
else
return { id : 0}; // get all
}
}
3) use requestEnd to decide how to deal with read result
requestEnd: function(e) {
var type = e.type;
if (e.type == 'read') {
// examine the response(by count/add additional flags into response object)
var isFullReadResponse = ...;
if (!isFullReadResponse) {
// DIY
e.preventDefault();
UpdateSingleRow(response, datasource.data());
}
}
}
4) Implement UpdateSingleRow method as you proposed - .."and set the relevant parts, and then set the dirty bit to false", (Refresh a single Kendo grid row)

How can I write a wait_for_ajax call in selenium where the web page uses XMLHttpRequest based AJAX calls?

I have a select box which calls a XMLHttpRequest based AJAX call to populate another select box.
I want selenium to wait till the select box is populated. I have to wait for XMLHttpRequest's readyState variable to have value 4 which means data populated.
In Java: You could wait for the readyState change like this:
int tryCount = 0;
boolean desiredResponseReceived = false;
while (desiredResponseReceived == false && tryCount < 20) {
String readyState = (String) js.executeScript("return xhr.readyState;");
if (readyState.equals("4")) {
desiredResponseReceived = true;
}
else {
Thread.sleep(250);
tryCount++;
}
}
if (desiredResponseReceived == false) {
driver.quit();
}
One problem with looking for the ready state is, the page could be making more than one XMLHttpRequest. And you won't know which one is which. Even if you check for it immediately after clicking the relevant button (for example) even that could fire off multiple requests. And there's no telling which would return first.
Another option in your case might be to instead wait for the options in the select box to change. So in the code above, you might replace
String readyState = (String) js.executeScript("return xhr.readyState;");
if (readyState.equals("4")) {
with
List<WebElement> options = driver.findElements(By.tagName("option"));
if (options.size() > 1) {
Or if you don't know what minimum number of options to expect, you could test that an option with a particular attribute is in the List. Or something like that.

ASP.NET MVC 3 Unobtrusive Jquery Validate not showing custom error messages more than once

So, I took some code from this Microsoft provided Example which allows me to use the jquery validate unobtrusive library to parse validation error message returned from my server and display them in the UI. They have a video demonstrating this. So, here is the piece of Javascript code I'm using:
$.validator.addMethod("failure", function () { return false; });
$.validator.unobtrusive.adapters.addBool("failure");
$.validator.unobtrusive.revalidate = function (form, validationResult) {
$.removeData(form[0], 'validator');
var serverValidationErrors = [];
for (var property in validationResult) {
//var elementId = property.toLowerCase();
var item = form.find('#' + property);
if (item.length < 1) { item = form.find('#' + property.replace('.', '_')); }
serverValidationErrors.push(item);
item.attr('data-val-failure', validationResult[property].join(', '));
jQuery.validator.unobtrusive.parseElement(item[0]);
}
form.valid();
$.removeData(form[0], 'validator');
$.each(serverValidationErrors, function () {
this.removeAttr('data-val-failure');
jQuery.validator.unobtrusive.parseElement(this[0]);
});
};
So then after a AJAX form post in the handle error function I would do something like this:
$.validator.unobtrusive.revalidate(form, { 'PhysicalAddress.CityName': ['You must select a valid city'] });
Where PhysicalAddress.CityName is the name of my viewmodel property and html input field. So, it knows to put the validation message next to the correct html element.
This works 1 time. Then when they hit submit again and my code calls the unobtrusive.revalidate method again.. it doesnt work. It only shows the validation message one time then after that the validation message disappears for good.
Does anyone have any idea as to why this might be happening?.. I stepped through the revalidate method and no errors were thrown and everything seems like it should work.. but the unobtrusive library for some reason is not re-binding the validation error message.
Thanks
Probably this behavior depends on a known problem of the jQuery validation plugin: dynamically adding new validation rules for elements works just once! Further attempts are rejected because the plugin think they are a duplicated attempt to define the already defined rules.
This is the reason why the $.validator.unobtrusive.parse doesn't work when you add newly created content (when for instance you add a new row to a collection of items). There is a patch for the $.validator.unobtrusive.parse that you might try to apply also to the revalidate function....but it is better to rewrite it from scratch in a different way. The revalidate function usse the validation plugin just to place at the right place all validation errors, then it tries to reset the state of the validation plugin. However, deleting the validator object from the form is not enough to cancel all job done since there is another object contained in the form.data('unobtrusiveValidation'), where form is a variable containing the form being validated...This data are not reset by the revalidate function...and CANNOT be reset since resetting them would cause the cancellation of ALL client side validation rules.
Maybe this problem has been solved in the last version of the validation plugin, so try to update to the last version with nuget.
If this doesn't solve your issue I can pass you an analogous function implemented in a completely different way(it mimics what the server does on the server side to show server side errors). It will be contained in the upcoming version of the Mvc Controls toolkit. However, if you give me a couple of days (I will be very busy for 2 days) I can extract it from there with its dependencies so you can use it. Let me know if you are interested.
Below the code I promised. It expects an array whose elements are:
{
id:id of the element in error
errors:array of strings errors associated to the element
}
It accepts several errors for each element but just display di first one for each element
id is different from the name because . [ ] an other special char are replaced by _
You can transform name into id on the sever with
htmlName.Replace('$', '_').Replace('.', '_').Replace('[', '_').Replace(']', '_');
or on the client in javascript with:
name.replace(/[\$\[\]\.]/g, '_');
function remoteErrors(jForm, errors) {
//////////
function inner_ServerErrors(elements) {
var ToApply = function () {
for (var i = 0; i < elements.length; i++) {
var currElement = elements[i];
var currDom = $('#' + currElement.id);
if (currDom.length == 0) continue;
var currForm = currDom.parents('form').first();
if (currForm.length == 0) continue;
if (!currDom.hasClass('input-validation-error'))
currDom.addClass('input-validation-error');
var currDisplay = $(currForm).find("[data-valmsg-for='" + currElement.name + "']");
if (currDisplay.length > 0) {
currDisplay.removeClass("field-validation-valid").addClass("field-validation-error");
replace = $.parseJSON(currDisplay.attr("data-valmsg-replace")) !== false;
if (replace) {
currDisplay.empty();
$(currElement.errors[0]).appendTo(currDisplay);
}
}
}
};
setTimeout(ToApply, 0);
}
/////////
jForm.find('.input-validation-error').removeClass('input-validation-error');
jForm.find('.field-validation-error').removeClass('field-validation-error').addClass('field-validation-valid');
var container = jForm.find("[data-valmsg-summary=true]");
list = container.find("ul");
list.empty();
if (errors.length > 0) {
$.each(errors, function (i, ival) {
$.each(ival.errors, function (j, jval) {
$("<li />").html(jval).appendTo(list);
});
});
container.addClass("validation-summary-errors").removeClass("validation-summary-valid");
inner_ServerErrors(errors);
setTimeout(function () { jForm.find('span.input-validation-error[data-element-type]').removeClass('input-validation-error') }, 0);
}
else {
container.addClass("validation-summary-valid").removeClass("validation-summary-errors");
}
}
function clearErrors(jForm) {
remoteErrors(jForm, []);
}

requesting two Ajax

I'm trying to make two Ajax calls to get data to populate different bits of a web page, and as you'll already know, only the second happens.
So I thought I'd do this:
callAjax1('a'); callAjax2('b');
function callAjax1(data) {
ajax(data);
}
function callAjax2(data) {
ajax(data);
}
function ajax(data) {
// calls XMLHttpRequestObject etc
}
The idea was that instead of calling ajax() twice, now, I'd have two independent instances of ajax that would run independently.
It works .. but only if I put in an alert at the top of ajax() to let me know I've arrived.
So I'm thinking that alert gives the first request time to finish before the second is called. Therefore, I've not managed to separate them properly into separate instances. Is that not possible?
What am I missing?
All the best
J
UPDATE:
I'm thinking this, do I stand a chance?
tParams = new Array (2); // we intend to call ajax twice
tParams[0] = new Array('ajaxGetDataController.php', 'PROJECT', 'id');
tParams[1] = new Array('ajaxGetFileController.php', 'FILE', 'projectId');
<select name='projectSelector' onchange=\"saveData(tParams, this.value);\">\n";
// gets called, twice
function saveData(pParams, pData) // pParams are: PageToRun, Table, Field
{
if (XMLHttpRequestObject)
{
tPage = pParams[0][0]+'?table='+pParams[0][1]+'&pField='+pParams[0][2]+'&pData='+pData;
XMLHttpRequestObject.open('GET', tPage);\n
XMLHttpRequestObject.onreadystatechange = callAjax(pParams, pData);
XMLHttpRequestObject.send(null);
}
}
function callAjax(pParams, pData)
{
if (XMLHttpRequestObject.readyState == 4 && XMLHttpRequestObject.status == 200)
{
var tReceived = XMLHttpRequestObject.responseXML;
options = tReceived.getElementsByTagName('option'); // fields and their values stored in simplest XML as options
popForm(options, pParams[0][1]); // goes off to use the DOM to populate the onscreen form
pParams.shift(); // cuts off pParams[0] and moves all elements up one
if (pParams.length>0)
{
saveData(pParams, pData);
}
}
}
I would create a ready state variable for the AJAX function:
function ajax(data) {
readyState = false;
// calls XMLHttpRequestObject etc
}
And then check for the ready state before executing the second call:
function callAjax2(data) {
if(readyState == true) {
ajax(data);
readyState = true;
}
}
And make sure to change the readyState back to false after the AJAX calls have executed. This will ensure the first AJAX call has finished executing before the second one tries to fire.

Resources