jqGrid pager only returning first page - asp.net-mvc-4

I want to have client side paging.
But for some reason, I only seem to get back the first page? Even though I know I have two pages worth of data (IE... I step through my code, and I definitely have two...)... What is more baffling is that my links to navigate through the pages never seem to be correct... For instance I would expect the following screen to say 1 of 2...
Also I would expect the bottom right hand side to say View 1-15 of 21?
My feeling is that I am doing something wrong in my data layer to give this pager it's info.
So It only returns the first page.
public static string JsonifyEnc(IEnumerable<TemplateModel> model, int popId, int page, int rows) {
TemplateModel variable = model.ToArray()[0];
ArrayList al = new ArrayList();
//foreach (PatientACOModel patMod in variable.Template) {
int i = 1;
int rowstart = (page * rows + 1) - rows;
int rowend = page * rows;
//Here is where I create the rows... nothing special here
var griddata = new {
total = variable.Template.Count % rows > 0 ? (variable.Template.Count / rows) + 1 : (variable.Template.Count / rows),
page = page,
records = al.Count,
rows = al.ToArray()
};
When I quick wath the total variable it says two?
This would be the first part of my json string that is returned...
{"total":2,"page":1,"records":15,"rows":
So it's there. Also, this is how I am building up my jqGrid...
$(document).ready(function () {
jQuery("#frTable").jqGrid ({
cmTemplate: { sortable: false },
caption: '#TempData["POPNAME"]' + ' Population',
datatype: 'json',
mtype: 'GET',
url: '/Encounters/GetAjaxPagedGridData/', //'Url.Action("GetAjaxPagedGridData", "Encounters", new { popId = TempData["POPULATIONID"] })',//
postData: { popId: '#TempData["POPULATIONID"]'},
pager: '#pager',
jsonReader: {repeatitems: false},
loadonce: true,
height: 'auto',
gridview: true,
viewrecords: true,
rowNum: 15,
shrinkToFit: false,
autowidth: true,

If you use loadonce: true on the client side you should change the server code so that it ignores page and rows options and returns all data. You should just sort the data corresponds to sidx and sord parameter (see sortname and sortorder in jqGrid). You don't need to fill total, page and records parts in the response.
If you use loadonce: true jqGrid load the data and save it in internal data and _index parameters. After that jqGrid change datatype option of jqGrid to "local". So all later sorting, filtering (searching) and paging of data will be done locally.

Related

How to use gt/le operator in aurelia slickgrid with Odata

I want to send my own operator in odata request and not use the aurelia slickgrid inbuilt "eq" operator.
This is my column definition
{
id: 'LockoutEndDateUtc', name: 'Status', field: 'LockoutEndDateUtc', minWidth: 85, maxWidth: 95,
type: FieldType.boolean,
sortable: true,
formatter: Formatters.multiple,
params: { formatters: [this.StatusFormatter, Formatters.checkmark] },
filterable: true,
filter: {
collection: [
{ value: 'le ' + (() => {const dt = new Date(); return dt.toISOString().split('.')[0] + "Z";})(), label: 'True' },
{ value: 'gt ' + (() => {const dt = new Date(); return dt.toISOString().split('.')[0] + "Z";})(), label: 'False' }
], //['', 'True', 'False'],
model: Filters.singleSelect,//multipleSelect//singleSelect,
}
}
This is the UI
This is how the request filter looks like..
$filter=(LockoutEndDateUtc%20eq%20le%202022-06-28T12%3A59%3A25Z)
If i remove %20eq from the above request, everything else works. So my question is how to i remove %20eq. Or how do i send my own gt, le in the request.
You can't really do that on a boolean filter (you could however do it on a date filter with operator) and I don't think I've added any ways to provide a custom filter search the way you want to do it, but since you're using OData, you have a bit more control and you could change the query string yourself. To be clear, it's not at all recommended to change the OData query string, it's a last solution trick and at your own risk, but for your use case it might be the only way to achieve what you want.
prepareGrid() {
this.gridOptions = {
// ...
backendServiceApi: {
service: new GridOdataService(),
process: (query) => this.getCustomerApiCall(query),
} as OdataServiceApi
};
}
}
getCustomerApiCall(query: string) {
let finalQuery = query;
// in your case, find the boolean value from the column and modify query
// your logic to modify the query string
// untested code, but it would probably look similar
if (query.includes('LockoutEndDateUtc%20eq%20true')) {
// calculate new date and replace boolean with new date
finalQuery = query.replace('LockoutEndDateUtc%20eq%20true', 'LockoutEndDateUtc%20le%202022-06-28T12%3A59%3A25Z');
}
return finalQuery;
}
Another possible solution but requires a bit more work.
If I'd be using a regular grid, without backend service and without access to the query string, I would probably add an external drop down outside of the grid and also add the date column and then control filters in the grid by using dynamic filtering. You can see a demo at Example 23, the principle is that you keep the column's real nature (date in your case) and filter it, if you want something like a "below today's date" then add an external way of filtering dynamically (a button or a drop down) and control the filter dynamically as shown below (from Example 23)

Search Query Not Handling Large Number of Users

We have an asp.net-core website which handles users search as follows:
public async Task<ICollection<UserSearchResult>> SearchForUser(string name, int page)
{
return await db.ApplicationUsers.Where(u => u.Name.Contains(name) && !u.Deleted && u.AppearInSearch)
.OrderByDescending(u => u.Verified)
.Skip(page * recordsInPage)
.Take(recordsInPage)
.Select(u => new UserSearchResult()
{
Name = u.Name,
Verified = u.Verified,
PhotoURL = u.PhotoURL,
UserID = u.Id,
Subdomain = u.Subdomain
}).ToListAsync();
}
The query translates to something similar to the following:
SELECT [t].[Name], [t].[Verified], [t].[PhotoURL], [t].[Id], [t].[Subdomain] FROM (SELECT [u0].* FROM [AspNetUsers] AS [u0] WHERE (((CHARINDEX('khaled', [u0].[Name]) > 0) OR ('khaled' = N'')) AND ([u0].[Deleted] = 0)) AND ([u0].[AppearInSearch] = 1) ORDER BY [u0].[Verified] DESC OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY ) AS [t]
In Client-Side we use typeahead and bloodhound as follows:
engine = new Bloodhound({
identify: function (user) {
return user.UserID;
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name'),
dupDetector: function (a, b) { return a.UserID === b.UserID; },
remote: {
cache: false,
url: '/account/Search?name=%QUERY&page=0',
wildcard: '%QUERY'
}
});
and we configure typeahead as follows:
$('#demo-input').typeahead(
{
hint: $('.Typeahead-hint'),
menu: $('.Typeahead-menu'),
minLength: 3,
classNames:
{
open: 'is-open',
empty: 'is-empty',
cursor: 'is-active',
suggestion: 'Typeahead-suggestion',
selectable: 'Typeahead-selectable'
}
},
{
source: engineWithDefaults,
displayKey: 'name',
templates:
{
suggestion: template,
empty: empty,
footer: all
},
limit: 5
})
The search works just find on localhost and the query runs great as a sql query.
I have also created an index on Verified and cut the speed to 1 second or less.
Our website has millions of registered users and the problem is that as soon as we make search available for all users the DTU percentage on Azure goes to 100% and the queries timeout.
We also have a redis cache to speed-up similar queries but this didn't help us with this issue.
Your support is appreciated :)
It's quite likely to be u.Name.Contains(name) i.e. CHARINDEX('khaled', [u0].[Name]) > 0 which will have to scan the entire table or, at best, the index. That will be slow and there's not much you can do about it.
If you have a large bias to deleted or appearInSearch you might be able to use a conditional index but these types of searches are notoriously slow. You will need some special constructs to make this work.

OnDemandGrid (Dgrid) won't fire a request

When scrolling the grid, it does not fire a new request for more items when you reach the end of the 20 items
I am using Dojo version 1.10.4. I created a TrackableRest Store
var TrackableRest = declare([Rest, Trackable]);
var interceptStore = new TrackableRest({
target: 'rest/intercepts/',
accepts: "application/json",
sortParam: 'sort',
rangeStartParam: 'offset',
rangeCountParam: 'limit',
headers:{
'Accept': "application/json",
'Content-Type':"application/json",
'charset':"UTF-8"
},
idProperty: 'id'
});
Then I created a grid:
var grid = window.grid = new CustomGrid({
id: 'grid',
//sort: [{property:'ELNOT'},{property:'RF_AVG'}], // Initialize sort on last name, ascending
collection: interceptsStore,
sort: "id",
getBeforePut: false,
columns: getColumns(),
allowSelectAll: true,
loadingMessage: 'Loading data...',
noDataMessage: 'No results found.',
title: "All",
minRowsPerPage: 20,
maxRowsPerPage: 250
});
The request is sent
http://localhost:8080/OlympiaMap/rest/intercepts/?sort=+id&offset=0&limit=20
And the response includes the header Content-Range with a value of items=0-20/606
and the data looks like
enter image description here
So after two days of waiting I realized that even though you return the Content Header in the desired format it appears that it is using the values in your response total, limit and offset (or whatever you choose for your start and count params). For instance, in my response headers I returned Content-Range with a value of "items 0-20/606" but in the actual response I have a items array with 20 items and a total of 20. When I hard coded this valued (just to test) to match the 606 value I started to see the virtual scrolling work and send request incrementing the start and count. Like this
1st on page load
http://localhost:8080/OlympiaMap/rest/intercepts/?sort=+id&offset=0&limit=20
While scrolling
http://localhost:8080/OlympiaMap/rest/intercepts/?sort=+id&offset=20&limit=20
etc and so on
This is due to a lack of documentation on part of the dstore and dgrid

jQuery Datatables highlighting row

I'm using datatables version 1.9.4 and am having a problem with adding a class to certain rows.
I have multiple datatables, all with class 'display'. I'm using jQuery tabs to display each datatable on a separate tab.
All is working well, except I want to add a class to a table row depending on the column values; if column 6 is less than column 14, I want to add myClass.
I've found suggestions to use fnRowCallback, but I'm getting random results, such as sometimes if column 6 is less than column 14, myClass gets added correctly, but other times if column 14 is less than column 6 myClass still gets added!
This doesn't happen for all rows though, so it's pretty random.
Here's the code I'm using
$(document).ready(function() {
$('.display').dataTable({
"bJQueryUI": true,
"sPaginationType": "full_numbers",
"bProcessing": true,
"bServerSide": true,
"bScrollCollapse": true,
"sScrollY": "300px",
"sAjaxSource": "ajax.php",
"sDom": '<"H"lfr>t<"F"ipS>',
"oScroller": {
"loadingIndicator": true
},
"fnRowCallback": function( nRow, aData ) {
var $nRow = $(nRow);
if (aData[6] < aData[14]) {
$nRow.addClass("myClass");
}
return nRow
}
});
});
Is there something wrong with what I've done, or is it because I'm using multiple tables?
I think I have this working, but there is probably a cleaner way, so if anyone knows of a better way of doing this, please let me know!
I'm looping through all the rows for each table once the table has been drawn.....
"fnDrawCallback": function( oSettings ) {
for (var i = 0, row; row = oSettings.nTable.rows[i]; i++) {
price = Number(row.cells[4].innerHTML.replace(/[^0-9\.]+/g,""));
average = Number(row.cells[6].innerHTML.replace(/[^0-9\.]+/g,""));
if (price < average) {
row.className = row.className + " myClass";
}
}
}

View pictures or images inside Jquery DataTable

May I know if it is possible to put pictures or images into the rows of DataTables (http://datatables.net/) and how does one goes in doing it?
yes, simple way (Jquery Datatable)
<script>
$(document).ready(function () {
$('#example').dataTable({
"processing": true, // control the processing indicator.
"serverSide": true, // recommended to use serverSide when data is more than 10000 rows for performance reasons
"info": true, // control table information display field
"stateSave": true, //restore table state on page reload,
"lengthMenu": [[10, 20, 50, -1], [10, 20, 50, "All"]], // use the first inner array as the page length values and the second inner array as the displayed options
"ajax":{
"url": "#string.Format("{0}://{1}{2}", Request.Url.Scheme, Request.Url.Authority, Url.Content("~"))/Home/AjaxGetJsonData",
"type": "GET"
},
"columns": [
{ "data": "Name", "orderable" : true },
{ "data": "Age", "orderable": false },
{ "data": "DoB", "orderable": true },
{
"render": function (data, type, JsonResultRow, meta) {
return '<img src="Content/Images/'+JsonResultRow.ImageSrcDB+'">';
}
}
],
"order": [[0, "asc"]]
});
});
</script>
[edit: note that the following code and explanation uses a previous DataTables API (1.9 and below?); it translates easily into the current API (in most cases, just ditch the Hungarian notation ("fnRowCallback" just becomes "rowCallback" for example) but I have not done so yet. The backwards compatibility is still in place I believe, but you should look for the newer conventions where possible]
Original reply follows:
What Daniel says is true, but doesn't necessarily say how it's done. And there are many ways. Here are the main ones:
1) The data source (server or otherwise) provides a complete image tag as part of the data set. Don't forget to escape any characters that need escaping for valid JSON
2) The data source provides one or more fields with the information required. For example, a field called "image link" just has the Images/PictureName.png part. Then in fnRowCallback you use this data to create an image tag.
"fnRowCallback": function( nRow, aData, iDisplayIndex, iDisplayIndexFull ) {
var imgLink = aData['imageLink']; // if your JSON is 3D
// var imgLink = aData[4]; // where 4 is the zero-origin column for 2D
var imgTag = '<img src="' + imgLink + '"/>';
$('td:eq(4)', nRow).html(imgTag); // where 4 is the zero-origin visible column in the HTML
return nRow;
}
3) Similar to above, but instead of adding a whole tag, you just update a class that has the image as a background. You would do this for images that are repeated elements rather than one-off or unique pieces of data.
You mean an image inside a column of the table?
Yes, just place an html image tag
like this
<img src="Images/PictureName.png">
instead of putting data (some string) into a column just put the above html tag....
Asp.net core DataTables
The following code retrieve the image from a folder in WWWroot and the path in the DB field ImagePath
{
"data": "ImagePath",
"render": function (data) {
return '<img src="' + data + '" class="avatar" width="50" height="50"/>';
}
}
In case the Name of the picturefile is put together out of one or more informations in the table, like in my case:
src="/images/' + Nummer + Belegnummer + '.jpg"
you can make it that way:
var table = $('#Table').DataTable({
columnDefs: [
{
targets: 0,
render: getImg
}
]
});
function getImg(data, row, full) {
var Nummer = full[1];
var Belegnummer = full[4];
return '<img src="/images/' + Nummer + Belegnummer + '.jpg"/>';}
The picture is in the first column, so Targets = 0 and gets the Information from the same row.
It is necessary to add the parameters data and row.
It is not necessary to outsource it into a seperate function, here getImg, but it makes it easier to debug.