How can I obtain and re-set the state of an ag-grid table? I mean, which columns are being show, in what order, with what sorting and filtering, etc.
Update: getColumnState and setColumnState seem to be close to what I want, but I cannot figure out the callbacks in which I should save and restore the state. I tried restoring it in onGridReady but when the actual rows are loaded, I lose the state.
There is a very specific example on their website providing details for what you are trying to do: javascript-grid-column-definitions
function saveState() {
window.colState = gridOptions.columnApi.getColumnState();
window.groupState = gridOptions.columnApi.getColumnGroupState();
window.sortState = gridOptions.api.getSortModel();
window.filterState = gridOptions.api.getFilterModel();
console.log('column state saved');
}
and for restoring:
function restoreState() {
gridOptions.columnApi.setColumnState(window.colState);
gridOptions.columnApi.setColumnGroupState(window.groupState);
gridOptions.api.setSortModel(window.sortState);
gridOptions.api.setFilterModel(window.filterState);
console.log('column state restored');
}
I believe you are looking for this page of the Docs. This describes the column api and what functions are available to you. The functions that are of most relevance would be:
getAllDisplayedColumns() - used to show what columns are able to be rendered into the display. Due to virtualization there may be some columns that aren't rendered to the DOM quite yet, iff you want only the columns rendered to the DOM then use getAllDisplayedVirtualColumns() - both functions show the order as they will be displayed on the webpage
The Column object that is returned from these functions contains sort and filter attributes for each of the columns.
I believe that all you need would be available to you from that function which would be called like this gridOptions.columnApi.getAllDisplayedColumns()
Other functions which might be useful:
Available from gridOptions.columnApi:
getColumnState() - returns objects with less detail than the above funciton - only has: aggFunc, colId, hide, pinned, pivotIndex, rowGroupIndex and width
setColumnState(columnState) - this allows you to set columns to hidden, visible or pinned, columnState should be what is returned from getColumnState()
Available from gridOptions.api:
getSortModel() - gets the current sort model
setSortModel(model) - set the sort model of the grid, model should be the same format as is returned from getSortModel()
getFilterModel() - gets the current filter model
setFilterModel(model) - set the filter model of the grid, model should be the same format as is returned from getFilterModel()
There are other functions that are more specific, if you only want to mess with pinning a column you can use setColumnPinned or for multiple columns at once use setColumnsPinned and more functions are available from the linked pages of the AG-Grid docs
The gridReady event should do what you need it to do. What I suspect is happening is your filter state is being reset when you update the grid with data - you can alter this behaviour by setting filterParams: {newRowsAction: 'keep'}
This can either by set by column, or set globally with defaultColDef.
Here is a sample configuration that should work for you:
var gridOptions = {
columnDefs: columnDefs,
enableSorting: true,
enableFilter: true,
onGridReady: function () {
gridOptions.api.setFilterModel(filterState);
gridOptions.columnApi.setColumnState(colState);
gridOptions.api.setSortModel(sortState);
},
defaultColDef: {
filterParams: {newRowsAction: 'keep'}
}
};
I've created a plunker here that illustrates how this would work (note I'm restoring state from hard code strings, but the same principle applies): https://plnkr.co/edit/YRH8uQapFU1l37NAjJ9B . The rowData is set on a timeout 1 second after loading
To keep the filter values you may use
filterParams: {newRowsAction: 'keep'}
Here's my approach. It simply involves creating a wrapper function with the same API as instantiating the agGrid.
A few things of interest in this function
saves/loads to local storage
makes use of the addEventListener that becomes available to the options object once it is passed in to the Grid function.
attaches to the onGridReady callback of the options object without erasing what may already be defined.
usage:
newAgGrid(document.getElementById('my-grid'), options);
static newAgGrid(element, options) {
const ls = window.localStorage;
const key = `${location.pathname}/${element.id}`;
const colStateKey = `${key}colstate`;
const sortStateKey = `${key}sortState`;
function saveState(params) {
if (ls) {
ls.setItem(colStateKey, JSON.stringify(params.columnApi.getColumnState()));
ls.setItem(sortStateKey, JSON.stringify(params.api.getSortModel()));
}
}
function restoreState(params) {
if (ls) {
const colState = ls.getItem(colStateKey);
if (colState) {
params.columnApi.setColumnState(JSON.parse(colState));
}
const sortState = ls.getItem(sortStateKey)
if (sortState) {
params.api.setSortModel(JSON.parse(sortState));
}
}
}
// monitor the onGridReady event. do not blow away an existing handler
let existingGridReady = undefined;
if (options.onGridReady) {
existingGridReady = options.onGridReady;
}
options.onGridReady = function (params) {
if (existingGridReady) {
existingGridReady(params);
}
restoreState(params);
}
// construct grid
const grid = new agGrid.Grid(element, options);
// now that grid is created, add in additional event listeners
options.api.addEventListener("sortChanged", function (params) {
saveState(params);
});
options.api.addEventListener("columnResized", function (params) {
saveState(params);
});
options.api.addEventListener("columnMoved", function (params) {
saveState(params);
});
return grid;
}
we can use useRef() to refer <AgGridReact> element to access getColumn and setColumn state in following way.
const GridRef = useRef()
GridRef.current.columnApi.getColumnState() // get Column state value
GridRef.current.columnApi.setColumnState() // set Column state value
<AgGridReact
enableFilter={true}
rowStyle={rowStyle}
ref={GridRef}
></AgGridReact>
The following needs to be done.
Include a div for the grid in your html
<div id="myGrid" style="width: 500px; height: 200px;"></div>
On the js side
//initialize your grid object
var gridDiv = document.querySelector('#myGrid');
//Define the columns options and the data that needs to be shown
var gridOptions = {
columnDefs: [
{headerName: 'Name', field: 'name'},
{headerName: 'Role', field: 'role'}
],
rowData: [
{name: 'Niall', role: 'Developer'},
{name: 'Eamon', role: 'Manager'},
{name: 'Brian', role: 'Musician'},
{name: 'Kevin', role: 'Manager'}
]
};
//Set these up with your grid
new agGrid.Grid(gridDiv, gridOptions);
Check this pen out for more features. https://codepen.io/mrtony/pen/PPyNaB . Its done with angular
Related
There is a custom useMarkers hook for converting markers into clusters on the map.
It takes needed props,but the essential one are options prop.
const { markersArray, supercluster } = useMarkers({
points: transformedMarkers,
bounds,
zoom,
options: defaultOptions2, // <=== HERE IS WHAT WE NEED
});
And here is it from the inside:
const initialMarkersData = {
markersArray: [],
supercluster: []
};
const useMarkers = (initialData = {}) => {
const [markersData, setMarkersData] = useState(initialMarkersData);
const { clusters, supercluster } = useSupercluster(initialData);
useEffect(() => {
if (initialData.zoom <= 17) {
// console.log("SET NON-CLUSTERS");
setMarkersData({
...markersData,
markersArray: initialData.points,
});
} else {
// console.log("SET CLUSTERS");
setMarkersData({
markersArray: clusters,
supercluster,
});
}
}, [initialData.zoom, clusters, supercluster]);
return markersData;
};
Inside useMarkers I`m passing props to another hook, useSupercluster (comes from separate npm library), and depending on options prop it should return an updated clusters array, and depending on the zoom level an additional useEffect should change the markersData, which is then returns as a result.
And the problem is that when I add a condition for passing different options, the new array is not returned.
For example, if I pass options like this, it works:
options: defaultOptions2,
But if I add condition instead of just single defaultOptions2
options: zoom < 17 ? defaultOptions : defaultOptions2
It doesn't work. If I console.log initialData inside useMarkers hook, I will see new options, so the condition works correct, but although this hook is re-renders, it looks like useSupercluster() doesn`t reinitialize again because it doesn't return modified array according to new options. What could be the reason?
UPDATE*: I tried to investigate this behavior with debugger and everything looks correct. The supercluster property, which is destructured from useSupercluster hook, has to have the this options in it, and although new options successfully passed to useSupercluster within initialData, supercluster property got the empty object at the end;
I'm using the OData V4 model in UI5. I've created a binding with some expands in it and now I try to obtain the context of a child entity.
Here is the code how I bind entities to some element. As a result I get an object with on 'SomeEntity' and an array with 'SomeOtherEntity' as a property.
oPage.bindElement({
path: /SomeEntity(id),
parameters: {
$expand: {
SomeOtherEntity: {
$select: ['ID', 'name', 'sequence'],
$orderby: 'sequence'
}
}
}
});
Now I can get the context of the binding with oPage.getBindingContext() and can execute methods like requestObject, setProperty, create and delete from this object.
What I want is to obtain the context of one of the 'SomeOtherEntity' properties to (for example) delete one of them.
I have no idea how to accomplish this. Anybody has an idea?
You can create an own ListBinding to SomeOtherEntity and filter the desired set.
(I'm not quite sure, but it might be necessary to trigger a refresh on the ListBinding to force an initial load)
After the data is loaded (dataReceived-Event), delete all the Contexts.
Each Delete returns a Promise and you can proceed with a Promise.all.
var oDataModel = this.getModel();
var aPromises= [];
var oListBinding = oDataModel.bindList("/SomeOtherEntity", undefined, undefined, new Filter("ID", FilterOperator.EQ, sIdToDelete), {
$$operationMode: OperationMode.Server
});
oListBinding.attachEventOnce("dataReceived", function (oEvent) {
var aContexts = oListBinding.getContexts();
aContexts.forEach(function (oContext) {
aPromises.push(oContext.delete("$auto"));
});
Promise.all(aPromises).then(function () {
/* Cleanup after Deletion
});
});
I'm using ag-grid and I have a column definition as following :
{
headerName: "Color",
valueGetter: function (params) {
return JSON.parse(params.data.color).name;
},
field: 'color',
cellRenderer: function (params) {
if (angular.isDefined(params.data) && angular.isDefined(params.data.color)) {
var color = JSON.parse(params.data.color);
return '<div style="width: 50px; height: 18px; background-color:' + color.htmlValue + ';"></div>';
}
},
suppressMenu: true,
suppressSorting: true
}
When I export the grid in CSV format, I get undefined for the color column, which is a cell renderer, I searched for a solution for this and I found this in the official documentation :
The raw values, and not the result of cell renderer, will get used,
meaning:
Cell Renderers will NOT be used.
Value Getters will be used.
Cell Formatters will NOT be used (use processCellCallback instead).
As you can see I'm already using a valueGetter but I always get undefined in the exported data for the color column.
How can I solve this ?
You could solve it by using processCellCallback when exporting to CSV. This way you can see and control exactly what is going to be exported.
Besides the column definition, you can pass other parameters to your grid options.
From ag-grid docs: What gets exported
The raw values, and not the result of cell renderer, will get used,
meaning:
...
Cell Formatters will NOT be used (use processCellCallback instead).
So, let's say that you have your column definition in a variable called columnDefs. Now you pass that to your gridOptions.
const gridOptions = {
columnDefs: columnDefs,
}
The latter code should work. So, now you want to handle the clicking on CSV Export on the context menu (You can also do it with a custom button if you may).
Export to CSV:
Now you have to add the provided getContextMenuItems function to you gridOptions object. For more info: Configuring the Context Menu
const gridOptions = {
columnDefs: columnDefs,
getContextMenuItems() {
return [
{
name: 'CSV Export',
action: function(params) {
gridOptions.api.exportDataAsCsv({
processCellCallback: function(cell) {
// Manipulate the value however you need.
return cell.value;
},
});
},
},
];
},
};
The idea is to get the CSV Export and programmatically add what you need to happen in the action. On the action, what you need is to call the exportDataAsCsv function from the gridOptions.api. Now (I know this is a ton of information) one of the options you have is to include your processCellCallback function where you can make sure you pass the cell value. Doing so is very useful because you can manipulate the value however you may need (e.g. Adding a $ sign to a number that is supposed to be money).
Custom button:
There is not much to say in the case you need a custom button. The only thing you would need to do is make sure you call the exportDataAsCsv on the gridOptions.api when onclick event is fired.
Something like:
onClick() {
gridOptions.api.exportDataAsCsv({
processCellCallback: ...
});
}
As another answer mentions, the ag-grid docs specifically state "Cell Renderers will NOT be used":
https://www.ag-grid.com/javascript-grid-excel/#what-gets-exported
I did a workaround that calls the cellRenderer from the processCellCallback function, like this:
processCellCallback: function (cell) {
var cellVal = cell.value;
if(_.get(cell, 'column.colDef.cellRenderer')) {
cellVal = cell.column.colDef.cellRenderer({value: cell.value});
}
return cellVal;
}
A lil late to the party but I recently had the same inconvenience, missing the raw values because four columns needed the cellRenderer and as you know per the docs those values will not be used. In my example I needed to plug in the raw values for in_progress, complete, unanswered, & needs_review
gridApi.exportDataAsExcel({
...
processCellCallback: (params) => processCells(params)
});
const processCells = (cell) => {
let cellVal = cell.value;
const {
in_progress,
complete,
unanswered,
needs_review
} = cell.node.data.percentageStatus; // raw data source
if(!cell.value){ // for the 4 cols this was always undefined
switch(cell.column.colId){
case "in_progress":
cellVal = in_progress;
break;
case "completed":
cellVal = complete;
break;
case "unanswered":
cellVal = unanswered;
break;
case "needs_review":
cellVal = needs_review;
break;
default:
cellVal = "n/a";
}
}
return cellVal;
}
I've found these answers a little confusing. Hoping to help the next dev looking into this. Vue JS with Vux store.
const actions = {
exportPurchaseOrderLines({ state: stateObject }) {
//set gridApi in state
stateObject.gridApi.exportDataAsCsv({
// nice file name
fileName: `${stateObject.order.number}-export`,
// select only selected rows
onlySelectedAllPages: true,
processCellCallback: (params) => {
// use the header name to determine which column to process
if (params.column.colDef.headerName === 'Linked bills') {
// if there are linked bills, return the bill numbers
if (params.node.data.bills.length > 0) {
return params.node.data.bills.map((bill) => bill.number).join(', ');
}
}
// return all the other cells without issues
return params.value;
},
});
},
// more actions .....
};
This is how I solved this issue :
When I call the export function the params arg in the cellRenderer function is not the same when the grid is loaded, the params arg looks like this when we export :
{export:true, value: "{...}"}
the value is the value in the field key, so in the cellRenderer function I had to do like this :
if(angular.isDefined(params.export)){
return JSON.parse(params.value.slice(1, -1)).name;
}
Notice that double quotes are always added which looks weird, so when we have a string value in the field, params.value will look like this : ""theStringValue"", that's why I use params.value.slice(1, -1).
I have a long list of items that I want to show in a <ul>. I want to add a "filter" input, so the user can narrow down the list of items to those matching the filter.
My controller contains a filter prop and a list array:
function Ctrl() {
this.filter = m.prop('');
this.list = [];
}
I've added an update method to the controller, which looks at the filter prop and updates the contents of the list array:
Ctrl.prototype.update = function (value) {
var _this = this;
if (this.filter()) {
searchItems(this.filter(), function (items) {
_this.list = items;
});
} else {
this.list = [];
}
};
Finally, my view iterates over the list array and renders the items. Additionally, it displays an input on top, bound to the filter prop:
var view = function (ctrl) {
return m('#content', [
m('input', {
oninput: m.withAttr("value", ctrl.filter),
value: ctrl.filter()
}),
m('ul', [
ctrl.list.map(function (item, idx) {
return m('li', m('span', item.getName()));
})
])
]);
};
My question is, how to make the update function fire when the value of filter changes, so that I get the updated list of items?
Do I need to position two oninput events? One to update filter and one to fire update?
Should I use a single oninput event and update the filter property within he update function?
Anything else?
When you use m.withAttr, what you're saying is that when the event handler fires (oninput), you will take some attribute of the element (value) and pass it into your second argument, which is a function (ctrl.filter). Your current sequence of events:
filter property gets updated
mithril redraws
What you want to do, is call the update function (instead of the getter/setter ctrl.filter function), and bind it so you can retain the proper context in the function:
m('input', {
oninput: m.withAttr("value", ctrl.update.bind(ctrl)),
value: ctrl.filter()
}),
Then, in your update function the value will be passed to the function and you can set it there.
Ctrl.prototype.update = function (value) {
this.filter(value);
...
Now what'll happen:
ctrl.filter property gets updated
ctrl.list gets filtered based on ctrl.filter
mithril redraws
Another way to handle this is to not have any "list" property in your controller / model, but to let the view grab a filtered list instead. There's only one thing really changing, after all, and that's the "filter" property. The filtered list is derived from that, so by creating another property on the controller, you're effectively duplicating the same state.
Additionally, you could keep m.withAttr('value', ctrl.filter) and benefit from that simplicity.
Something like:
var filteredItems = ctrl.getFilteredItems();
var view = function (ctrl) {
return m('#content', [
m('input', {
oninput: m.withAttr("value", ctrl.filter),
value: ctrl.filter()
}),
m('ul', [
filteredItems.map(function (item, idx) {
return m('li', m('span', item.getName()));
})
])
]);
};
I'm trying to apply several filters at once. Because there is a listener on the filters that will retrieve information from an online store when the filters are cleared. So I need them to be replaced at once.
The filter function is being called, log messages, but somehow the result is ignored. So all items are always shown whether includeStatus is true or false.
flipElements: function (includeStatus)
{
this.setFilters(
[
new Ext.util.Filter(
{
property: 'userName',
value: 'me'
}),
new Ext.util.Filter(
{
filterFn: function (record)
{
var result = includeStatus === (record.get('statusId') === 3);
console.log(result);
return result;
}
})
]);
}
The problem is not that the model does not have the property because when I simply set the filter with filterBy it does show/hide the items properly.
Edit: even when the log says false it still shows the items.
setFilters only changes the filters property of the store. In order to trigger filter actions call the function filter. This is normally used to add a filter in the style 'property,value' to the filterset, but when no arguments are given it filters the store.