jqGrid addChildNode bug when calling the function - jqgrid

I seem to have a bug with jqGrid and the addChildNode command. Whenever I use something like the following
$('#grid').jqGrid("addChildNode", "6", "2", data);
I get no new row but the root gets another expand arrow icon, sort of like this:
▼ ▼ Row name
"6" is the new ID field value and 2 is the ID field of the parent (not rowid). The idPrefix is "loc" so as not to have numbers as ID values.
If I repeat the command, I just get another arrow down and still no new rows.
Using the latest version of jqGrid, v5.3.2. Evaluating to switch over from the free fork.

Related

Kendo TreeView in grid display 'undefined'

I had this kendo grid demo, and on outletID column, i want to use kendoTreeView with checkbox, so multiple selected outletID possible. But when edit it display undefined result, and the template that display outletName also not working. Appreciate your help
DEMO IN DOJO
Your tree needs dataTextField and dataValueField set.
Your column template doesn't know where to look for the outletName. Kendo supports 1-N relationships, but I'm not aware of N-N.
The data in your template is the current row of the grid. For the first row, that would be {"id":"1","outletID":"LA2,LA3","accountName":"Data1"}. You need to process that data yourself. For instance:
template: "#= (data.outletID) ? data.outletID.split(',')
.map(x => TreeData.find(y => y.outletID == x)['outletName']) : '' #"
For the editor, the value of a dropDownTree is an array. Your row has a string. You need to do two things:
1 . Init the editor's value in the function outletTree:
if (options.model) {
ddt.value((options.model[options.field] || '').split(','))
}
2 . When the dropDownTree's value changes, update your grid row:
change: e => {
const value = e.sender.value();
console.log(value)
options.model.set(options.field, value.join(','))
}
Here's an updated dojo: https://dojo.telerik.com/#GaloisGirl/oYEGerAK . The "Update" button doesn't work yet, probably because the dataSource must support edition. Here is how to do it on local data.

How to iterate through a hidden column in jquery data table

I have a data table that has a hidden column. I want to set the values of that column to a java script array. (Note: I want to get the values that belong to the current page only). If I use a search filter, I want the values of the current page of the search result.
I've tried like this.
$('#datatbl').DataTable().rows({filter: 'applied'}).every(function () {
var row = this.data();
arr.push(row[0]);
});
But,this code gives all values from all the pages. Please help....
Try adding page:'current' in selector.
$('#datatbl').DataTable().rows({filter: 'applied', page:'current'})

idPrefix usage in jqGrid

