Datatables AJAX Data Source and Custom Pagination actions - javascript

I am using Datatables for building the table for my data.
I am passing JSON response from Server side [Java] to JSP and using Datatables js to build the tables in JSP using the same data.
If I pass 100 records, Datatables automatically provides facility to paginate and scroll through the results. But in my case, I always get only 20 records and when I user click on next page, I should call the localServlet to fetch fresh JSON response for next 20records.
So how do I configure Datatable so that whenever pagination function is used, call the AJAX resource and fetch the data and paint it.

There is a sample at http://datatables.net/release-datatables/examples/data_sources/server_side.html
however the code is in PHP. But having a look at it you will see that there request parameters it uses are iDisplayStart and iDisplayLength
You will have to reimplement this in your server side java.
Below is some code I have used (using Stripes)
Long count = (Long) getContext().getRequest().getSession(true).getAttribute("xbcount");
if (count == null) {
count = histDao.getCount();
getContext().getRequest().getSession(true).setAttribute("xbcount", count);
}
DataTableRes res = new DataTableRes (getsEcho(), count, count);
int rowStartIdxAndCount[] = {getiDisplayStart(), getiDisplayLength()};
List<HistoryUint> list = histDao.findAll(rowStartIdxAndCount);
And the DAO
public List<HistoryUint> findAll(final int... rowStartIdxAndCount) {
EntityManagerHelper.log("finding all HistoryUint instances",
Level.INFO, null);
try {
final String queryString = "select model from HistoryUint model";
Query query = getEntityManager().createQuery(queryString);
if (rowStartIdxAndCount != null && rowStartIdxAndCount.length > 0) {
int rowStartIdx = Math.max(0, rowStartIdxAndCount[0]);
if (rowStartIdx > 0) {
query.setFirstResult(rowStartIdx);
}
if (rowStartIdxAndCount.length > 1) {
int rowCount = Math.max(0, rowStartIdxAndCount[1]);
if (rowCount > 0) {
query.setMaxResults(rowCount);
}
}
}
return query.getResultList();
} catch (RuntimeException re) {
EntityManagerHelper.log("find all failed", Level.SEVERE, re);
throw re;
}
}

Related

Is there another way to set initial values on a Kendo virtualized mutliselect widget?

I have a kendo multi-select widget that is bound to a lot of data, to handle this I have virtualized the widget and put the stress onto the server.
I'm trying to select some initial data via a javascript function that passes an array of Ids. It works well only if the data that's being selected is on the first paged result set of the widget, if any of the Ids are further in then they are not selected and I need to fix it.
Here is the code for my widget:
#(Html.Kendo().MultiSelect()
.Name("Cars")
.DataTextField("Name")
.DataValueField("Id")
.Placeholder("Select cars...")
.Filter(FilterType.Contains)
.DataSource(source => { source
.Custom()
.ServerFiltering(true)
.Events(e => e.Error("errorHandler"))
.ServerPaging(true)
.PageSize(80)
.Type("aspnetmvc-ajax")
.Transport(transport => {
transport.Read("GetData", "Positions");
})
.Schema(schema => { schema
.Data("Data")
.Total("Total")
.Errors("Errors");
});
}))
The data is received from the GetData Method of the Positions controller which is tied to my cars repository.
GetData
public JsonResult GetData([DataSourceRequest] DataSourceRequest request)
{
var car = unitOfWork.CarRepository.Get().OrderBy(n => n.Name);
var results = vessel.ToDataSourceResult(request);
return Json(results);
}
Here is my function that runs after user input (button). I've added a sample array to show you what's passed in.
InitialSelection
function initialSelection(filter) {
//filter is: "6544","4880","6545","6548"
var carSelection = $("#Cars").data("kendoMultiSelect");
var filterArray = filter.split(',').map(Number);
if (filterArray.length > 0) {
console.log(filterArray)
carSelection.value(filterArray);
} else {
carSelection.value();
}
}
Is there a better way to make an initial selection of data than what I'm doing with the above javascript? As I said, if the ids are not on the first page of results that are returned, they are not selected which is frustrating.
You could simply change the read declaration to something like this:
.Transport(transport => {
transport.Read(read => read.Action("GetData", "Positions").Data("intialvalues"));
})
Then add a function for the initialvalues data object like:
function inititalvalues(){
var filterArray = filter.split(',').map(Number);
if(filterArray === undefined || filterArray === null || filterArray.length <= 0)
{
filterArray = []
}
return {filterValues: filterArray};
}
then in your read method on your controller you add the following:
public JsonResult GetData([DataSourceRequest] DataSourceRequest request, List<int> filterValues)
{
if (filterValues.Count > 0 && request.Page == 1)
{
..get the first page minus how many filterValues you have to inject the selected Items at the top of the list...
}
else if (filterValues.Count > 0 && request.Page > 1)
{
..get the page you need and remove any selected items from the page as they will be at the top...
}
else
{
..just get everything as normal as nothing should be selected at this stage.
}
return your paged resultset back...
}
hopefully that gives you a starting point.

