KendoGrid - display datasource as rows (not columns) - kendo-grid

There is a way to display the dataSource as rows and not columns?
My dataSource has only one record with 30 properties. I would like to display the properties as rows (property1 => row1, property2 => row2, ...) instead of columns (property1 => column1, property2 => column2, ....).
In fact, I would like to retrieve 3 records (each one with 30 properties), but the 3 records would be the colums and the common 30 properties would be the rows.
How can I do that?
This is what I did so far, but I'm not sure this is the right way.
var _dataSource = function () {
var dataSource = new kendo.data.DataSource({
transport: {
read: { // returns only 3 records
url: url,
dataType: "json"
}
},
schema : {
data: "data",
total: "total",
parse: function (data) {
console.log(data);
var dataArray = [];
var i = 0;
for (var property in data[0]) {
console.log(property);
console.log(data[0][property]);
var record = {
header: "",
headerId: "",
record1: "",
record2: "",
record3: ""
};
record.header = headerRows[i]; // array of header string (the 30 properties)
record.headerId = property;
record.record1 = data[0][property];
record.record2 = data[1][property];
record.record3 = data[2][property];
console.log(record);
dataArray.push(record);
i++;
}
return dataArray;
}
}
});
return dataSource;
};
But this gives the error: TypeError: e is undefined

Got it! So simple!
Instead of build the logic on schema.parse, I did it on schema.data.
Actually, it makes more sense on schema.data. Don't know why I was trying on schema.parse.

Related

Transform Request to Autoquery friendly