Given a jqGrid populated with local data and created with the option of idPrefix:"custTable" , all the generated rows get the prefix in the html id i.e. custTableRow_1 custTableRow_2 etc. Does this idPrefix'ed version of the id need to be passed in to the jqGrid methods, if so which ones?
for example to delete a row with deleteRowData does it need the prefixed id? how about setRowData or addRowData? when adding after row x it seems to need the prefixed for the srcrowid parameter. How about multiselect rows?
If I delete a row using the prefixed id of the row it disappears from the display but when I reload the grid the delete item shows up again in the grid, like it wasn't removed. This doesn't happen when idPrefix is not used.
thanks for any help.
The option idPrefix was introduced to hold ids on the HTML page unique even you have on the page the ids like the rowids loaded from the server. Typical example is two grids with the data loaded from the server. Let us you have two tables in the database where you use IDENTITY or AUTOINCREMENT in the definition of the PRIMARY KEY. In the case the primary key will be generated automatically in the table and will be unique inside the table, but there are not unique over the tables. So if you would use the primary keys as ids of the grids and place on one page two grids you can have id duplicates.
To solve the problem you can use idPrefix: "a" as additional option in the first grid and use idPrefix: "b" in the second grid. In the case locally jqGrid will uses everywhere ids with the prefix, but the prefix will be cut if the ids will be sent to the server.
So you will see locally in all callbacks (events) and in all methods (like setRowData, addRowData etc) the ids with the prefix, but on the server side the ids the prefixes will be removed immediately before sending to the server.
I recommend you additionally to read another answer about the restrictions in the ids which I posted today.
UPDATED: I looked through the code which you posed on jsfiddle and found some clear bugs in your code. You current code
1) use wrong algorithm to generate id of the new row. For example the following code
// generic way to create an animal
function newAnimal(collection, defaults) {
var next = collection.length + 1;
var newpet = {
id : next,
name: defaults.name + next,
breed: defaults.breed
};
return newpet;
}
use collection.length + 1 for the new id. It's wrong if you allows to delete the items. By adding of two items, deleting one from there and adding new item one more time follows to id duplicates. Instead of that it's more safe to use some variable which will be only incremented. You can use $.jgrid.randId() for example which code is very simple.
2) you call addRowData with adding a prefix manually (see dogsPrefix+newdog.id below). It's wrong because jqGrid adds the prefix one more time to the rows.
// add dog button actions
$('#dogAddAtEnd').click(function() {
var newdog = newAnimal(dogs, dogDefaults);
dogs.push(newdog);
dogAdded();
dogsTable.jqGrid('addRowData', dogsPrefix+newdog.id, newdog);
});
Probably there are more problems, but at least these problems can explain the problems which you described.
UPDATED 2: I examined new demo which you posted. It has still the lines
grid.jqGrid('addRowData', newanimal.id, newanimal,
"after", prefix+ followingId);
and
dogsTable.jqGrid('addRowData', dogsPrefix+newdog.id, newdog);
which must be fixed to
grid.jqGrid('addRowData', newanimal.id, newanimal,
"after", followingId);
and
dogsTable.jqGrid('addRowData', newdog.id, newdog);
Nevertheless I tested the demo after the changes and found bugs in code of addRowData, delRowData and setRowData. The problem are in the line of the delRowData and the same line of setRowData
var pos = $t.p._index[rowid];
can be fixed to the following
var id = $.jgrid.stripPref($t.p.idPrefix, rowid), pos = $t.p._index[id];
Inside of addRowData I suggest to include the line
var id = rowid; // pure id without prefix
before the line
rowid = t.p.idPrefix + rowid;
of addRowData. Another tow lines of addRowData
lcdata[t.p.localReader.id] = rowid;
t.p._index[rowid] = t.p.data.length;
should be changed to
lcdata[t.p.localReader.id] = id;
t.p._index[id] = t.p.data.length;
where unprefixed id will be used.
The modified code of you demo which uses the fixed version of jquery.jqGrid.src.js you can test here.
I will post my bug report to trirand later to inform the developer of the jqGrid. I hope that soon the bug fix will be included in the main code of jqGrid.
Additionally I recommend you to use $.jgrid.stripPref method to strip prefixes from the rowids. For example the function
//general delete selected
function deleteSelectedAnimal(list, grid, prefix)
{
var sel = grid.jqGrid('getGridParam', 'selrow');
if (sel.length)
{
var gridrow = sel;
//get the unprefixed model id
var modelid = gridrow;
if (prefix.length !== 0)
{
modelid = modelid.split(prefix)[1];
}
// make it a numeric
modelid = Number(modelid);
//delete the row in the collection
list = RemoveAnimal(list, modelid);
//delete the row in the grid
grid.jqGrid('delRowData', gridrow);
}
}
from your demo can be rewritten to the following
//general delete selected
function deleteSelectedAnimal(list, grid)
{
var sel = grid.jqGrid('getGridParam', 'selrow'),
gridPrefix = grid.jqGrid('getGridParam', 'idPrefix');
if (sel !== null)
{
//delete the row in the collection
// ??? the gogs list will be not modified in the way !!!
list = RemoveAnimal(list, $.jgrid.stripPref(gridPrefix, sel));
//delete the row in the grid
grid.jqGrid('delRowData', sel);
}
}
I am not sure that the line list = RemoveAnimal(list, $.jgrid.stripPref(gridPrefix, sel)); or the function RemoveAnimal do what you want, but it's not a problem which connected with jqGrid.
One more small remark about your code. You use already in the objects which you add to the grid the id property. It's the same name as defined in the localReader.id. In the case the data from the id property will be used as id attribute of the grid rows (<tr>). The local data parameter will save the id additionally to other properties which are build from the name property of the items of colModel. So I see no sense to define hidden column
{ key: true, name: 'id', align: 'left', hidden: true }
How you can see on the demo all stay works exactly as before if you remove id column from the grids which you use.
UPDATED 3: As promised before I posted the corresponding bug report here.

