Loading 10k+ rows of JSON into jqGrid 1k rows at a time - jqgrid

Introduction
I load a JSON object via ajax into the browser outside of jqGrid. I then load the JSON into a grid, which the initial setting datatype: "local", using the following:
$("#myGrid").jqGrid("setGridParam", {datatype: "json", loadonce: true});
$("#myGrid")[0].addJSONData(jsonObject);
$("#myGrid").jqGrid("setGridParam", {datatype: "local", loadonce: true});
The above tricks the grid into allowing me to load a JSON object without the jqGrid making an automatic call for it (which is its default behavior).
Implementing server-side pagination would be ideal, but not currently feasible in my case.
The Question
I need to load about 10-20k rows into the grid from JSON. But that takes several seconds. To boost performance, I'm trying to add 1k rows from a JSON object at a time (which is fast and doesn't stall the browser), but I can't figure out how to do that with a JSON object on my hands. Here is my attempt, which loads the first 1k rows into the grid, but doesn't properly update the pager, and doesn't allow me to add additional rows via addJSONData.
$("#myGrid").jqGrid({
jsonReader: {
page: function(json) { return 1; },
records: function(json) { return json.records; },
repeatitems: false,
root: "objects",
total: function(json) { return json.totalPages; }
},
// . . .
}
$.jgrid.extend({
loadJsonFirstPageOnly: function(json) {
return this.each(function() {
// copy the first page of data into a truncated JSON object to load into the grid
var resultsPerPage = $("#" + this.id + "_ResultsPerPage").val();
var numberOfPages = parseInt(json.objects.length / resultsPerPage);
var firstPageData = {"objects": [], "pages": numberOfPages, "records": json.objects.length};
for (var i = 0; i < 1000 && i < json.objects.length; i++) {
firstPageData.objects[i] = json.objects[i];
}
// load the truncated JSON object into the grid
var $this = $(this).jqGrid("setGridParam", {datatype: "json", loadonce: true});
this.addJSONData(firstPageData);
$this.jqGrid("setGridParam", {datatype: "local", loadonce: true});
// adjust the grid's internal state
this.p.allData = json;
});
}
});
Any thoughts? Thanks!

Related

What causes jqgrid events to fire multiple times?

Our jqgrid is configured in an initgrid function that is called as the last statement of a ready handler. For some reason, the gridcomplete function is getting called multiple times. With the code below, it gets called twice, but it had been getting called 3 times. Twice is bad enough. After stepping through it multiple times, I don't see what is triggering the second execution of the gridComplete function.
When I hit the debugger at the start of gridComplete, the call stack is virtually identical each time, the only difference being a call to 'L' in the jqgrid:
Anyone have an idea why this is occurring? We are using the free version 4.13, in an ASP.net MVC application.
$(function(){
....
initGrid();
}
function initGrid(){
$gridEl.jqGrid({
xhrFields: {
cors: false
},
url: "/IAConsult/GetWorkFlowIARequests",
postData: {
showAll: showAllVal,
role: role,
IsIAArchitect: userIsIA
},
datatype: "json",
crossDomain: true,
loadonce: true,
mtype: 'GET',
sortable: true,
viewrecords: true,
pager: '#workFlowIAGridPager',
multiselect: true,
rowNum: 50,
autowidth: true,
colModel: [...],
beforeSelectRow: function (rowid, e) {
var $myGrid = $(this),
i = $.jgrid.getCellIndex($(e.target).closest('td')[0]),
cm = $myGrid.jqGrid('getGridParam', 'colModel');
return (cm[i].name === 'cb');
},
jsonReader: {
repeatitems: true,
root: "IAConsultWorkflowRequestsList"
},
beforeSubmitCell: function (rowid, name, value, iRow, iCol) {
return {
gridData: gridData
};
},
serializeCellData: function (postdata) {
return JSON.stringify(postdata);
},
gridComplete: function () {
console.log('grid complete');
let rowIDs = $gridEl.getDataIDs();
let inCompleteFlag = false;
let dataToFilter = $gridEl.jqGrid('getGridParam', 'lastSelectedData').length == 0
? $gridEl.jqGrid("getGridParam", "data")
: $gridEl.jqGrid('getGridParam', 'lastSelectedData');
let $grid = $gridEl, postfilt = "";
let localFilter = $gridEl.jqGrid('getGridParam', 'postData').filters;
let columnNames = columns.split(',');
$('.moreItems').on('click', function () {
$.modalAlert({
body: $(this).data('allitems'),
buttons: {
dismiss: {
caption: 'Close'
}
},
title: 'Design Participants'
});
});
rowCount = $gridEl.getGridParam('records');
gridViewRowCount = rowCount;
let getUniqueNames = function (columnName) {
... };
let buildSearchSelect = function (uniqueNames) {
var values = {};
values[''] = 'All';
$.each(uniqueNames,
function () {
values[this] = this;
});
return values;
};
let setSearchSelect = function (columnName) {
...
};
function getSortOptionsByColName(colName) {
...
}
$grid.jqGrid("filterToolbar",
{ stringResult: true, searchOnEnter: true });
if (localFilter !== "" && localFilter != undefined) {
globalFilter = localFilter;
}
let grid = $gridEl.jqGrid("setGridParam",
{
postData: {
"filters": globalFilter,
showAll: showAllVal,
role: role,
IsIAArchitect: userIsIA
},
search: true,
forceClientSorting: true
});
//grid.trigger("reloadGrid");
//Ending Filter code
for (i = 0; i < columnNames.length; i++) {
var htmlForSelect = '<option value="">All</option>';
var un = getUniqueNames(columnNames[i]);
var $select = $("select[id='gs_workFlowIAGrid_" + columnNames[i] + "']");
for (j = 0; j < un.length; j++) {
val = un[j];
htmlForSelect += '<option value="' + val + '">' + val + '</option>';
}
$select.find('option').remove().end().append(htmlForSelect);
}
debugger;
},
// all grid parameters and additionally the following
loadComplete: function () {
$gridEl.jqGrid('setGridWidth', $(window).width(), true);
$gridEl.setGridWidth(window.innerWidth - 20);
},
height: '100%'
});
I personally almost never use gridComplete callback. It exists in free jqGrid mostly for backwards compatibility. I'd recommend you to read the old answer, which describes differences between gridComplete and loadComplete.
Some additional advices: it's dangerous to register events inside of callbacks (see $('.moreItems').on('click', ...). If you need to make some actions on click inside of grid then I'd recommend you to use beforeSelectRow. Many events, inclusive click event supports event bubbling and non-handled click inside of grid will be bubbled to the parent <table> element. You use already beforeSelectRow callback and e.target gives you full information about clicked element.
I recommend you additionally don't use setGridParam method, which can decrease performance. setGridParam method make by default deep copy of all internals parameters, inclusive arrays like data, which can be large. In the way, changing one small parameter with respect of setGridParam can be expensive. If you need to modify a parameter of jqGrid then you can use getGridParam without additional parameters to get reference to internal object, which contains all jqGrid parameters. After that you can access to read or modify parameters of jqGrid using the parameter object. See the answer for example for small code example.
Also adding the property
loadonce: true
might help.

Slick grid values not populating in the grid

I am trying to populate slick grid it is showing the values while debugging but not showing in the web page.
I have added all the required references. Here is the js function.
function LoadMonthStatus(dropdownYear, buttonId) {
try {
$(".screen").css({ opacity: 0.5 });
$(".slick-cell").css({ opacity: 0.5 });
dirtyFlag = false;
var drpYear = document.getElementById(dropdownYear);
year = drpYear.options[drpYear.selectedIndex].value;
data = [];
var dropdownoptions = ""; // "hard,soft,closed";
var columns = {};
var options = {
editable: true,
enableCellNavigation: true,
asyncEditorLoading: false,
autoEdit: true,
autoHeight: true
};
columns = [
{ id: "month", name: "Month", field: "Month", width: 250 },
{ id: "status", name: "Close Status", field: "Status", options: columnOptions, editor: Slick.Editors.Select, width: 150 }
];
$.ajax({
type: "GET",
url: "http://localhost:51072/PULSE.Service/api/MonthCloseStatus/LoadMonths",
data: { year: $("#drpYear").val() },
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (msg) {
data = msg;
monthStatusGrid = new Slick.Grid("#slkgrdMonths", data, columns, options);
//grid.setSelectionModel(new Slick.CellSelectionModel());
monthStatusGrid.onCellChange.subscribe(function (e, args) {
var cell = monthStatusGrid.getCellNode(args.row, args.cell);
cell.className += cell.className ? ' slick-cell-modified' : 'slick-cell-modified';
dirtyFlag = true;
});
if (msg == null || msg == "")
document.getElementById(buttonId).disabled = true;
else
document.getElementById(buttonId).disabled = false;
//Enable the button
$(".screen").css({ opacity: 1 });
$(".slick-cell").css({ opacity: 1 });
},
error: function (xhr, textStatus, errorThrown) {
try {
var err = JSON.parse(xhr.responseText);
jAlert(err.Message, 'PULSE');
}
catch (e) {
jAlert(clientErrMessage, 'PULSE');
}
$(".screen").css({ opacity: 1 });
$(".slick-cell").css({ opacity: 1 });
}
});
}
catch (e) {
jAlert(clientErrMessage, 'PULSE');
}
}
Slick grid should be populated with months like Jan, Feb, March and its status respectively in 2 columns.
There are too many variables to give an answer. However I would recommend that you create the grid on page load and just populate it with data on ajax load, rather than creating the grid possibly each time.
In my experience, data not being visible is often CSS related.
Have a look at the samples in the SlickGrid repo, they are pretty comprehensive. Make sure you are using https://github.com/6pac/SlickGrid rather than the old MLeibman repo.
If you want assistance, the best idea is to create a stand alone test page that works with local files (apart from the ajax call) in the example folder of the slickgrid repo.
If you can share that, we can easily reproduce the behaviour. As a bonus, about 90% of the time you'll work out the problem while you are creating the example page.
Check to see
-if the property of data after ajax call is matching with the property provided in columns Field and vice versa.
- if the container u are passing is correct.

free-jqGrid 4.15.6 ExpandNode producing runtime error

I recently upgraded from the original Tony Tomov's jqGrid v4.5.4 to Oleg's free-jqGrid 4.15.6.
When using 4.5.4, the code below worked perfect, but in 4.15.6, it does not. The run-time error is produced by the two calls to expandNode and expandRow located in the forceNodeOpen() function. The error given:
TypeError: rc1 is undefined
The forceNodeOpen() function is used to force all ancestor nodes of the current treegrid node to show as expanded. Much like a table of contents...if we load an initial topic node, we want the whole topic hierarchy to be expanded:
// force a node (if expandable) to stay expanded
function forceNodeOpen(rowid)
{
var record = $("#tree").jqGrid('getRowData', rowid);
var div = $('tr#'+rowid).find('div.ui-icon.treeclick');
div.removeClass('ui-icon-triangle-1-e tree-plus').addClass('ui-icon-triangle-1-s tree-minus');
**$('#tree').jqGrid('expandNode', record);**
**$('#tree').jqGrid('expandRow', record);**
// get all ancestoral parents and expand them
// *NOTE*: the getAncestorNodes function of grid was not usable for
// some reason as the same code below just would not work
// with the return array from getAncestorNodes
var parent = $("#tree").jqGrid('getNodeParent', record);
while(parent)
{
forceNodeOpen(parent['id']);
parent = $("#tree").jqGrid('getNodeParent', parent);
}
}
// using topic url, get the tree row id
function getTopicID(topic)
{
var nodes = $('#tree').jqGrid('getRowData');
var rowid = 1;
$.each(nodes, function(e,i)
{
var url = $(this).attr('url');
if(url == topic)
{
rowid = $(this).attr('id');
return false;
}
});
return rowid;
}
// post request to help server via ajax
function loadTopic(topic)
{
// no need to load again
if(loadedtopic == topic) { return false; }
// select the topic node
var rowid = getTopicID(topic);
loading = true;
$('#tree').jqGrid('setSelection', rowid);
forceNodeOpen(rowid);
loading = false;
// wipe content
$('h1#help_content_topic span:first').html('Loading...');
$('div#help_content').html('');
// block UI for ajax posting
blockInterface();
// request help content
$.ajax(
{
type: 'POST',
url: '/index.php',
data: { 'isajax': 1, 'topic': topic },
success: function(data)
{
$.unblockUI();
$('h1#help_content_topic span:first').html(data['topic']);
$('div#help_content').html(data['content']);
return false;
}
});
// save current topic to prevent loading same topic again
loadedtopic = topic;
}
// table of contents
$('#tree').jqGrid({
url: "topics.php",
datatype: "xml",
autowidth: true,
caption: "Help Topics",
colNames: ["id","","url"],
colModel: [
{name: "id",width:1,hidden:true, key:true},
{name: "topic", width:150, resizable: false, sortable:false},
{name: "url",width:1,hidden:true}
],
ExpandColClick: true,
ExpandColumn: 'topic',
gridview: false,
height: 'auto',
hidegrid: false,
pager: false,
rowNum: 200,
treeGrid: true,
treeIcons: {leaf:'ui-icon-document-b'},
// auto-select topic node
gridComplete: function()
{
// save current topic to prevent loading same topic again
loadedtopic = '<? echo($topic) ?>';
var rowid = getTopicID('<? echo($topic) ?>');
$('#tree').jqGrid('setSelection', rowid);
forceNodeOpen(rowid);
$.unblockUI();
},
// clear initial loading
loadComplete: function()
{
loading = false;
},
onSelectRow: function(rowid)
{
// ignore initial page loads
if(loading) { return false; }
// load the selected topic
var topic = $("#tree").jqGrid('getCell',rowid,'url');
loadTopic(topic);
}
});
The forceNodeOpen(rowid) is invoked from the loadTopic() function, which is called inside the onSelectRow() event of the treegrid.
Not sure what 4.5.4 did that allowed this code to work but 4.15.6 finds it to be an error. The offending line in 4.15.6.src.js:
expandNode: function (rc) {
...
if (p.treedatatype !== "local" && !base.isNodeLoaded.call($($t), p.data[p._index[id]]) && !$t.grid.hDiv.loading) {
// set the value which will be used during processing of the server response
// in readInput
p.treeANode = rc1.rowIndex;
p.datatype = p.treedatatype;
...});
I have only included a few lines from the above core function. It's the p.treeANode = rc1.rowIndex that throws the error.
I have to be missing something but do not know what. Hoping somebody can tell me what to do. If I remark out the two expandNode and expandRow treegrid function calls in forceNodeOpen() function, the system does not error out and the desired topic loads. But the hierarchy is not expanded as desired.
START EDIT 1
The server-side code that returns the topic nodes:
echo("<?xml version='1.0' encoding='UTF-8'?>\n");
require('db.php');
echo("<rows>\n");
echo("<page>1</page>\n");
echo("<total>1</total>\n");
echo("<records>1</records>\n");
$sql = "SELECT node.id, node.parentid, node.topic, node.url, node.lft, node.rgt, (COUNT(node.parentid) - 1) AS depth
FROM helptopics AS node, helptopics AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
GROUP BY node.url ORDER BY node.lft";
$stmt = AMDB::selectStatement($sql);
while($data = $stmt->fetch(PDO::FETCH_ASSOC))
{
$id = $data['id'];
$pid = $data['parentid'];
$topic = $data['topic'];
$url = $data['url'];
$lft = $data['lft'];
$rgt = $data['rgt'];
$leaf = $rgt - $lft == 1 ? 'true' : 'false';
$exp = 'false';
$dep = $data['depth'];
echo("<row><cell>{$id}</cell><cell>{$topic}</cell><cell>{$url}</cell><cell>{$dep}</cell><cell>{$lft}</cell><cell>{$rgt}</cell><cell>{$leaf}</cell><cell>{$exp}</cell></row>\n");
}
echo("</rows>\n");
exit();
END EDIT 1
I see that you use
var record = $("#tree").jqGrid('getRowData', rowid);
to get node data of TreeGrid. It wasn't good, but it worked in old jqGrid because it saved internal jqGrid data twice: once as local data and once more time in hidden cells of the grid.
You should use getLocalRow instead of getRowData:
var record = $("#tree").jqGrid('getLocalRow', rowid);
The code will work on both old jqGrid and free jqGrid. Additionally, getLocalRow has always better performance comparing with getRowData even in jqGrid 4.5.4.

Kendo ui dropdownlist virtualization fails on 2nd page retrieval

Kendo ui version 2015.2.805
I have a kendo dropdownlist configured per examples in the kendo docs to do paging of data. It works for page 1, loading 80 entries from the server using the take/skip parameters. As I page down the list to where new items should be loaded I can see in fiddler that the widget is requesting take=80, skip=80 (which is page 2) and the server is returning the requested records. But the widget just keeps re-requesting the same page over and over until I stop it. During this time the unloaded entries are shown in the list as 'loading' with a animated spinner.
To do virtualization the widget requires a valueMapper (I am using the older kendo UI so it is required). I have implemented it and while I am unclear on what it should return (the item index I believe) I know from the docs that if it requests a non-existent value to return [], and that is what I am doing in that case. I modified the convertValues function to send only a single index instead of the example array, but the valueMapper is only called once upon initialization so any errors in what I'm returning can't have any effect on this problem (I believe).
When the widget is first initialized in fiddler I see that the valueMapper is called with a value of -1 and the server returns [], then the widget calls for the paged data (take=80, skip=0) and the server returns that and the widget displays the data normally.
When I page down to unloaded items, the widget calls for page two data (take=80, skip=80) and the server returns it but the widget keeps re-requesting the data. The widget never calls the valueMapper after the first call (which may be normal).
I have the page size set correctly for the height and itemHeight, but this would only cause page one to be loaded twice (which it is not).
Here is the setup:
wizard.paramMap = function (data, type) {
var params = {};
params.take = data.take;
params.skip = data.skip;
return params;
};
$("#clientField").kendoDropDownList({
autoWidth: true,
dataTextField: "text",
dataValueField: "value",
virtual: {
itemHeight: 26,
valueMapper: function(options) {
$.ajax({
url: resturi + "clientlist/valueMapper",
dataType: "json",
type: "GET",
data: convertValues(options.value),
success: function (data) {
console.log("valueMapper = " + data)
options.success(JSON.parse(data));
}
})
}
},
height: 520,
dataSource: {
transport: {
read: {
url: resturi + "clientlist",
dataType: "json",
type: "get"
},
parameterMap: wizard.paramMap
},
schema: {
data: function (response) {
console.log("clientlist = " + response);
var o = JSON.parse(response);
return o;
}
},
pageSize: 80,
serverPaging: true
}
});
function convertValues(value) {
var data = {};
value = $.isArray(value) ? value : [value];
for (var idx = 0; idx < value.length; idx++) {
//data["values[" + idx + "]"] = value[idx];
data["values"] = value[idx];
break;
}
return data;
}
The data returned by the datasource must return a total record count along with the paged data. This is the same as for paging in the kendo datagrid. So changing server output to:
{"total":X,"data":[...some data...]}
and the schema to:
schema: {
total: function (response) {
return (JSON.parse(response).total);
},
data: function (response) {
return (JSON.parse(response).data);
}
}
fixes the problem.

Youtube search autocomplete not working with Framework 7

I am trying to use Framework 7 autocomplete feature with youtube search API v3. I had used search api for autocomplete using Jquery UI. Framework 7 has also Ajax autocomplete feature. But my code is not working with Framework 7.
Here is my youtube search autocomplete js code for jquery UI, that works 100% and shows up video search suggestion on text input
//code for auto complete using jquery UI works perfect
jQuery( "#vid-search" ).autocomplete({
source: function( request, response ) {
//console.log(request.term);
var sqValue = [];
jQuery.ajax({
type: "POST",
url: "http://suggestqueries.google.com/complete/search?hl=en&ds=yt&client=youtube&hjson=t&cp=1",
dataType: 'jsonp',
data: jQuery.extend({
q: request.term
}, { }),
success: function(data){
console.log(data[1]);
obj = data[1];
jQuery.each( obj, function( key, value ) {
sqValue.push(value[0]);
});
response( sqValue);
}
});
},
select: function( event, ui ) {
setTimeout( function () {
youtubeApiCall();
}, 300);
}
});
And here is my youtube search autocomplete code with framework 7, Doest show up video search suggestions on text inpute..
var autocompleteDropdownAjax = myApp.autocomplete({
input: '#autocomplete-dropdown-ajax',
openIn: 'dropdown',
preloader: true, //enable preloader
valueProperty: 'id', //object's "value" property name
textProperty: 'name', //object's "text" property name
limit: 20, //limit to 20 results
dropdownPlaceholderText: 'Try "JavaScript"',
expandInput: true, // expand input
source: function (autocomplete, query, request, response, render) {
var results = [];
if (query.length === 0) {
render(sqValue);
return;
}
// Show Preloader
autocomplete.showPreloader();
// Do Ajax request to Autocomplete data
$$.ajax({
type: "POST",
url: "http://suggestqueries.google.com/complete/search?hl=en&ds=yt&client=youtube&hjson=t&cp=1",
dataType: 'jsonp',
data: jQuery.extend({
q: request.term
}, { }),
success: function (data) {
// Find matched items
console.log(data[1]);
obj = data[1];
jQuery.each( obj, function( key, value ) {
sqValue.push(value[0]);
});
response( sqValue);
// Hide Preoloader
autocomplete.hidePreloader();
// Render items by passing array with result items
render(sqValue);
}
});
},
select: function( event, ui ) {
setTimeout( function () {
youtubeApiCall();
}, 300);
}
});
That is because the jSon result should be in a special format, the result rendered by the API looks like:
["funn",["funny vines","funny videos 2016","funny videos","funny","funnel vision","funny cat videos","funny fails","funny pranks","funny cats","funny songs"]]
First parent array has the query you typed, second child array has the result array, you must push the child array to the result.
for (var i = 0; i < data[1].length; i++) {
results.push(data[1][i])
}
And here is the result:
Full code:
var autocompleteDropdownAjax = myApp.autocomplete({
input: '#autocomplete-dropdown-ajax',
openIn: 'dropdown',
preloader: true, //enable preloader
valueProperty: 'value', //object's "value" property name
textProperty: 'text', //object's "text" property name
limit: 20, //limit to 20 results
dropdownPlaceholderText: 'Search Youtube',
expandInput: true, // expand input
source: function(autocomplete, query, render) {
var results = [];
var returned = [];
if (query.length === 0) {
render(results);
return;
}
// Show Preloader
autocomplete.showPreloader();
// Do Ajax request to Autocomplete data
$$.ajax({
url: 'http://suggestqueries.google.com/complete/search?client=firefox&ds=yt',
method: 'GET',
crossDomain: true,
dataType: 'json',
//send "query" to server. Useful in case you generate response dynamically
data: {
q: query
},
success: function(data) {
// Find matched items
for (var i = 0; i < data[1].length; i++) {
results.push(data[1][i])
}
// Hide Preoloader
autocomplete.hidePreloader();
// Render items by passing array with result items
render(results);
}
});
}
});
I hope this helps :)

Resources