query search returning same result twice

I recently learned that azure mobile services limits their query search to 50 (or 1000 if you change the default), but my app needs to be able to access unlimited records. I created this service to return a list that contains all of the records. I made the skip count 1 because I wanted to make sure it worked on a small scale before doing a skip count like 50. After debugging this code it keeps returning two of the same records with one of them different. There are three completely different records in my database so I am confused why it is picking up one of them twice. I made sure that when the request was made that skip amount was either 0,1,2.
return new Promise(function(resolve, reject) {
var x = 0
var total = 1;
list = [];
console.log(list.length);
while(x <= 3){
x++;
var query = table.where(where).includeTotalCount().skip(skipAmount).take(1).read().done(function (results) {
total = results.totalCount;
if(results[0] != undefined)
{
for (var i = results.length - 1; i >= 0; i--) {
list.push(results[i]);
console.log(results[i]);
}
}
else
{
resolve(list);
}
}, function (err) {
reject(err);
});
skipAmount++;
}
});
}
}
You may need to use the .orderBy() or .orderByDescending() query methods to make sure that the sorted data are returned.
table
.where(where)
.orderBy('updatedAt')
.includeTotalCount()
.skip(skipAmount)
.take(1)
.read()
.then(success, failure);

HtmlUnit: Skip JS execution

Problem:
I am parsing pages generated by JS using HtmlUnit.
I have to wait until all JS are loaded and then parse page.
All these pages share same JS scripts.
There is a one problematic script that won't parse.
The problematic script does not affect html rendering.
What I want to do:
I want to detect name of the problematic script.
Put this name on blacklist.
And skip it for further parsing.
This is the code I use for JS loading...
private void waitForJs(WebClient client, HtmlPage page) throws Exception {
int maxDelay = 1000;
int attempts = 10;
int i = client.waitForBackgroundJavaScript(maxDelay);
while (i > 0 && attempts > 0) {
i = client.waitForBackgroundJavaScript(maxDelay);
if (i == 0) {
break;
}
synchronized (page) {
page.wait(500);
}
log("Waiting for JS (" + i + "), attempts: " + attempts, false);
attempts--;
}
}
I had to intoduce "attempts" variable in order to not stuck on loading of damaged script. Instead of this, I want to put all problematic script(s) - remaining in waitForJs - on blacklist and skip their loading in the futures. Is it possible?
The code above has an encoding issue - we have to use the correct charset when getting the bytes from the content string.
WebResponseData data = new WebResponseData(content.getBytes(response.getContentCharset()),
response.getStatusCode(), response.getStatusMessage(), response.getResponseHeaders());
You can modify the content of the JavaScript to be empty string, as hinted here:
new WebConnectionWrapper(webClient) {
public WebResponse getResponse(WebRequest request) throws IOException {
WebResponse response = super.getResponse(request);
if (request.getUrl().toExternalForm().contains("my_url")) {
String content = response.getContentAsString();
// change content
content = "";
WebResponseData data = new WebResponseData(content.getBytes(),
response.getStatusCode(), response.getStatusMessage(), response.getResponseHeaders());
response = new WebResponse(data, request, response.getLoadTime());
}
return response;
}
};

jqGrid set records count

