I'm trying to get editing working properly on a virtualized grid. I'm also using continuous virtualization mode. I've run across a problem when I try to edit data while the grid is scrolled down. Every time an edit is completed, the grid scrolls itself back to the top. This will force the user to scroll back down every time they want to edit a row that isn't near the top. Is there anyway to fix this?
To reproduce this with the code below, scroll down until you can see the product with ID 30. Change the product name from "car" to "train". Click the "done" button. You will notice the grid is now scrolled back to the top. This code is a slightly modified version of the "EditCellAndRow" demo.
<!doctype html><!--[if lt IE 7 ]> <html lang="en" class="no-js ie6"> <![endif]--><!--[if IE 7 ]> <html lang="en" class="no-js ie7"> <![endif]--><!--[if IE 8 ]> <html lang="en" class="no-js ie8"> <![endif]--><!--[if IE 9 ]> <html lang="en" class="no-js ie9"> <![endif]--><!--[if (gt IE 9)|!(IE)]><!--> <html lang="en" class="no-js"> <!--<![endif]--><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <title>NetAdvantage for jQuery Samples</title> <link href="../content/style.css" rel="stylesheet" type="text/css" /> <script src="../scripts/modernizr.min.js"></script> <script src="../scripts/jquery.min.js"></script> <script src="../scripts/jquery-ui.min.js"></script> <script src="../../js/infragistics.loader.js"></script> <style type="text/css"> #eventList { border: 1px soild; height: 200px; overflow-y: auto; padding-top: 5px; color: #656565; } </style> <script type="text/javascript"> var i = -1, a = 'A'.charCodeAt(0), gridData = [], colors = ['Red', 'Orange', 'Pink', 'Yellow', 'Green'], names = ['Jet', 'Train', 'Car', 'Boat'], evntCounter = 0; $.ig.loader({ scriptPath: "../../js/", cssPath: "../../css/", resources: "igGrid.Selection,igGrid.Updating,igValidator" }); while (i++ < 40) { gridData[i] = { "ProductID": i, "Name": names[i % 4], "Code": String.fromCharCode(a + i % 4) + String.fromCharCode(a + 8 - i % 5) + String.fromCharCode(a + 10 - i % 10) + (1 + i % 7), "Color": colors[i % 5], "OrderDate": new Date(2009 + i % 4 % 2, (i + 3) % 12, 5 + i * 4 % 24), "ListPrice": (i === 3) ? null : Math.pow((6 - i % 4), 7) * 1.25 + i }; } $.ig.loader(function() { $("#grid1").igGrid({ virtualization: true, virtualizationMode: "continuous", autoCommit: false, autoGenerateColumns: false, primaryKey: "ProductID", columns: [ { // note: if primaryKey is set and data in primary column contains numbers, // then the dataType: "number" is required, otherwise, dataSource may misbehave headerText: "Product ID", key: "ProductID", width: "100px", dataType: "number" }, { headerText: "Product Name", key: "Name", width: "130px" }, { headerText: "Code", key: "Code", width: "100px" }, { headerText: "Color", key: "Color", width: "80px" }, { headerText: "Order Date", key: "OrderDate", width: "120px", dataType: "date" }, { headerText: "List Price", key: "ListPrice", width: "150px", dataType: "number", format: "currency" } ], dataSource: gridData, height: "300px", tabIndex: 1, features: [ { name: "Selection", mode: "row" }, { name: "Updating", dataDirty: function(evt, ui){return false}, enableAddRow: false, editMode: "row", enableDeleteRow: false, // event raised after cell editing started editCellStarted: function (evt, ui) { logEvent("editCellStarted event fired. Column Key = " + ui.columnKey); buttonsState(false); }, // event raised after end row editing but before dataSource was updated editCellEnding: function (evt, ui) { // check if cell in "Code" column has length 4 if (ui.update && $("#autoFill").is(":checked")) { if (ui.columnKey === "Code" && ui.value.length < 4) { // use custom value ui.value = "ABC1"; } } buttonsState(true); logEvent("editCellEnded event fired. Column Key = " + ui.columnKey + "; Row Index = " + ui.rowID + "; Cell Value = " + ui.value + "; Update = " + ui.update); }, // event raised after end row editing but before dataSource was updated editRowEnding: function (evt, ui) { var val, editor, updating; // do not update dataSource if ($("#cancelUpdate").is(":checked")) { return false; } // do not close edit mode if List Price is undefined if (ui.update && $("#keepEditingPrice").is(":checked")) { // get reference to igGridUpdating updating = $("#grid1").igGridUpdating(); // get reference to igEditor used for ListPrice editor = updating.igGridUpdating("editorForKey", "ListPrice"); // get value of igEditor val = editor.igEditor("value"); if (val === null) { ui.keepEditing = true; alert("Please enter value into List Price field"); // or application may fill value by itself, by something like: //editor.igEditor("value", 123); } } buttonsState(true); logEvent("editRowEnding event fired. Row Index = " + ui.rowID); }, // event raised before row editing started editRowStarting: function (evt, ui) { // do not update dataSource if ($("#cancelEditCar").is(":checked")) { var cellValueForNameColumn, row = ui.rowID, grid = $("#grid1").data("igGrid"); // get value of cell in this row for the column with key "Name" cellValueForNameColumn = grid.getCellValue(row, "Name"); if (cellValueForNameColumn === "Car") { return false; } } // to check if that event is raised while new-row-adding: // var rowAdding = ui.rowAdding; logEvent("editRowStarting event fired. Row Index = " + ui.rowID); }, // event raised after editing row started editRowStarted: function (evt, ui) { logEvent("editRowStarted event fired. Row Index = " + ui.rowID); buttonsState(false); }, // event raised after editing row ended editRowEnded: function (evt, ui) { $("#grid1").igGrid("commit");
logEvent("editRowEnded event fired. Update Row = " + ui.rowID); buttonsState(true); }, columnSettings: [ { columnKey: "ProductID", readOnly: true }, { columnKey: "Name", defaultValue: names[1], editorOptions: { button: "dropdown", listItems: names, readOnly: true, dropDownOnReadOnly: true } }, { columnKey: "Code", editorOptions: { type: "mask", inputMask: ">LLL0", dataMode: "rawtext" } }, { columnKey: "Color", defaultValue: colors[1], editorOptions: { button: "dropdown", listItems: colors } }, { columnKey: "OrderDate", editorType: "datepicker", validation: true, editorOptions: { minValue: new Date(1955, 1, 19), maxValue: new Date(), required: true } }, { columnKey: "ListPrice", editorType: "currency", editorOptions: { button: "spin", minValue: 0, maxValue: 100000, validatorOptions: {} } } ] } ] }); // process events of checkboxes and buttons
$("#startEdit").bind({ click: function (e) { $("#grid1").igGridUpdating("startEdit", 1, 1, e); buttonsState(false); } }); $("#endEdit").bind({ click: function (e) { // note: if action is called from codes, then in order to raise events of igGridUpdating // the browser event should be used (e: 2nd param param in endEdit() method. // if 2nd param "e" was not used, then application may raise notification explicitly if ($("#grid1").igGridUpdating("endEdit", true, e)) { buttonsState(true); } } }); $("#cancelEdit").bind({ click: function (e) { $("#grid1").igGridUpdating("endEdit", false); buttonsState(true); } }); $("#editMode").bind({ change: function (e) { var editMode = $(this).val(); $("#grid1").igGridUpdating("option", "editMode", editMode); $("#startEdit").attr("value", (editMode === "row") ? "Start edit 2nd row" : "Start edit 2nd column in 2nd row"); buttonsState(true); } }); $("#startEditTriggers").bind({ change: function (e) { var startEditTriggers = $(this).val(); $("#grid1").igGridUpdating("option", "startEditTriggers", startEditTriggers); } }); });
$("#showButtons").live({ change: function (e) { $("#grid1").igGridUpdating("option", "showDoneCancelButtons", $(this).is(":checked")); buttonsState(true); } });
// get number of rows in grid function numberOfRows () { return $("#grid1").data("igGrid").dataSource.dataView().length; } // adjust action buttons function buttonsState (editing) { $("#startEdit").prop("disabled", !editing); $("#endEdit").prop("disabled", editing); $("#cancelEdit").prop("disabled", editing); } // show the raised event function logEvent(message) { var evntWrap = $("#eventList"); $(evntWrap).append("<div>" + (++evntCounter) + ". " + message + "<div>"); $(evntWrap).prop("scrollTop", $(evntWrap).prop("scrollHeight")); } </script></head>
<body> <div id="container"> <header> <h1 class="logo"><a href="../index.html"><img src="../Content/images/infragistics.png" /></a></h1> </header>
<div id="main" role="main"> <section class="product"> <img src="../Content/images/jquery-netadvantage.png" /> <p>Create the best Web experiences in browsers and devices with our user interface controls designed expressly for jQuery, ASP.NET MVC, HTML 5 and CSS 3. You'll be building on a solid and proven foundation without any plug-ins or extensions, just real world best practices and the most forward-thinking, robust Web technology.</p> </section> <div class="content clearfix"> <!-- side nav begins here --> <iframe src="../nav.html" scrolling="auto" class="side-nav"></iframe> <!-- side nav ends here --> <section class="main-box"> <hgroup> <h1>Infragistics NetAdvantage JQuery</h1> <h2>Edit Cells and Rows</h2> <p>This sample demonstrates how to configure editors for data types and interact with events related to editing row and cell.<br /> The selection of igGrid is enabled, so, active row will match with editing-row and if grid has focus, then editing of active row can be started from keyboard. Keyboard triggers can be adjusted by <b>startEditTriggers</b> option.</p> </hgroup>
<div class="sampleContainer">
<label class="fontSize" for="editMode" style="margin-bottom:5px;">Edit mode:</label> <select id="editMode" style="margin-bottom:5px;"> <option selected="selected">row</option> <option>cell</option> <option>none</option> </select> <label class="fontSize" for="startEditTriggers" style="margin-bottom:5px;margin-left:150px;">Start edit triggers:</label> <select id="startEditTriggers" style="margin-bottom:5px;"> <option value="click,F2,enter" selected="selected">click,F2,enter</option> <option value="dblclick,F2,enter">dblclick,F2,enter</option> <option value="dblclick">dblclick</option> <option value="click">click</option> <option value="click,F2">click,F2</option> <option value="F2,enter">F2,enter</option> </select> <br /> <div id="eventList" class="controlPanelEvent"> </div> <table id="grid1"></table> <table class="standard-grid" style="margin:10px 0;"> <tr> <tr> <td style="width:33%;"> <input type="checkbox" id="showButtons" checked="checked" /><label for="showButtons">show Done/Cancel buttons</label> </td> </tr> </tr> <tr> <td colspan="3"> <input type="checkbox" id="cancelUpdate" /><label for="cancelUpdate">cancel update of dataSource</label> <br /> <input type="checkbox" id="cancelEditCar" /><label for="cancelEditCar">cancel editing of rows with <b>Product Name</b> "Car"</label> <br /> <input type="checkbox" id="keepEditingPrice" title="keep row in edit mode if user deleted ListPrice or it is undefined" /><label for="keepEditingPrice" title="keep row in edit mode if user deleted ListPrice or it is undefined">keep row-editing if <b>ListPrice</b> is missing</label> <br /> <input type="checkbox" id="autoFill" checked="checked" /><label for="autoFill">automatically fill <b>Code</b> with "ABC1" if its length is less than 4</label> </td> </tr> <tr> <td colspan="3"> Trigger actions from code: <br /> <input id="startEdit" type="button" value="Start edit 2nd row" style="margin-top:5px;" /> <input id="endEdit" type="button" disabled="disabled" value="Stop edit and update" /> <input id="cancelEdit" type="button" disabled="disabled" value="Stop edit and cancel update" /> </td> </tr> </table> </div> </section> </div> </div> <footer> </footer> </div></body></html>
Hi Nick,Just to clarify: only the grid is scrolled to the 1st row (not the entire page).If you take a look at the igGrid's DOM (with Firebug for example) when you click the "Done" button, you will notice that the DOM of the grid is rebuild.This is a specific characteristic of the scenario when you have continuous virtualization and you're updating a row.The only solution I can recommend is to use the virtualScrollTo API method of the igGrid, with it's input parameter is is the top offset of the grid scroller. I've updated your modified demo and attached it to my current reply so feel free to take a look at how it works.Unfortunately this solution isn't perfect (if I recall correctly, we have a feature request to improve this API method in order to handle such scenarios), but with some tweaking of the top offset that's being passed to virtualScrollTo, it might just work for you.Hope this helps,Borislav
I've tried using that, and no it isn't perfect. It also doesn't help with horizontal scrolling causing the same issue. Thank you for the complete and informative answer though. At least I know I'm not missing anything.
You're welcome Nick.I try to provide an out-of-the-box solution whenever it's available and a workaround when no such solution exists.However, in such tough situations (virtualization is a tough one to manipulate) I can give you only the truth (or at least do my best to do so) so that you can make your decisions knowing exactly what can and cannot be done.
All the best,Borislav