Here is the code of my dropdown.
{
name: 'ClassId', index: 'ClassId', align: 'center',editable: true, edittype: 'select',
editoptions: {
dataUrl:'#Url.Action("GetAllClasses", "Class", new { Area = "Curriculums"})',
buildSelect: function (data) {
var response, s = '<select>', i;
response = jQuery.parseJSON(data);
//s += '<option value="0">--Select Class--</option>';
if (response && response.length) {
$.each(response, function (i) {
s += '<option value="' + this.Id + '">' + this.ClassName + '</option>';
});
}
return s + '</select>';
}
}
},
I am using form edit.I am reloading grid after insert.But the problem is after inserting data when I try to add another one my dropdowns are getting refreashed.I want that the dropdown selected value will be previously selected value.I don't want to change dropdown selected value on the second add.
The most simple way to prevent reloading of data from dataUrl could be setting Cache-Control HTTP header in the server response. Setting of Cache-Control: max-age=60 in the server response for example will prevent reloading of data from the server during 60 sec. In case of ASP.NET MVC you can use CacheControl attribute for example (see Duration and Location properties).
One more alternative would be dynamical setting editoptions.value instead of usage editoptions.dataUrl. For example one can include the information needed for building editoptions.value as an extension of the standard response of the server for filling the grid. One can use beforeProcessing to process the part of the data. You will find the corresponding examples in the following cold answers: this one, this one, this one, this one, this one and other. The answer describes in short one of the the possible scenario to create full dynamic grid.
Related
I've created a site for sharing some old family letters. I'm using two Select2 controls to let people filter who wrote the letters and who received them. It's all working nicely, but I'd like to have each select2 get filtered based on what was selected in the other one. That is, if a user chooses a particular letter writer, then I'd like the select2 of recipients to show only people that letter writer wrote to. I've actually got that part working, but updating the contents of the dropdown removes any selections it had, and I can't get updating the selections to work.
A few notes and then I'll show some code and try to clarify what I'm seeing and trying.
Both select2 controls are set up for multiple selection.
There's a MySQL database that contains the information about what letters exist, who they were written by, and who they were sent to.
The HTML for the two select2 dropdowns is quite similar. Here's the code for the "From" dropdown:
<select id="from" class="js-example-basic-multiple" multiple="multiple" style="width: 100%">
<?php
echo GetPeople('F');
?>
</select>
GetPeople is a PHP function that calls out to the database and then converts the returned data to a series of lines. The parameter indicates whether to get the "From" data or the "To" data. That parameter is also used as part of the ID for each item (because it turns out that if options with the same ID appear in different select2 controls, things go downhill fast).
function GetPeople ($type) {
$people = getdata("GetPeople", "'$type',''", "Couldn't retrieve list of people.<br/>");
$return = '';
while ($obj = mysqli_fetch_object($people)) {
$return .= "<option value='" . $obj->FullName . "' id='" . $type . $obj->iid . "'>" . $obj->FullName . "</option>";
}
return $return;
}
The OnChange event for each of the select2 controls calls a JS function called updatePeople, passing either 'F' or 'T' to indicate which select2 needs its contents updated. That is, the From dropdown passes 'T' and the To dropdown passes 'F'.
updatePeople uses AJAX to go out and collect the data, build a new set of lines and sub them into the specified control. That's all working as expected. But doing so leaves that control with no items selected. When I try to restore the selections the control had before the update, nothing happens. Here's the code for updatePeople, including my attempt to restore the selection:
function updatePeople(cwhich) {
var results = '';
var success = false;
//get the currently selected list for the other dropdown, too
//so we can use them to restore selections later
if (cwhich == "F") {
var chosen = getChoices('#to');
var selecteditems = getChoices('#from',true);
} else {
var chosen = getChoices('#from');
var selecteditems = getChoices('#to',true);
}
var filters = cwhich + "','" + chosen + "'";
var fnargs = "GetPeople|'" + filters;
$.ajax({
url: 'retrievedata.php',
type: "POST",
async: false,
data: {"functionname": "getpeople", "arguments": fnargs},
dataType: "JSON",
success: function (obj) {
if (cwhich=="F") {
var target = "#from";
} else {
var target = "#to";
}
if ((obj===null)) {
//clear dropdown
$(target).html('');
}
else {
//build the list of options
var options = '';
for (line = 0; line < obj.length; line++) {
options += "<option value='" + obj[line].FullName + "' id='" + cwhich + obj[line].iid + "'>" + obj[line].FullName + "</option>";
}
window.allowupdate = false;
$(target).html(options);
//turn list of selected items into array
var arrSelected = selecteditems.split(',');
$(target).val(arrSelected);
$(target).trigger('change');
window.allowupdate = true;
}
success = true;
},
error: function (textStatus, errorThrown) {
success = false;
$("#testresult").text('Error occurred: '.textStatus);
console.log('Error occurred: '.textStatus);
}
});
}
The window.allowupdate bit is because trying to change the selections for one dropdown would fire its OnChange, and that would change the first one. So I added a flag that's checked in the OnChange code, so that I can change one without changing the other.
What I'm looking for is how to set the modified select2 control to have the same items selected as before updating the contents of the control. TIA
Solved my own problem. There were two issues:
Setting the control’s HTML seemed to make it unhappy. I changed that code to first disconnect from select2, then update the HTML, then reconnect to select2:
$(target).select2('destroy');
$(target).html(options);
$(target).select2({placeholder: 'Anyone (or choose senders)'});
I didn’t have the right value to reset the selections. My code was retrieving the primary keys for the selected items, but I needed their actual contents, which I was able to retrieve by calling the control’s val function.
I need to use kendo-ui grid for data editing. Problem is that every possible item in returned response is string, but which contains other types of value (eg Value = "true" or Value = "32%" or Value = "[0:standard, 1:advanced]").
So I need to set up template on grid to correspond different data type within string.
So for true/false i have to have checkbox, for 32% it should provide text box but with percent validation, for array response it needs to be a drop down.
I managed to set up drop down and text box options using editor, but I cannot make checkbox to handle properly in any way. Checkbox is displayed as expected, but whatever I try it won't bind data to the grid after grid is saved. (it is always not checked, regardless of value)
Here is code snippet of column "value" and what I used for template (item.type === "3" is boolean).
field: 'value',
title: 'value',
headerAttributes: {
'class': 'table-header-cell'
},
template: function (item) {
if (item.type === "3") {
var boolValue = (/true/i).test(item.value);
item.value = boolValue;
return '<input id="' + item.name+ '" type="checkbox" #= value ? \'checked="checked"\' : "" # class="chkbx" />';
} else {
return ''; //this will follow for other types
}
},
Thanks in advance.
When the template definition is a function, you don't need to use the # markers to differentiate between markup and javascript like you do when you are defining a kendo template using kendo's template language or a string directly.
This is because inside the function it is always javascript and the # markers are only directives in the kendo templating language.
So, simplify your template to just:
template: function (item) {
return '<input class="chkbx" id="' + item.name + '" type="checkbox"' + (item.value ? 'checked="checked"' : '') + '/>';
}
I've left out the other datatype handling for simplicity.
Then, you need to add code to push the checkbox changes into the grid's datasource when they occur:
$("#grid").on("click", ".chkbx", function () {
var $checkBox = $(this),
checked = $checkBox.is(":checked"),
dataItem = grid.dataItem($checkBox.closest("tr"));
dataItem.set("value", checked);
});
This is a technique that I am currently using in production code.
Dojo example
It may also be possible to use the kendo MVVM bindings in your template for a more elegant solution instead of the explicit click handler, but I'd have to experiment more with that to figure it out.
i have set ,
editoptions: { aysnc: true, dataUrl: 'ControllerName/MethodName?__SessionKey=' + sessionkey + "&Id=" + Id, buildSelect: buildSelectFromJson, style: "width: calc(100% - 65px);",
dataEvents: [
{
type: 'change',
fn: function (e) {}
}
]
}
in which buildSelectFromJson returns select list in html.
Now dataurl hits server for each row but my select list is same for all rows. so how can i restrict to a single hit and then use that select list for all other rows?
I can suggest you two alternatives:
the server code (responsible for the URL ControllerName/MethodName) can place HTTP caching header. For example Cache-Control: private, max-age=(time in seconds). It will force getting the data during specified time interval from the local web browser cache.
You can make Ajax request to ControllerName/MethodName separately and set editoptions.value based on the response instead of usage editoptions.dataUrl (only if dataUrl is undefined the value will be used). See the answer for the code example of the possible implementation. By the way you can combine the call to ControllerName/MethodName with the main call for filling the grid. See the answer and this one.
By the way the property aysnc: true which you use in editoptions is unknown and it will be ignored.
setRowData takes almost 5 seconds when the grid has 300 rows, 30 cols, and 4 frozen cols.
$("#tbl").jqGrid({
gridview: true,
url: '../controller/GetData',
datatype: "json",
rowNum: pageSize,
viewrecords: true,
loadtext: '',
loadui: 'disable',
rowList: [1000, 2000, 3000],
width: $(window).width() - LeftMargin,
shrinkToFit: false,
pager: '#dvPager',
pagerpos: 'left',
colNames: GetColNames(selectedViewName, viewCols),
colModel: GetColModel(selectedViewName, viewCols),
cmTemplate: {
title: false
},
recordtext: "Displaying {0} - {1} of {2}",
rowattr: function (rowData) {
},
//onCellSelect: function (rowid, iCol, cellcontent, e) {
// e.stopPropagation();
// proto.editcell(rowid, iCol, cellcontent, e);
//},
loadComplete: function (data) {..},
onPaging: function (data) {..},
serializeGridData: function (postData) {..}
});
Can you please guide me with the performance tuning tips?
In GetColModel we are binding columns as follows -
'Discount': {
name: 'Discount', index: 'Discount', width: colModelWidthDict['Discount'], title: false, sortable: false, resizable: colResizable, hidden: !bReadCalculationSellInfo,
formatter: function (cellValue, option, rowObject) {
if (rowObject.level == 0 || (rowObject.Flag == 0 && (rowObject.Discountable && rowObject.Discountable.toLowerCase() == 'no')))
return '';
else {
if (!proto.IsReadOnly(rowObject) && ((rowObject.Flag == 0 && (rowObject.Discountable && rowObject.Discountable.toLowerCase() == 'yes')) || rowObject.Flag > 0))
return proto.GetControls("Discount", option.rowId, "Numeric", rowObject.Discount, 6, 90)
else
return '<span class="amt">' + cellValue + '</span>';
}
},
unformat: function (cellValue, option) {
return cellValue;
},
cellattr: function (rowId, val, rowObject, cm, rdata) {
if (parseInt(rowObject.PPPChngDisc) == 1)
return ' style="color:red"';
}
}
//code for colModel - 1 column above
In general you should prefer to modify only small number of rows. If you modify one row then the web browser have to recalculate position of all other rows (500 rows) of the grid. In the case modifying 300 rows means 300*500 operations for the web browser.
If you fill the whole grid and you use gridview: true option then jqGrid places at the beginning all the data for the grid body in array of strings. After preparing of all information in string format jqGrid place the whole body of the grid as one operation. It's just one assigning innerHTML property of tbody. Thus it's more effective to reload the whole grid as to make modification of many rows of the grid in the loop using setRowData.
So I would recommend you to reload the grid in your case.
Moreover you should consider to reduce the number of rows in the page of grid. If you use rowNum: 500 you just reduce the performance of your page tin many times without any advantage for the user. The user can see about 20 rows of the data only. Only if the user scrolls he/she is able to see another information. In the same way the user could click next page button. Holding 500 rows force web browser make a lot of unneeded work. Modifying of the first row from 500 the web browser have to recalculate position of all 500 rows. Even if the user move the mouse over a row the web browser change the class of the hovering row and it have to recalculate position of all other from 500 rows event the rows are not visible.
One more remark. The implementation of frozen columns in jqGrid 4.6 is slow. Starting with the end of 2014 and publishing 4.7.1 with changing Lisanse and making jqGrid commercial I develop free jqGrid as fork based on jqGrid 4.7 (see the readme for more details). I implemented a lot of changes and new features in the fork. The recent changes includes many improvement of frozen columns feature. If you loads free jqGrid or just try it directly from GitHub by including the corresponding URLs (see the wiki) you will see that the performance of frozen columns is dramatically improved. Additionally frozen columns supports now row and cell editing in all editing modes. I plan to publish free jqGrid 4.9 soon. So I recommend you to try it.
UPDATED: You posted the code, which you use in comments below. I see the main problem in the implementation of Process function which will be called on change in the <input> fields on the grid. I recommend you to try something like this one:
function Process(contrl) {
var $cntrl = $(contrl),
$td = $cntrl.closest("tr.jqgrow>td"),
$tr = $td.parent(),
rowId = $tr.attr("id"),
newValue = $cntrl.val(),
$grid = $("#list"),
p = $grid[0].p,
colModel = p.colModel,
columnName = colModel[$td[0].cellIndex].name, // name of column of edited cell
localRowData = $grid.jqGrid("getLocalRow", rowId),
iColTotal = p.iColByName.total;
// modify local data
localRowData[columnName] = newValue;
localRowData.total = parseInt(localRowData.quantity) * parseFloat(localRowData.unit);
// modify the cell in the cell (in DOM)
//$grid.jqGrid("setCell", rowId, "total", localRowData.total);
$($tr[0].cells[iColTotal]).text(localRowData.total);
return false;
}
One can event improve the code and not use setCell at all. It's important to understand that local grid hold the data in the form of JavaScript object. The access to the data is very quickly. You can modify the data without any side effects existing with DOM elements. In the above code I use only relative search inside of the DOM of the row using closest and parent. It works very quickly. I recommend to save $("#list") in the variable outside of the Process (move var $grid = $("#list") in the outer function) and just use $grid variable. It will make access to the DOM of page very quickly. The line localRowData = $grid.jqGrid("getLocalRow", rowId) get the reference to internal JavaScript data object which represent the internal data of the row. By modifying the data by localRowData[columnName] = newValue; or localRowData.total = ... we make the half of job. One need only to change the value in the DOM on the cell Total. I used iColByName which exist in the latest sources of free jqGrid on GitHub. It get just the column index in colModel by column name. One can find the same information alternatively if one use more old jqGrid. The line $($tr[0].cells[iColTotal]).text(localRowData.total); shows how to assign manually the data in the cell. It's the most quick way to do this, but one assign the data without the usage formatter. For more common case (for example in case of frozen "total" column) one can use the commented row above: $grid.jqGrid("setCell", rowId, "total", localRowData.total);.
I have 3 different tabs where i am displaying data using jQGrids(each tab contain one grid).
But i just thought that my grids are completely the same, only the difference is that they using different url to get data.
So I have three similar girds on each tab only with different urls:
First: url: '/Home/GetData?id=1' Second: url: '/Home/GetData?id=2' and Third: url: '/Home/GetData?id=3'
So i was thinking that may be i may declare grid only once and than on each tab click a can pass the url to load data? So on each tab click jQGrid will be populating from the new url.
May be some one may have any ideas about that?
Or may be some one may have better ideas how to reduce "jQGrid copy-paste" in that case?
UPDATE 0:
Nearly get it work i mean it is working but there is one small problem,
When i am switching tabs the header of the grid getting lost...and some jqgrid formatting as well.
here is my code:
$("#tabs").tabs({
show: function (event, ui) {
if (ui.index == 0) {
//$("#Grid1").appendTo("#tab1");
//$("#Grid1Pager").appendTo("#tab1");
//When Appending only pager and grid div, header getting lost so i've append the whole grid html instead
$("#gbox_Grid1").appendTo("#tab1");
changeGrid("#Grid1", 1);
}
else if (ui.index == 1) {
//$("#Grid1").appendTo("#tab2");
//$("#Grid1Pager").appendTo("#tab2");
$("#gbox_Grid1").appendTo("#tab2");
changeGrid("#Grid1", 2);
}
else if (ui.index == 2) {
//$("#Grid1").appendTo("#tab3");
//$("#Grid1Pager").appendTo("#tab3");
$("#gbox_Grid1").appendTo("#tab3");
changeGrid("#Grid1", 3);
}
}
});
function changeGrid(grid, id) {
$(grid).jqGrid('setGridParam', {
url: '/Home/GetData?id=' + id
});
$(grid).trigger('reloadGrid');
}
UPDATE 1
All right, i've changed the code to append the whole grid instead of appending grid div and pager only. So it is working like that.
You can basically make the tabs as regular buttons that will call some function which sets new URL parameter to the grid and reloads it.
The function should be something like this:
function changeGrid(grid, id) {
$(grid).jqGrid('setGridParam', {
url: '/Home/GetData?id=' + id, page: 1
});
$(grid).trigger('reloadGrid');
}
Note that I set the page to 1. Keep in mind that you might need to set the default sorting column or something similar depending on your solution.
UPDATE
If you really want to go with the tabs, the 'show' event handler can be simplified.
Try this:
$("#tabs").tabs({
show: function (event, ui) {
var id = ui.index + 1;
$("#gbox_Grid1").appendTo("#tab" + id);
$("#Grid1").jqGrid('setGridParam', {
url: '/Home/GetData?id=' + id
});
$("#Grid1").trigger('reloadGrid');
}
});