We are working with a 3rd party grid (telerik kendo) that has paging/sorting/filtering built in. It will send the requests in a certain way when making the GET call and I'm trying to determine if there is a way to translate these requests to AutoQuery friendly requests.
Query string params
Sort Pattern:
sort[{0}][field] and sort[{0}][dir]
Filtering:
filter[filters][{0}][field]
filter[filters][{0}][operator]
filter[filters][{0}][value]
So this which is populated in the querystring:
filter[filters][0][field]
filter[filters][0][operator]
filter[filters][0][value]
would need to be translated to.
FieldName=1 // filter[filters][0][field]+filter[filters][0][operator]+filter[filters][0][value] in a nutshell (not exactly true)
Should I manipulate the querystring object in a plugin by removing the filters (or just adding the ones I need) ? Is there a better option here?
I'm not sure there is a clean way to do this on the kendo side either.
I will explain the two routes I'm going down, I hope to see a better answer.
First, I tried to modify the querystring in a request filter, but could not. I ended up having to run the autoqueries manually by getting the params and modifying them before calling AutoQuery.Execute. Something like this:
var requestparams = Request.ToAutoQueryParams();
var q = AutoQueryDb.CreateQuery(requestobject, requestparams);
AutoQueryDb.Execute(requestobject, q);
I wish there was a more global way to do this. The extension method just loops over all the querystring params and adds the ones that I need.
After doing the above work, I wasn't very happy with the result so I investigated doing it differently and ended up with the following:
Register the Kendo grid filter operations to their equivalent Service Stack auto query ones:
var aq = new AutoQueryFeature { MaxLimit = 100, EnableAutoQueryViewer=true };
aq.ImplicitConventions.Add("%neq", aq.ImplicitConventions["%NotEqualTo"]);
aq.ImplicitConventions.Add("%eq", "{Field} = {Value}");
Next, on the grid's read operation, we need to reformat the the querystring:
read: {
url: "/api/stuff?format=json&isGrid=true",
data: function (options) {
if (options.sort && options.sort.length > 0) {
options.OrderBy = (options.sort[0].dir == "desc" ? "-" : "") + options.sort[0].field;
}
if (options.filter && options.filter.filters.length > 0) {
for (var i = 0; i < options.filter.filters.length; i++) {
var f = options.filter.filters[i];
console.log(f);
options[f.field + f.operator] = f.value;
}
}
}
Now, the grid will send the operations in a Autoquery friendly manner.
I created an AutoQueryDataSource ts class that you may or may not find useful.
It's usage is along the lines of:
this.gridDataSource = AutoQueryKendoDataSource.getDefaultInstance<dtos.QueryDbSubclass, dtos.ListDefinition>('/api/autoQueryRoute', { orderByDesc: 'createdOn' });
export default class AutoQueryKendoDataSource<queryT extends dtos.QueryDb_1<T>, T> extends kendo.data.DataSource {
private constructor(options: kendo.data.DataSourceOptions = {}, public route?: string, public request?: queryT) {
super(options)
}
defer: ng.IDeferred<any>;
static exportToExcel(columns: kendo.ui.GridColumn[], dataSource: kendo.data.DataSource, filename: string) {
let rows = [{ cells: columns.map(d => { return { value: d.field }; }) }];
dataSource.fetch(function () {
var data = this.data();
for (var i = 0; i < data.length; i++) {
//push single row for every record
rows.push({
cells: _.map(columns, d => { return { value: data[i][d.field] } })
})
}
var workbook = new kendo.ooxml.Workbook({
sheets: [
{
columns: _.map(columns, d => { return { autoWidth: true } }),
// Title of the sheet
title: filename,
// Rows of the sheet
rows: rows
}
]
});
//save the file as Excel file with extension xlsx
kendo.saveAs({ dataURI: workbook.toDataURL(), fileName: filename });
})
}
static getDefaultInstance<queryT extends dtos.QueryDb_1<T>, T>(route: string, request: queryT, $q?: ng.IQService, model?: any) {
let sortInfo: {
orderBy?: string,
orderByDesc?: string,
skip?: number
} = {
};
let opts = {
transport: {
read: {
url: route,
dataType: 'json',
data: request
},
parameterMap: (data, type) => {
if (type == 'read') {
if (data.sort) {
data.sort.forEach((s: any) => {
if (s.field.indexOf('.') > -1) {
var arr = _.split(s.field, '.')
s.field = arr[arr.length - 1];
}
})
}//for autoquery to work, need only field names not entity names.
sortInfo = {
orderByDesc: _.join(_.map(_.filter(data.sort, (s: any) => s.dir == 'desc'), 'field'), ','),
orderBy: _.join(_.map(_.filter(data.sort, (s: any) => s.dir == 'asc'), 'field'), ','),
skip: 0
}
if (data.page)
sortInfo.skip = (data.page - 1) * data.pageSize,
_.extend(data, request);
//override sorting if done via grid
if (sortInfo.orderByDesc) {
(<any>data).orderByDesc = sortInfo.orderByDesc;
(<any>data).orderBy = null;
}
if (sortInfo.orderBy) {
(<any>data).orderBy = sortInfo.orderBy;
(<any>data).orderByDesc = null;
}
(<any>data).skip = sortInfo.skip;
return data;
}
return data;
},
},
requestStart: (e: kendo.data.DataSourceRequestStartEvent) => {
let ds = <AutoQueryKendoDataSource<queryT, T>>e.sender;
if ($q)
ds.defer = $q.defer();
},
requestEnd: (e: kendo.data.DataSourceRequestEndEvent) => {
new DatesToStringsService().convert(e.response);
let ds = <AutoQueryKendoDataSource<queryT, T>>e.sender;
if (ds.defer)
ds.defer.resolve();
},
schema: {
data: (response: dtos.QueryResponse<T>) => {
return response.results;
},
type: 'json',
total: 'total',
model: model
},
pageSize: request.take || 40,
page: 1,
serverPaging: true,
serverSorting: true
}
let ds = new AutoQueryKendoDataSource<queryT, T>(opts, route, request);
return ds;
}
}

Immutable.js add new data

I want to save all the searches from the user as a key of objects (search field):
beforeState = fromJS({
showFilter: false,
loading: false,
error: false,
search: fromJS({})
})
afterState = fromJS({
showFilter: false,
loading: false,
error: false,
search:
key1: [{}, {},...],
key2: [{}, {}, {}...]
})
New data:
const searchText = 'test'
const data = [{object1}, {object2},....]
const expectedResult = state
search has to be immutable as it can change. key1, key2... doesn't need to, as once they're initilised won't change.
Two questions:
I think I need fromJS function in searchKey in order to get a map, I mean, fromJS function does not nest maps
var t = beforeState.get('search').constructor.name;
console.log(t) //gets Map but without fromJS gets Object
But as array inside key1, key2, can't mutate, another fromJS would't be needed. Is it that way?
How can I insert key1, key2.. values inside search field?
Using mergeDeep, seems to be ok:
var boxes = Immutable.fromJS({
box1: {
id:1
},
box2: {
id:2
},
search: Immutable.fromJS({box3: {z:7}})
});
var data = Immutable.fromJS({
search: {
box3: {id:3}
}
});
var newBoxes = boxes.mergeDeep(data);
console.log(newBoxes.get('search').toJS());

kendoAutoComplete expects that JSON reponse contains the same propertyname as the search filter

Datasource is defined as:
var KendoDataSource_EmployeeAutoCompleteByFirstName = {
serverFiltering: true,
serverPaging: true,
serverSorting: true,
pageSize: 10,
transport: {
read: {
url: '#Url.Action("GetEmployeesByFirstName", "Employee")',
dataType: "json"
}
}
};
AutoComplete is defined as:
function KendoGridFilterAutoComplete(element, kendoDataSource, textField) {
element.kendoAutoComplete({
minLength: 3,
filter: "startswith",
dataSource: kendoDataSource,
dataTextField: textField
});
}
When using a kendoAutoComplete widget, the filter which is send by the datasource is like:
filter[logic]=and&
filter[filters][0][value]=smith&
filter[filters][0][operator]=startswith&
filter[filters][0][field]=LastName&
filter[filters][0][ignoreCase]=true
The JSON response from the server looks like:
[
{"First":"Bill","LastName":"Smith"},
{"First":"Jack","LastName":"Smith"},
{"First":"ABC","LastName":"Smithy"}
]
This works fine, however as you can see I return multiple entries, so the kendoAutoComplete shows two the same entries (Smith) because the first-name differs.
So what I actually want is do distinct on the server, and return only the possible LastName, as an array of strings like this:
[
"Smith",
"Smithy"
]
However the kendoAutoComplete cannot handle this. It shows "undefined" or an error.
How to solve this ?
I've create the following code:
#region AutoComplete
public virtual IQueryable GetAutoComplete(KendoGridRequest request)
{
// Get filter from KendoGridRequest (in case of kendoAutoComplete there is only 1 filter)
var filter = request.FilterObjectWrapper.FilterObjects.First();
// Change the field-name in the filter from ViewModel to Entity
string fieldOriginal = filter.Field1;
filter.Field1 = MapFieldfromViewModeltoEntity(filter.Field1);
// Query the database with the filter
var query = Service.AsQueryable().Where(filter.GetExpression1<TEntity>());
// Apply paging if needed
if (request.PageSize != null)
{
query = query.Take(request.PageSize.Value);
}
// Do a linq dynamic query GroupBy to get only unique results
var groupingQuery = query.GroupBy(string.Format("it.{0}", filter.Field1), string.Format("new (it.{0} as Key)", filter.Field1));
// Make sure to return new objects which are defined as { "FieldName" : "Value" }, { "FieldName" : "Value" } else the kendoAutoComplete will not display search results.
return groupingQuery.Select(string.Format("new (Key as {0})", fieldOriginal));
}
public virtual JsonResult GetAutoCompleteAsJson(KendoGridRequest request)
{
var results = GetAutoComplete(request);
return Json(results, JsonRequestBehavior.AllowGet);
}
#endregion
Which returns a unique list of anonymous objects which look like { "LastName" : "a" }.

JqGrid trying to send large data from server to grid but getting:Error during serialization or deserialization using the JSON JavaScriptSerializer

I have a problem I am receiving large amount of data from the server and am then converting it to Json format, to be then viewed in JqGrid. It works for small amount of data say for example 200 rows but when doing this for 10000 rows it throws the following error
System.InvalidOperationException: Error during serialization or deserialization using the JSON JavaScriptSerializer. The length of the string exceeds the value set on the maxJsonLength property
I have tried using the javascript serializer and set it to maxjsonLenght = int32.MaxValue but still no luck
Following is my code please give me suggestions with examples how I can fix this? Thanks all!
GridConfig
public JqGridConfig(String db, String jobGroup, String jobName, String detailTable, String filterBatchControl, String filterDate, String filterTime, int page)
{
var entityhelper = new EntityHelper();
var s = new JsonSerializer();
try
{
//Populate Grid Model, Column Names, Grid Column Model, Grid Data
entityhelper.PopulateDetailGridInit(db, jobGroup, jobName, detailTable, filterBatchControl, filterDate, filterTime);
JqGridDetailAttributes = entityhelper.GridDetailAttributes;
JqGridDetailColumnNames = entityhelper.GridDetailColumnNames;
//JqGridDetailsColumnNamesForExport = entityhelper.GridDetailColumnNamesForExport;
JqGridDetailColumnModel = entityhelper.GridDetailColumnModel;
//Dynamic Data
JqGridDynamicDetailData = entityhelper.GridDetailData;
#region Column Model
foreach (KeyValuePair<String, JqGridColModel> kvp in entityhelper.GridDetailColumnModel)
{
s.Serialize(kvp.Key, kvp.Value.Attributes);
}
JqGridDetailColumnModelJson = s.Json();
#endregion
#region Concrete data. 1. List<dynamic> populated, 2. Convert to Json String, 3: Convert back to List<Detail>
JqGridDetailData = JsonSerializer.ConvertDynamicDetailsToJson(JqGridDynamicDetailData); // this is where the error occurs
}
catch (Exception ex)
{
//TODO: Logging
System.Diagnostics.Debug.WriteLine(ex.Message);
}
}
Json Serializer
public static IList<Detail> ConvertDynamicDetailsToJson(IList<dynamic> list)
{
if (list.Count == 0)
return new List<Detail>();
var sb = new StringBuilder();
var contents = new List<String>();
sb.Append("[");
foreach (var item in list)
{
var d = item as IDictionary<String, Object>;
sb.Append("{");
foreach (KeyValuePair<String, Object> kvp in d)
{
contents.Add(String.Format("{0}: {1}", "\"" + kvp.Key + "\"", JsonConvert.SerializeObject(kvp.Value)));
}
sb.Append(String.Join(",", contents.ToArray()));
sb.Append("},");
}
sb.Append("]");
//remove trailing comma
sb.Remove(sb.Length - 2, 1);
var jarray = JsonConvert.DeserializeObject<List<Detail>>(sb.ToString());
return jarray;
}
Controller that return Json result from server
public JsonResult DetailGridData(TheParams param)
{
dynamic config= "";
switch (param.JobGroup)
{
case "a":
config = new BLL.abcBLL().GetDetailGridData("rid", "desc", 1, 20, null,
param.FilterBatchControl,
param.JobName, param.DetailTable,
param.JobGroup, param.BatchDate,
param.Source);
break;
}
return Json(config, JsonRequestBehavior.AllowGet); // this reurns successfully json result
}
View where the Jqgrid exists and does not populate the grid
<script type="text/javascript">
var jobGroup = '#ViewBag.JobGroup';
var jobName = '#ViewBag.JobName';
var detailTable = '#ViewBag.DetailTable';
var filterBatchControl = '#ViewBag.FilterBatchControl';
var controlDate = '#ViewBag.ControlDate';
var controlTime = '#ViewBag.ControlTime';
var source = '#ViewBag.DetailSource';
var page = '#ViewBag.page';
function loadDetailData() {
var param = new Object();
param.BatchDate = controlDate;
param.BatchTime = controlTime;
param.JobGroup = jobGroup;
param.JobName = jobName;
param.DetailTable = detailTable;
param.FilterBatchControl = filterBatchControl;
param.Source = source;
param.page = page;
window.parent.loadingDetailsHeader();
$.ajax({
url: "/control/detailgriddata",
dataType: 'json',
type: 'POST',
data: param,
async: false,
success: function (response) {
try {
jgGridDetailColumnNames = response.JqGridDetailColumnNames;
//jqGridDetailColumnData = response.JqGridDetailData;
jqGridDetailColumnData = response.config;
$('#detailGrid').jqGrid('setGridParam', {colNames: jgGridDetailColumnNames});
$('#detailGrid').jqGrid('setGridParam', {data: jqGridDetailColumnData}).trigger('reloadGrid');
parent.loadingDetailsHeaderComplete();
}
catch(e) {
window.parent.loadingDetailsHeaderException(e.Message);
}
return false;
},
error: function (xhr, ajaxOptions, thrownError) {
alert(xhr.status);
alert(thrownError);
}
});
}
function exportdetails(date) {
var param = new Object();
param.db = source;
param.jobGroup = jobGroup;
param.jobName = jobName;
param.detailTable = detailTable;
param.filterBatchControl = filterBatchControl;
param.filterDate = date;
param.filterTime = "NULL";
$.ajax({
type: 'POST',
contentType: 'application/json; charset=utf-8',
url: '#Url.Action("ExportDetailsCsv", "Control")',
dataType: 'json',
data: $.toJSON(param),
async: false,
success: function (response) {
window.location.assign(response.fileName);
},
error: function (xhr, ajaxOptions, thrownError) {
alert("Details Export Exception: " + xhr.status);
}
});
}
//<![CDATA[
$(document).ready(function () {
'use strict';
$(window).resize(function () {
$("#detailGrid").setGridWidth($(window).width());
}).trigger('resize');
var dgrid = $("#detailGrid");
$('#detailGrid').jqGrid('clearGridData');
loadDetailData();
dgrid.jqGrid({
datatype: 'json',
data: jqGridDetailColumnData,
colNames: jgGridDetailColumnNames,
colModel: [ #Html.Raw(#ViewBag.ColModelDetail) ],
rowNum: 25,
rowList: [25, 50, 100],
pager: '#detailPager',
gridview: true,
autoencode: false,
ignoreCase: true,
viewrecords: true,
altrows: false,
autowidth: true,
shrinkToFit: true,
headertitles: true,
hoverrows: true,
height: 300,
onSelectRow: function (rowId) {
//This is a demo dialog with a jqGrid embedded
//use this as the base for viewing detail data of details
//$('#dialogGrid').dialog();
//gridDialog();
},
loadComplete: function (data) {},
gridComplete: function (data) {
//if (parseInt(data.records,10) < 50) {
$('#detailPager').show();
//} else {
//$('#detailPager').show();
//}
}
}).jqGrid('navGrid', '#detailPager', { edit: false, add: false, del: false, search: false }, {});
});
//]]>
</script>
<table id="detailGrid">
<tr>
<td />
</tr>
</table>
<div id="detailPager"></div>
<div id="dialogGrid"></div>
Probably you should consider to use server side paging instead of returning 10000 rows to the client? Server side paging of SQL data can be implemented much more effectively as client side paging (sorting of large non-indexed data in JavaScript program).
One more option which you have is the usage of another JSON serializer. For example it can be protobuf-net, ServiceStack.Text (see here too), Json.NET and other. In the way you can additionally improve performance of your application comparing with JavaScriptSerializer.

Loop to add data to complex JSON object

I have a complex JSON Object like this:
var requestData = { __batchRequests: [ { __changeRequests: [
{ requestUri: "Customers", method: "POST", headers: { "Content-ID": "1" }, data: {
CustomerID: 400, CustomerName: "John"
} }
] } ] };
I am trying to do two things:
Declare this object but with the variable data empty
With a loop, add items dynamically to the data object,
How can I do it?
This isn't too complex an object. And it isn't JSON until it's converted into a string.
Right now, it's just plain-ol' JS objects and arrays.
Breaking that down into its elements might look like this:
var requestData = {};
requestData.__batchRequests = [];
requestData.__batchRequests[0] = {};
requestData.__batchRequests[0].__changeRequests = [];
requestData.__batchRequests[0].__changeRequests[0] = {};
requestData.__batchRequests[0].__changeRequests[0].requestUri = "Customers";
requestData.__batchRequests[0].__changeRequests[0].method = "POST";
requestData.__batchRequests[0].__changeRequests[0].headers = { "Content-ID" : "1" };
requestData.__batchRequests[0].__changeRequests[0].data = {};
Aside from the repeats, what do you see?
Personally, I see that __changeRequests[0] is an object as simple as:
var changeRequest = {
requestUri : "Customers",
method : "POST",
headers : { "Content-ID" : "1" },
data : {}
};
I also see that I can just push that onto my array of change requests:
requestData.__batchRequests[0].__changeRequests.push(changeRequest);
Right?
I also know that my changeRequest variable still points to the one that I just added to the array, and whatever I change on the object will show up as changed in the array's reference to the object, too:
changeRequest.data.CustomerName = "Bob";
changeRequest.data.CustomerID = "204";
requestData.__/*...*/changeRequests[0].data.CustomerName; // Bob
So how about writing yourself some helper-functions?
function extend (obj, additions) {
var key;
for (key in obj) { if (additions.hasOwnProperty(key)) {
obj[key] = additions[key];
}
}
function makeChangeRequest (url, method, headers, data) {
var request = {
requestUri : url,
method : method,
headers : {},
data : {}
};
extend(request.headers, headers);
extend(request.data, data);
return request;
}
function getBatch (num) { return requestData.__batchRequests[num]; }
var changeReq = makeChangeRequest("Customers",
"POST",
{ "Content-ID" : "1" },
{ CustomerName : "Bob", CustomerID : "2012" });
var batch = getBatch(0);
batch.__changeRequests.push(changeReq);
If you want to add more data to changeReq.data later:
extend(changeReq.data, { Address : "33 Nowhere Rd.", City : "Splitsville" });
For the first part of your question, you can initialize data with an empty associative array:
var requestData = { __batchRequests: [ { __changeRequests: [
{ requestUri: "Customers", method: "POST", headers: { "Content-ID": "1" }, data: {} }
] } ] };
This next part assumes, perhaps incorrectly, that you can use jQuery. It also assumes that you have an array containing all of the relevant key value pairs.
var customerDeetsArray =[{CustomerID: 400}, {CustomerName: "John"}];
for (var i in customerDeetsArray) {
requestData.data = $.extend(requestData.data, customerDeetsArray[i]);
}
See working example which makes use of console.debug:
http://jsfiddle.net/4Rh72/6/