Massive data combobox with Kendo UI virtualization not working - javascript

I'm trying to create a combobox with pagination (virtualization) using Kendo UI and Angular JS:
The idea is to bring 10000 items in pages of 10. Using this and this Kendo Demos I get this far:
HTML:
<select kendo-combo-box k-options="config"></select>
Angular Controller:
$scope.config = {
template: "<span>#= key # - #= value #</span>",
dataTextField: "key",
dataValueField: "value",
virtual: {
itemHeight: 26,
valueMapper: function (options) {
console.log("valueMapper was executed.");
},
},
height: 156,
dataSource: {
//type: "odata", // comment explained below
pageSize: 24,
serverPaging: true,
//serverFiltering: true,
schema: {
total: "total",
data: "data"
},
transport: {
read: function (options) {
console.log("dataSource transport read was called.");
// ajax call with result object => response.
options.success(response);
}
},
}
};
JSON response object (in transport read function):
{
"total": "10000",
"data": [
{ "key": "1", "value": "a" },
{ "key": "2", "value": "b" },
...
{ "key": "9", "value": "j" }
]
}
PS: "data" has 10 elements.
This is not working. The combo is created with 10 items and nothing more.
If I uncomment the lines:
type: "odata",
serverFiltering: true,
The combobox is build with 10000 items with "loading..." as description. But no matter how much time I wait, this "loading..." texts are never updated to my information, and more, the line:
console.log("dataSource transport read was called.");
keep getting hit in infinity loop.
Before finishes I notice other strange behavior. The function in valueMapper is never executed (the message "valueMapper was executed." is never printed.).
And if I set virtual: false no pagination is used.
How can I make this work?

Related

Kendo UI Grid: if I make changes and save them, I can still cancel them afterwards. How to lock them in?

I have a Kendo UI Grid bound to a local data source. If I make some changes and click on "Save changes", and then I click on "Cancel changes", the changes are rolled back. I expected them to be "locked in" because I saved them.
Furthermore, if I make a change, save it, make another change, save again and finally cancel, both changes are rolled back.
See UPDATED fiddle, with problem and solution:
http://jsfiddle.net/q24ennne/7/
My HTML:
<div id="grid"></div>
My JavaScript:
window.gridData = [
{ id: 1, text: "Uno" },
{ id: 2, text: "Dos" },
{ id: 3, text: "Tres" },
{ id: 14, text: "Catorce" },
];
(function() {
$('#grid').kendoGrid({
toolbar: ["save", "cancel"],
editable: true,
saveChanges: function(e) {
gridData = $('#grid').getKendoGrid().dataSource.data();
$('#grid').getKendoGrid().refresh();
console.log("gridData:");
console.log(gridData);
},
columns: [{
field: "text",
title: "No."
}],
dataSource: {
data: gridData,
}
});
})();
Thanks!
You need to include a schema for your datasource that specifies which property is the id of each item. In your case this happens to also be called "id" so add this:
dataSource: {
data: gridData,
schema: {
model: { id: "id" }
}
}
The grid will then correctly track and keep your saved changes.

ExtJS 6 grid doesn't update/refresh column headers properly

