SlickGrid w/ DataView not immediately reflecting changes in underlying data - javascript

I have the following code that builds a grid using slickgrid.js.
var grid;
var gridDataview;
var gridOptions = {
enableCellNavigation: true,
enableColumnReorder: true,
forceFitColumns: false,
topPanelHeight: 25
};
function createGrid(data) {
gridDataview = new Slick.Data.DataView({ inlineFilters: true });
grid = new Slick.Grid("#grid", gridDataview, data.columns, gridOptions);
grid.setSelectionModel(new Slick.RowSelectionModel());
var pager = new Slick.Controls.Pager(gridDataview, grid, $("#pager"));
var columnpicker = new Slick.Controls.ColumnPicker(data.columns, grid, gridOptions);
grid.onSort.subscribe(function (e, args) {
sortdir = args.sortAsc ? 1 : -1;
sortcol = args.sortCol.field;
// using native sort with comparer
// preferred method but can be very slow in IE with huge datasets
gridDataview.sort(comparer, args.sortAsc);
});
// if you don't want the items that are not visible (due to being filtered out
// or being on a different page) to stay selected, pass 'false' to the second arg
gridDataview.syncGridSelection(grid, true);
$("#gridContainer").resizable();
}
I am using this with knockout-js and initially only create the grid after the user makes a selection from a listbox, at which point i fetch data from an rest service and build the grid. each subsequent user selection will not create the grid, only update the data.
self.selectedInstrument.subscribe(function (newValue) {
$.getJSON('/data/' + self.selectedCategory().id + '/' + newValue.id, function (data) {
self.cotData(data);
if (grid == null) {
debugger;
createGrid(data);
}
//gridDataview.beginUpdate();
gridDataview.setItems(data.data);
//gridDataview.endUpdate();
});
});
What's happening is:
1. when the grid is initially create, no data is shown, just the column headers. if i move re-order a column header, then the data is shown.
2. If i sort a column, the sort is not visibly reflected. if i start scrolling, then i see the sort being reflected.
3. if i add a grid.render() to the end of the subscription handler above, i do see the data immediately, but then i'm not able to vertically scroll any longer. things seem badly broken at this point.
Any thoughts on what may be happening here? This started happening after i modified the code to create a DataView rather than loading the data right into the grid immediately. I need a DataView because i want to allow sorting and later different types of aggregation and groupings.
Is this possibly related to usage of slickgrid with knockout js?
Thanks much

Not sure why yet, as i'm still feeling my way around SlickGrid, but i had to add the following two subscriptions. the first allowed the grid to display rows immediately when new data is loaded and the second solved a similar issue, but when sorting:
// wire up model events to drive the grid
gridDataview.onRowCountChanged.subscribe(function (e, args) {
grid.updateRowCount();
grid.render();
});
gridDataview.onRowsChanged.subscribe(function (e, args) {
grid.invalidateRows(args.rows);
grid.render();
});

Related

amCharts 4 chart not updating with switchbutton, slider

For a radar chart, I'm trying to toggle (with a switch button) or slide (with a slider) between different sets of data to display. (I'll include the button here first, but I'm eventually try to extend that to a slider later.). 1. Initialize button and keep track of user toggle. 2. Pick which data set to use. both generateRadarData and generateRadarData2 work well on their own if I use chart.data = either one.
The below is the edited attempt:
var chartSwitchButton = chart.chartContainer.createChild(am4core.SwitchButton);
chartSwitchButton.events.on("toggle", function () {chart.data = whichData();})
function whichData() {
var dataToUse = chart.data;
if (chartSwitchButton.isActive) {dataToUse = generateRadarData();
} else {
dataToUse = generateRadarData2();}
return dataToUse};
chart.data = whichData();
I have tried commenting out the last line (since ideally it would have been updated via the event listener), but then no data displays.
Here is a more recent attempt to update the data using invalidateRawData:
chartSwitchButton.events.on("toggle", function (event) {
chart.data = whichData();
chart.invalidateRawData();
});
function whichData() {
var data = [];
if (chartSwitchButton.isActive) {
chart.data = generateRadarData();
} else {
chart.data = generateRadarData2();
}
chart.invalidateRawData(); //also tried invalidateData. tried this command in event listener as well as here.
data.push(chart.data); //attempt to replace/update raw data
//console.log(chart.data);
return chart.data; //this return line is necessary to output data but theoretically shouldn't be.
}
and have tried implementing the if-else w/in the event listener without having to use whichData as a separate function like so:
chartSwitchButton.events.on("toggle", function () {if (chartSwitchButton.isActive) {
chart.data = generateRadarData();
} else {
chart.data = generateRadarData2();
}
chart.invalidateRawData();})
I'm still unable to switch between the two sets of data with user interaction. In fact, if I don't return something for chart.data or declare what chart.data is supposed to be outside of the events.on or whichData(), then none of my data prints at all.
If anybody has suggestions on how to do this with a button (or a slider would be even better) that would be awesome.
Basically, after setting up the button, I tried to (a) keep track of the state of the button (as determined by user), (b) determine which state the button is in, and (c) pick a data set to use based off of that info. This version is edited from a previous attempt as per initial comments below. Thanks for your help.
Documentation is "toggled" not "toggle" in the events listener. The event does not recognize "toggle" but needed "toggled". https://www.amcharts.com/docs/v4/reference/switchbutton/#toggled_event