jqgrid randId() produces duplicates after page reload

On my grid, after a user enters text on the bottom row, I am adding another row so they can fill out another row if needed. The grid will grow as needed by the user. This is working fine, however after a page reload and populating from db, the addrowdata() function does not honor existing row ids and creates duplicates, starting from 1 again, e.g. jqg1. It should look at existing row ids and create new unique ids. So if I have 5 rows already, it might start at jqg6. Here is the relevant code inside onCellSelect:
var records = jQuery("#table-1").jqGrid('getGridParam', 'records');
var lastRowId = jQuery("#table-1").jqGrid('getDataIDs')[records - 1];
if (lastRowId == id)
{
jQuery('#table-1').addRowData(undefined, {}, 'last');
}
I have also tried $.jgrid.randId() instead of undefined, same results as expected.
Thanks
Ryan
I think that the error is in the part where you fill grid with the data from the database. The data saved in the database has unique ids. The ids are not in the form jqg1, jqg2, ... So if should be no conflicts. You should just fill the id fields of the JSON with the ids from the database.
One more possibility is that you just specify the rowid parameter (the first parameter) of addRowData yourself. In the case you will have full control on the new ids of the rows added in the grid.
The code of $.jgrid.randId function is very easy. There are $.jgrid.uidPref initialized as 'jqg' and $.jgrid.guid initialized to 1. The $.jgrid.randId function do the following
$.jgrid.randId = function (prefix) {
return (prefix? prefix: $.jgrid.uidPref) + ($.jgrid.guid++);
}
If it is really required you can increase (but not decrease) the $.jgrid.guid value without any negative side effects.

jqgrid: how send and receive row data keeping edit mode