I have a jqGrid that points at an external API, which I have no control over. This external API has two endpoints:
Data Endpoint - Returns the table row data
Count Endpoint - Returns pagination counts etc.,
Based on user input the jqGrid filter gets converted into the appropriate query-string to filter the external API's Data and Count Endpoints.
I have jqGrids url being dynamically built based off of user input and targets the Data Endpoint...and during the loadBeforeSend() event it calls the Count Endpoint to get the latest pagination information based on users filter.
I am using the jsonreader capabilities:
jsonReader: {
root: 'products',
id: 'id',
records: function () {
return gridTotal;
},
total: function () {
// var totalPages = (gridTotal + reqOptions.limit-1) / reqOptions.limit;
var totalPages = Math.ceil(gridTotal / reqOptions.limit);
console.log('totalPages: ' + totalPages);
return totalPages;
},
page: function () {
//var totalPages = Math.ceil(gridTotal/20);
console.log('currentPage: ' + reqOptions.page);
return reqOptions.page;
}
},
Sample of the loadBeforeSend method:
loadBeforeSend: function (xhr, settings) {
settings.url = _newUrl || endpointURL;
// Lets fetch our data count...this may change as items get published so lets fetch during load
products.count(accessToken, _filterQuery)
.success(function (resp) {
// This is the total number of products that match our current search
gridTotal = resp.count;
}).catch(function (err) {
console.error(err);
});
}
Fetching from the Data Endpoint works really well, the issue is how to call the Count Endpoint and update the pagination data.
Tried the following:
Using setGridParam for records, last_page, etc.,
Using getGridParam('reccount')
Just update the html to look correct (not effective since paging will be off)
Is there a way to
Manually fire off the XHR for jqgrid URL...so I can request the Count first and when it returns then go fetch the Data?
Rerun the jsonreader functionality once the Count returns and gridTotal is set
Use a promise like structure to resolve records count
Updated to show #Oleg solution
loadBeforeSend: function (xhr, settings) {
settings.url = _newUrl || endpointURL;
// Lets fetch our data count...this may change as items get published so lets fetch during load
products.count(accessToken, _filterQuery)
.success(function (resp) {
// This is the total number of products that match our current search
gridTotal = resp.count;
gridTotal = resp.count;
grid.jqGrid('setGridParam', {
page: gridOpts.jsonReader.page(),
records: gridTotal,
lastpage: gridOpts.jsonReader.total()
});
grid[0].updatepager(false, true);
}).catch(function (err) {
console.error(err);
});
}
I hope that I correctly understand your problem. In the case you can first make loading of the main data from the Data Endpoint. Then (inside of loadComplete) you can start new $.ajax request manually to get the data from the Count Endpoint and to update the pagination data inside of success callback of the $.ajax.
What you need to do for the updating the pager is:
setting of page, records and lastpage parameters of jqGrid based on the data returned from the Count Endpoint.
call of $("#grid")[0].updatepager(false, true); which will uses the above options and to refresh the information on the pager.
You can see in the old answer and example of usage of .updatepager(false, true).

kendo grid template js with remote datasource

I got a table with remote datasource. in one cell I got the userID. Because I want to show the username instead of the user ID I made a custom template function:
function getUserName(pmcreator){
var user = '';
var data = ''
ds_userList.fetch(function(){
var data = this.data();
for(var i = 0, length = data.length; i < length; i++){
if(data[i].uID == pmcreator){
console.log(data[i].uLastname)
user = data[i].uLastname
}
}
});
return user
}
But its not working as it should, the cells stay empty. I got no errors but I see that the remote request to fetch the usernames is not completed before the grid is filled out. I thought the custom function of fetch is waiting for the results to return but it don't seems so.
Any Idea? I find thousends of examples but all with static local data. I need one with both remote, the grid conent and the template data.
This is probably due the fact that when yuo call the dataSource.fetch it fires off an async function, which causes the thread running the template to continue on. According to kendo you will need to return a control, then set the content of that control inside the callback.
Quick sample using Northwind categories...
Here is the template function
function getDetails(e) {
$.getJSON("http://services.odata.org/V3/Northwind/Northwind.svc/Categories", null, function(data) {
var category = data.value.filter(function(item, i) {
return item.CategoryID === e.CategoryID;
});
$("#async_" + e.CategoryID).html(category[0].Description);
});
return "<div id='async_" + e.CategoryID + "'></div>";
}
http://jsbin.com/ODENUBe/2/edit
I kept getting a recursive error maximum call stack when I just tried to fetch the dataSource, so I switched to a simple getJSON, but it should work pretty much the same.

Categories