Ag-grid - having expandable height cells causes rendering issues

I have an Ionic/Angular project where I wish to use the Ag-grid as a (virtual) container for my potentially very large list of items. I am very new the the ag-grid, I have played a little in the past, but still relatively new to it
Ag-grid is the has come the closest by far to being able to do what I am trying to achieve.
Basically I have a list of items, where I wish to use an Angular component in each cell, and this component has an "expander", with a varying number of sub items.
To get a component in the grid, I followed this excellent tutorial, which seemed to work perfectly.
It almost all works, but I find when I expand items down the list a bit (eg item position say 50), the UI starts to jump around, and not render properly. If you expand the first few, it is ok, it is only when you get down the list a bit I have problems.
I have a sample project here on Github.
It looks like the following...
My setup of the grid is in home.page.ts, where I have the following gridOptions
gridOptions: GridOptions = {
// Makes col take up full grid
onGridSizeChanged: () => {
this.gridOptions.api.sizeColumnsToFit();
} ,
getRowHeight(params) {
return params.data.getHeight();
},
getRowNodeId: function (data) {
return data.equipment;
},
}
The getRowHeight above will call ListItem.getHeight, and you can see this returns the height based on whether or not is it expanded...
public getHeight(): number {
let height = 72;
if (this.isOpen)
height += (this.jobs.length - 1) * 40;
return height;
}
Also, when the expander is clicked the refresh function passed into the constructor is called.
public setIsOpen(val: boolean) {
this.isOpen = val;
this.refresh();
}
And this call the following in the home.page.ts to try and refresh the grid cells..
private refreshCells(): void {
this.gridOptions.api.refreshCells({ force: true });
this.gridOptions.api.resetRowHeights();
}
And that's basically it.
It is close to working, ie click the initial items works, but when I scroll down, sometimes the view just does not refresh properly (sometimes it does, other times not).
Is this achievable, and if so, what can I do get get it to work better?

Presetting kendo DataSource PageSize to "All"