jqGrid has employee name and employee id columns.
If employee name has changed, server validate method should called to validate name. Current row columns should be updated from data returned by this method.
If employee id has changed, server validate method should called to validate id.
Current row columns should be updated from data returned by this method.
Preferably jqGrid should stay in edit mode so that user has possibility to continue changing, accept or reject changes.
How to implement this in inline and form editing?
I'm thinking about following possibilites:
Possibility 1.
Use editrules with custom validator like
editrules = new
{
custom = true,
custom_func = function(value, colname) { ??? }
},
Issues: How to get data from all columns, make sync or async call and update columns with this call results.
Possibility 2.
Require user to press Enter key to save row.
Issues: how to find which column was changed and pass this column number to server.
How to update current row data from server response.
Possibility 3.
using blur as described in Oleg great answer in
jqgrid change cell value and stay in edit mode
Issues: blur does not fire if data is entered and enter is pressed immediately. How to apply blur in this case ?
In summary server sice calculation/validation should be dones as follows:
If column in changed and focus moved out or enter is pressed in changed column to save, server side sync or if not possible then async method should be called. Changed column name and current edited row values like in edit method are passed as parameters to this method.
This method returns new values for edited row. current edited row values should be replaced with values returned by that method.
Update
Oleg answer assumes that primary key is modified. This factor is not important. Here is new version of question without primary keys and other updates:
jqGrid has product barcode and product name columns.
If product name has changed, server validate method should called to validate name. Current row columns should be updated from data returned by this method.
If product barcode has changed, server validate method should called to validate product barcode.
Current row columns should be updated from data returned by this method.
jqGrid should stay in edit mode so that user has possibility to continue changing, accept or reject changes.
How to implement this in inline and form editing?
I'm thinking about following possibilites:
Possibility 1.
Use editrules with custom validator like
editrules = new
{
custom = true,
custom_func = function(value, colname) { ??? }
},
Issue: custom_func does not fire if input element loses focus. It is called before save for all elements. So it cannot used.
Possibility 2.
Require user to press Enter key to save row.
Issues: how to find which column was changed and pass this column number to server.
Save method should known column (name or barcode change order) and fill different columns. This looks not reasonable.
Possibility 3.
using blur:
colModel: [{"label":"ProductCode","name":"ProductCode","editoptions":{
"dataEvents":[
{"type":"focus","fn":function(e) { ischanged=false}},
{"type":"change","fn":function(e) {ischanged=true}},
{"type":"keydown","fn":function(e) {ischanged=true }},
{"type":"blur","fn":function(e) { if(ischanged) validate(e)} }
]},
To implement validate I found code from Oleg great answer in
jqgrid change cell value and stay in edit mode
Summary of requirement:
If column in changed and focus moved out or enter is pressed in changed column to save, server side sync or if not possible then async method should be called. Changed column name and current edited row values like in edit method are passed as parameters to this method.
This method returns new values for edited row. current edited row values should be replaced with values returned by that method.
Update2
This question is not about concurrency. This is single user and jqGrid issue. Updating means that single user changes product name or barcode and server shoudl provide additonal data (product id and/or name/barcode) is responce of this.
Update 4
I tried code below.
If user enters new code and presses Enter without moving to other row, blur does not occur and validation is not called.
How to dedect in jqGrid save method if cell is dirty or other idea how to force this code to run if enter is pressed to end edit without losing focus from changed foreign key cell ?
function validate(elem, column) {
ischanged = false;
var i, form, row;
var postData = { _column: column };
var colModel = $("#grid").jqGrid('getGridParam', 'colModel');
var formEdit = $(elem).is('.FormElement');
// todo: use jQuery serialize() ???
if (formEdit) {
form = $(elem).closest('form.FormGrid');
postData._rowid = $("#grid").jqGrid('getGridParam', 'selrow');
for (i = 0; i < colModel.length; i++)
eval('postData.' + colModel[i].name + '="' + $('#' + colModel[i].name + '.FormElement', form[0]).val() + '";');
}
else {
row = $(elem).closest('tr.jqgrow');
postData._rowid = row.attr('id');
for (i = 1; i < colModel.length; i++)
eval('postData.' + colModel[i].name + '="' + $('#' + postData._rowid + '_' + colModel[i].name).val() + '";');
}
$.ajax('Grid/Validate', {
data: postData,
async: false,
type: 'POST',
success: function (data, textStatus, jqXHR) {
for (i = 0; i < data.length; i++) {
if (formEdit)
$('#' + data[i].name + '.FormElement', form[0]).val(data[i].value);
else
$('#' + postData._rowid + '_' + data[i].name).val(data[i].value);
}
}
});
}
colModel is defined as:
{"name":"ProductBarCode",
"editoptions": {"dataEvents":
[{"type":"focus","fn":function(e) {ischanged=false}
},
{"type":"change","fn":function(e) {ischanged=true},
{"type":"keydown","fn":function(e) {if(realchangekey()) ischanged=true}
},{"type":"blur","fn":function(e) { if(ischanged) { validate( e.target,ProductBarCode')}}
}]},"editable":true}
It's one from the problems which is much easier to avoid as to eliminate. I have to remind you about my advises (in the comments to the answer) to use immutable primary key, so that is, will be never changed. The record of the database table can be destroyed, but no new record should have the id of ever deleted record.
On any concurrency control implementation it is important that the server will be first able to detect the concurrency problem. It can be that two (or more) users of your web application read the same information like the information about the employee. The information can be displayed in jqGrids for example. If you allow to change the employee id, than the first problem would be to detect concurrency error. Let us one user will change the employee id and another user will try to modify the same employee based on the previous loaded information. After the user submit the midification, the server application will just receive the "edit" request but will not find the corresponding record in the database. The server will have to sent error response without any detail. So the errorfunc of the editRow or the event handler errorTextFormat of the editGridRow should trigger "reloadGrid" reload the whole grid contain.
If you allow to edit the primary key, then I can imagine more dangerous situation as described before. It can be that another user not only change the id of the current editing row to another value, but one could change the id of one more record so, that its new id will be the same as the current editing id. In the case the request to save the row will overwrite another record.
To prevent such problems and to simplify the optimistic concurrency control one can add an additional column which represent any form of the timestamp in every table of the database which could be modified. I personally use Microsoft SQL Server and add I used to add the non-nullable column of the type rowversion (the same as the type timestamp in the previous version of the SQL Server). The value of the rowversion will be send to the jqGrid together with the data. The modification request which will be send to the server will contain the rowversion. If any data will be save in the database the corresponding value in the corresponding rowversion column will be automatically modified by the SQL database. In the way the server can very easy detect concurrency errors with the following code
CREATE PROCEDURE dbo.spEmployeesUpdate
-- #originalRowUpdateTimeStamp used for optimistic concurrency mechanism
-- it is the value which correspond the data used by the user as the source
#Id int,
#EmployeeName varchar(100),
#originalRowUpdateTimeStamp rowversion,
#NewRowUpdateTimeStamp rowversion OUTPUT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
-- ExecuteNonQuery() returns -1, but it is not an error
-- one should test #NewRowUpdateTimeStamp for DBNull
SET NOCOUNT ON;
UPDATE dbo.Employees
SET Name = #EmployeeName
WHERE Id=#Id AND RowUpdateTimeStamp=#originalRowUpdateTimeStamp;
-- get the new value of the RowUpdateTimeStamp (rowversion)
-- if the previous update took place
SET #NewRowUpdateTimeStamp = (SELECT RowUpdateTimeStamp
FROM dbo.Employees
WHERE ##ROWCOUNT > 0 AND Id=#Id)
END
You can verify in the code of the server application that the output parameter #NewRowUpdateTimeStamp will be set by the stored procedure dbo.spEmployeesUpdate. If it's not set the server application can throw DBConcurrencyException exception.
So in my opinion you should make modifications in the database and the servers application code to implement optimistic concurrency control. After that the server code should return response with HTTP error code in case of concurrency error. The errorfunc of the editRow or the event handler errorTextFormat of the editGridRow should reload the new values of the currently modified row. You can use either the more complex way or just reload the grid and continue the modification of the current row. In case of unchanged rowid you can easy find the new loaded row and to start it's editing after the grid reloading.
In the existing database you can use
ALTER TABLE dbo.Employees ADD NewId int IDENTITY NOT NULL
ALTER TABLE dbo.Employees ADD RowUpdateTimeStamp rowversion NOT NULL
ALTER TABLE dbo.Employees ADD CONSTRAINT UC_Employees_NewId UNIQUE NONCLUSTERED (NewId)
GO
Then you can use NewId instead of the id in the jqGrid or in any other place which you need. The NewId can coexist with your current primary key till you update other parts of your application to use more effective NewId.
UPDATED: I don't think that one really need to implement any complex error correction for the concurrency error. In the projects at my customers the data which are need be edited can not contain any long texts. So the simple message, which describe the reason why the current modifications could not be saved, is enough. The user can manually reload the full grid and verify the current contain of the row which he edited. One should not forget that any complex procedures can bring additional errors in the project, the implementation is complex, it extend the development budget and mostly the additional investment could never paid off.
If you do need implement automated refresh of the editing row I would never implement the cell validation in "on blur" event for example. Instead of that one can verify inside of errorfunc of the editRow or inside of the errorTextFormat event handler of the editGridRow that the server returns the concurrency error. In case of the concurrency error one can save the id of the current editing row in a variable which could be accessed inside of the loadComplete event handle. Then, after displaying of the error message, one can just reload the grid with respect of $('#list').trigger('reloadGrid',[{current:true}]) (see here). Inside of loadComplete event handle one can verify whether the variable of the aborted editing row is set. In the case one can call editRow or editGridRow and continue the editing of the string. I think that when the current row are changed another rows of the page could be also be changed. So reloading of the current page is better as reloading of the data only one current cell or one row of the grid.

Resources