I've got a basic ExtJS gridpanel on which I can apply custom state on the fly. Using another control such as a combobox or another grid, my application applies the selected state on the grid. An example of this state:
{
"height": 384,
"columns": [{
"id": "h107"
},
{
"id": "h1",
"width": 30
},
{
"id": "unplannedtasks_ActualEndDate",
"hidden": true,
"width": 100
},
{
"id": "unplannedtasks_ActualNoResources",
"hidden": true,
"width": 100
},
{
"id": "unplannedtasks_ActualResponseDateTime",
"hidden": true
},
{
"id": "unplannedtasks_ActualTotalDurationInSeconds",
"width": 100
},
"filters": []
}
Here's the corresponding columns section of the grid declaration:
Ext.define('Ext.grid.Stateful', {
extend: 'Ext.grid.Panel',
stateEvents: ['columnmove', 'columnresize', 'sortchange', 'hiddenchange', 'groupchange', 'show', 'hide'],
// CODE OMMITTED FOR BREVITY
initComponent: function () {
Ext.apply(this, {
columns: [
filterAction,
new columns.tasks.ActualEndDate({ id: 'unplannedtasks_ActualEndDate', hidden: false }),
new columns.tasks.ActualNoResources({ id: 'unplannedtasks_ActualResponseDateTime', hidden: false },
new columns.tasks.ActualResponseDateTime({ id: 'unplannedtasks_ActualResponseDateTime', hidden: false },
new columns.tasks.ActualTotalDurationInSeconds({ id: 'unplannedtasks_ActualTotalDurationInSeconds', hidden: false }
]
});
}
})
An example of a column definition:
Ext.define("columns.tasks.ActualNoResources", {
extend: "Ext.grid.column.Column",
text: 'ActualNoResources',
dataIndex: 'ActualNoResources',
editor: {
allowBlank: false
}, filterable: true,
filter: {
type: 'string'
}
});
Everything goes as I expected except the column headers don't seem to refresh properly. If I open the columns panel in the grid, it is showing the correct amount of visible and hidden columns. Same story with the filters: if there's a filter in the state, it applies the correct value on the correct field. It's as though the column headers need to be refreshed in some way.
I tried to use grid.getView().refresh() but that doesn't work. Instead, if I resize the grid, it does refresh the hidden columns but not the columns that were initially hidden but now visible.
I think I am missing a simple line of code that belongs in the applyState method of the grid so I can command the grid to refresh the grid with the new state rather than the previous or initial state.
Any ideas on how to solve this?
As it turns out, the scenario that I pursue is not possible out of the box with ExtJS. What I'm asking can only be done during startup (without intervention of a developer's custom code) so I had to quit this approach and provide custom code. In the end, I had to rebuild the columns and then pass that collection to the reconfigure(store, columns) method of the grid.

jquery DataTables Editor: "select" field displays option value instead of label

I am using jquery's DataTables which is really working great. Then only problem I got is, that I am facing (in non-edit-view) the value of the select-field (which is an id). The user of course doesn't want to see the id of course.
Therefore I am looking for a possibility to configure that column in a way to show always the value of label property.
Here a some snippets:
$(document).ready(function() {
var table = $('#overviewTable').DataTable({
dom: "Tfrtip",
ajax: "/Conroller/GetTableData",
columns: [
{ data: "Id", className: "readOnly", visible: false },
{
data: "LoanTransactionId",
className: "readOnly readData clickable",
"fnCreatedCell": function(nTd, sData, oData, iRow, iCol) {
$(nTd).html("<a href='#'>" + oData.LoanTransactionId + "</a>");
}
},
{ data: "Id", className: "readOnly" },
{ data: "property_1", className: "readOnly" },
{ data: "Priority" },
{ data: null, className: "action readOnly", defaultContent: 'Info' }
],
order: [1, 'asc'],
tableTools: {
sRowSelect: "os",
sRowSelector: 'td:first-child',
aButtons: []
}
});
// data reload every 30 seconds
setInterval(function() {
table.ajax.reload();
}, 30000);
editor = new $.fn.dataTable.Editor({
ajax: "PostTable",
table: "#overviewTable",
fields: [
{
label: "Id",
name: "Id"
},
{
label: "Column 1",
name: "property_1"
},
{
label: "Priority",
name: "Priority",
type: "select",
options: [
{ label: "low", value: 0 },
{ label: "mid", id: 1 },
{ text: "high", id: 2 }
]
}
]
});
// Inline Edit - only those who are not readOnly
$('#overviewTable').on('click', 'tbody td:not(:first-child .readOnly)', function(e) {
editor.inline(this, {
submitOnBlur: true
});
});
How it looks in the display mode
How it looks in the edit mode
See the documentation on columns.render
You want to modify your column options for priority
Preferred Option: Your data source has a field with the priority as a string
This is the best option, as you don't want to have two places with this business logic. Keep it out of the client code.
Also, you will want to modify the editor as well so that the options used have been retrieved dynamically from the server to keep this business logic out of the client too. This is left as an exercise for the reader.
Since you don't provide details on what your data structure looks lik, I'm assuming it is an object, and it has an attribute priorityAsString so use the string option type for render.
columns: [
...
{
data: "Priority" ,
render: "priorityAsString",
},
Option 2) You write a function to map priority to string
Do this if you can't get the data from the server. But remember you will need to update many places when the priority list changes.
columns: [
...
{
data: "Priority" ,
render: renderPriorityAsString,
},
...
function renderPriorityAsString(priority) {
const priorityToString = {
0: 'low',
1: 'med',
2: 'high',
};
return priorityToString[priority] || `${priority} does not have a lookup value`;
}
"render": function ( data, type, full ) { return label;}

Knockout.js mapping ignore

We're working with knockout.js and knockout.mapping.js on .NET MVC 4. Let's say I have such JSON:
{
"deliveryPointType": "0",
"deliveryPointTypes": [
{
"id": 0,
"text": "Pridėti rankiniu būdu"
},
{
"id": 1,
"text": "Siųsti visiems regiono objektams"
}
],
"showRegionSelection": false,
"showDeliveryPointSelection": true,
"regionId": "",
"userHasRegions": "False",
"propertyNames": {
"deliveryPointTypeName": "Pridėti rankiniu būdu"
},
"initialMaterials": [
{
"quantity": 0,
"materialTypeId": "",
"propertyNames": {},
"validMaterial": true,
"showMaterialError": false,
"materialTypeAjax": {
"quietMillis": 300,
"cache": false,
"dataType": "json",
"type": "GET",
"url": "/lt-LT/Material/MaterialTypeNameLookup"
}
}
],
"deliveryBuildings": [
{
"clientId": "1",
"buildingId": "1",
"regionId": "",
"newBuilding": false,
"validClient": true,
"validBuilding": true,
"validRegion": true,
"showClientError": false,
"showBuildingError": false,
"showRegionError": false,
"propertyNames": {
"clientName": "klientas",
"buildingName": "ASD project, Antagynės gatvė, Kaunas, Lietuvos Respublika"
},
"clientAjax": {
"quietMillis": 300,
"cache": false,
"dataType": "json",
"type": "GET",
"url": "/lt-LT/Task/PayerLookup"
},
"buildingAjax": {
"quietMillis": 300,
"cache": false,
"dataType": "json",
"type": "GET",
"url": "/lt-LT/Object/GetClientAddressListByQuery"
},
"regionAjax": {
"quietMillis": 300,
"cache": false,
"dataType": "json",
"type": "GET",
"url": "/lt-LT/Object/RegionNameLookup"
}
}
],
"hasNewBuildings": false,
"showBuildingValidation": false,
"showMinimumBuildingRequiredValidation": false,
"showMaterialValidation": false,
"validRegion": true,
"showRegionError": false,
"regionAjax": {
"quietMillis": 300,
"cache": false,
"dataType": "json",
"type": "GET",
"url": "/lt-LT/Object/RegionNameLookup"
}
}
On form submit fail (if something goes wrong/invalid in service) it is repopulated with previous values. We convert ViewModel to JSON on form submit with $('#BuildingsJSON').val(ko.mapping.toJSON(viewModel.deliveryBuildings)).
On form repopulating, we parse JSON with ko.mapping.fromJSON(deliveryBuildings, mapping, viewModel.deliveryBuildings)(); mapping is just an empty object for now (tried "ignore" with no luck).
We use select2 fields to select addresses of buildings from a list (using ajax). Thing is, fromJSON populates almost every json property as observable, which I don't need. On select2 open we get an exception:
Uncaught TypeError: Object function observable() {
if (arguments.length > 0) {
// Write
// Ignore writes if the value hasn't changed
if (!observable['equalityComparer'] || !observable['equalityComparer'](_latestValue, arguments[0])) {
observable.valueWillMutate();
_latestValue = arguments[0];
if (DEBUG) observable._latestValue = _latestValue;
observable.valueHasMutated();
}
return this; // Permits chained assignments
}
else {
// Read
ko.dependencyDetection.registerDependency(observable); // The caller only needs to be notified of changes if they did a "read"
operation
return _latestValue;
}
} has no method 'toUpperCase'
I've debugged where it breaks - on type property of ajax call. I figured that we need to exclude ajax properties from casting to observable.
So, the question is: how can one not cast specific properties of specific objects to observable()? Is it enough using mapping plugin, is additional plugin needed or is it even impossible?
Have you looked at using the copy keyword in your mapping binding? This just copies the value over into a js property rather than making it an observable.
From the documentation:
var mapping = {
'copy': ["propertyToCopy"]
}
ko.mapping.fromJS(data, mapping, viewModel);

Datatables with serverside JSON (custom object)

I am trying to quickly create a simple display of results from an internal API using DataTables. The API returns JSON in the following structure:
obj {
status: 1,
results: 100,
offset: 25,
limit: 25,
data: [
[1]: {
title: "Blah blah one",
description: "Doesn't really matter",
misc: "Yadda yadda"
},
[2]: {
title: "Blah blah two",
description: "Doesn't really matter",
misc: "Yadda yadda"
},
]
}
I can't/don't want to change the API structure just because DataTables uses a weird structure, but I would like to access the built in functionalities for paging, dynamic loading, etc. DataTables seems to allow for custom data objects, and I've gotten the table to load with the following:
$(document).ready(function() {
$('#example').dataTable( {
"bProcessing": true,
"bServerSide": true,
"sAjaxSource": "http://api.oursite.com/api?limit=100",
"fnServerData": function( sUrl, aoData, fnCallback, oSettings ) {
oSettings.jqXHR = $.ajax( {
"url": sUrl,
"data": aoData,
"success": fnCallback,
"dataType": "jsonp",
"cache": false
} );
},
"sAjaxDataProp": "data",
"aoColumns": [
{ "mData": "title" },
{ "mData": "description" },
{ "mData": "misc" },
]
} );
});
But, none of the paging or sorting functions work. I think this is because DataTables requires a results count and paging variable in the object— "iTotalRecords" and "iTotalDisplayRecords". Is this correct? Is there any way to use the api variables instead? Thanks in advance. I'm currently not getting any errors in the dev console, so if it's erroring it's doing so silently...
While initializing the datatable, instead of directly assigning source to Ajaxsource, you can set the aaData to the javascript function where you can manipulate to return only obj.data. You need handle few things manually.
$('#tblExample').dataTable({
"bJqueryUI": true,
"bDestroy":true,
"bSortable": false,
"sAjaxSource": "",
"aaData":GetData(),
"aoColumns": [
{
"sTitle":"Index","mDataProp": null, "sWidth": "20px", "sDefaultContent": "<span class='ui-icon ui-icon-circle-close' onclick='RemoveActiveItem(this);'></span>", "bSortable": false},
{ "mDataProp": "Year"},
{ "mDataProp": "Month"},
{ "mDataProp": "Savings"}
]
});

Categories