Whenever a user changes the pagination on a grid, I save the setting in localStorage and retrieve it to set it back whenever the user navigates again to the page. To retrieve it I am using the pageSize property of the dataSource where I pass an IIF like so:
pageSize: function () {
var pageSize = 10;
if (window.localStorage) {
var userPreferencePageSize = localStorage.getItem("somegridPageSize");
if (userPreferencePageSize === parseInt(userPreferencePageSize)) {
userPreferencePageSize = parseInt(userPreferencePageSize);
}
if (!isNaN(userPreferencePageSize)) {
pageSize = userPreferencePageSize;
}
}
return pageSize;
}()
This worked well but a requirement appeared for the user to be able to set the pageSize to "All". Kendo handles "All" in the Grid so I thought this will let me set the dataSource pageSize to the string "All" (pageSize="All") as well. When I do it however the grid starts displaying NaN of X records and displays an empty grid. So the question is.. how do I preset the pageSize of the dataSource to just "All"?
NOTE: An alternative is to just fetch the grid maximum total count and then replace the number displayed in the dropdown with jquery text("All) but that looks like a hack and it seems to me this should already be inbuilt into the framework but I can't find anything in the doc's.
EDIT: This is getting even funnier. Due to lack of other options I implemented it like in the note and just set the dataSource pageSize directly:
$("#Grid").data("kendoGrid").dataSource.pageSize(pageSize);
But this is causing the filters on grid to malfunction and throw "string is not in correct format" (misleading error) error from the endpoint. After enough research I found out its caused by the DataSourceRequest doing some unidentifiable shuru buru in the background. Since setting the dataSource pageSize causes issues, I tried just setting the dropdown programatically and let kendo trigger the request to update pageSize itself like so:
var selectBox = $(".k-pager-sizes").find("select");
selectBox.val(pageSize);
selectBox.find("option[value='" + pageSize + "']").prop('selected', true);
But the grid doesn't let me do it and keeps reverting any changes I do inside the DOM from javascript.
So the question is, how in earth can you change the pageSize of a server-side kendo grid from javascript (triggering an extra request to endpoint).
To answer the question. This appears to be a bug in Kendo. Setting the pageSize to 0 (shortcut for "all") after the dataSource was already bound will always result in the grid having issues with any custom filters declared inside of a toolbar template.
To be exact, if you have a custom filter defined inside of the toolbar template:
#(Html.Kendo().TextBox().Name("Filter").Deferred())
You wire it up through the dataSource definition on the grid like:
.DataSource(ds =>
ds.Ajax()
.PageSize(defaultPageSize)
.Read(a => a.Action(actionName, controllerName, new
{
param = Model.param
}).Data("getFilterParameters")))
in javaScript fetching the parameters like:
getFilterParameters: function () {
return this.filterParameters;
},
populating them with a method:
filter: function () {
var grid = $("#Grid").data("kendoGrid");
this.filterParameters = {
param: $("#Filter").val()
};
grid.dataSource.page(1);
}
that has a simple event listener wired to it:
$("#Filter").on("keyup", filter);
Then after changing the pageSize programatically to 0/"all" with:
$("#Grid").data("kendoGrid").dataSource.pageSize(0);
The filter will start to always return NaN as the current page / skip of the filter object passed to the server with the query parameters (even though grid will display the numbers correctly). This will cause a "string is not in correct format" exception inside framework code on the endpoint whenever you try using the filter. A solution to the above is to slightly modify the getFilterParameters method:
getFilterParameters: function (e) {
// Workaround for Kendo NaN pager values if pageSize is set to All
if (isNaN(e.page)) e.page = 1;
if (isNaN(e.skip)) e.skip = 0;
//
return this.filterParameters;
}
This will re-initialize the page and skip values before submitting the filter request. These values will anyway be re-populated on the endpoint with the correct values. Wasn't me who noticed it but another developer working on the project, credit goes to her.

Prevent JQuery Datatables from keeping old data in the table after being destroyed

Good day
Painting the scenario:
I have a scenario I am working on for work, where I had to freeze the header row and first column on larger tables. I have utilized the jquery Datatables plugin with its fixedColumns extension to be able to do so
The table that I am working on also happens to have knockout.js bindings, which renders the <tbody> rows from data obtained in the page's typescript.
the issue
When I do a pull from the server to get the data from the table, on page refresh or on interactions with other controls on the page, I run the following code (be warned, it's quite ugly - just trying to get it to work now):
module dataTable {
export class dataTableClass {
applyTable(tableIdString: string) : void {
(<any>$(tableIdString))
.DataTable({
scrollY: 1000,
scrollX: true,
scrollCollapse: true,
paging: false,
searching: false,
fixedHeader: true,
fixedColumns: {
leftColumns: 1
}
});
}
dataTableExists(tableIdString: string): boolean {
return (<any>$.fn.DataTable.isDataTable(tableIdString));
}
applyDatatableIfTableExists(retryCount: number, elementId: string): void {
if (!elementId) {
console.warn("table id not defined.");
return;
}
if (!retryCount) {
retryCount = 0;
} else if (retryCount > 100) {
console.warn("could not convert table to dataTable.");
return;
}
window.setTimeout(() => {
var tableIdString = "#" + elementId;
if ((<any>$(tableIdString)).length <= 0) {
this.applyDatatableIfTableExists(++retryCount, elementId);
} else {
if (this.dataTableExists(tableIdString)) {
debugger;
(<any>$(tableIdString)).DataTable({ retrieve: true }).destroy(); // <-- issue here. it seems to restore old data to the now-datatable-less table
}
window.setTimeout(() => {
this.applyTable(tableIdString);
if (!this.dataTableExists(tableIdString)) {
this.applyDatatableIfTableExists(++retryCount, elementId);
}
}, 250);
if (!(<any>$(tableIdString)).hasClass(".dataTable")) {
(<any>$(tableIdString)).addClass(".dataTable");
}
}
}, 250);
}
applyDataTable(id) {
this.applyDatatableIfTableExists(0, id);
}
}
}
On server pull, in typescript, when the deferred code resolves and sets the table's data on an knockout observable array, the applyDataTable class gets called with the table's html ID
all the code runs fine for the first time - I allow the browser some time to render the table and to convert the table into a DataTable
When a user interacts with other controls on the page (to set filters for the knockout Observable array to change the table's contents), I destroy the table - presuming the table has already been initialised.
I recreate the table with the same DataTable options immediately after that so that new data (theoretically) displays in the table, having the fixedColumns extension render correctly again (since it changes cells and table header cells into tables of their own and apply css to make it look neat)
The issue comes on the line I marked. What happens on-screen is that it definitely removes the changes applied by DataTables from the HTML, but, the table that renders has old data in it (even though I fetched new data from the server by changing my search criteria on page - which generates new ....).
Also commenting out all this code inside my applyDataTable() method, it never sets the datatable plugin on my html table (as expected) and changing my filters reflects the newest and correct data on my table.
So in tl;dr - what is happening is that some state information gets stored by datatables. is there a way for me to clear this state and only apply a datatable with my settings over my html table?
You can remove the old data from table body or the whole table. It depends on whether you're recreating column headers later.
For example:
$('#example').DataTable().destroy();
$('#example tbody').empty();
So in your case it would be something like:
(<any>$(tableIdString)).DataTable().destroy();
(<any>$(tableIdString + ' tbody')).empty();

What is the correct syntax to change options on an existing Kendo Grid?

I've got a Kendo Grid:
$('#myGrid').kendoGrid({
...
scrollable: false,
...
});
and then later on I want to change its scrollable property. I've tried all of the following:
$('#myGrid').data("kendoGrid").options.scrollable = true;
$('#myGrid').data("kendoGrid").refresh();
-
$('#myGrid').data("kendoGrid").scrollable = true;
$('#myGrid').data("kendoGrid").refresh();
-
var MyGrid = $('#myGrid').data("kendoGrid");
MyGrid.options.scrollable = true;
MyGrid.refresh();
-
var MyGrid = $('#myGrid').data("kendoGrid");
MyGrid.scrollable = true;
MyGrid.refresh();
Nothing works. How does one change whether a grid is scrollable on the fly?
This is not supported out of the box, so you'd have to mess with the internals. It's probably easier to just recreate the grid, but if you still think you need it, this fiddle might help point you in the right direction:
http://jsfiddle.net/lhoeppner/AKzzL/
Basically you could try using something like this:
function enableScrolling() {
if (!grid.options.scrollable) {
grid.options.scrollable = true;
grid._thead();
grid.refresh();
}
}
function disableScrolling() {
grid.options.scrollable = false;
grid.table.unwrap(); // manually remove the wrapper that enables scrolling
}
Making a scrollable grid non-scrollable like that results in the data columns to have the wrong width though, so depending on your requirements, you may need to customize this some more.
Options of the Grid cannot be changed dynamically. You need to re-create the whole Grid with different options in order to disable/enable them dynamically.
EDIT
As from Q3 2014, the Grid supports the setOptions method, which does pretty much the same internally, but keeps most of the options and the state of the dataSource in sync.
var MyGrid = $('#myGrid').data("kendoGrid");
MyGrid.options.scrollable = true;
(base on my experience) Then you need to reload the dataSource ex:
MyGrid.setDataSource(kendoDataSource);

Categories