While using ag-grid dataSurce, how can I know if the getRows function being called due to scrolling or filtering - ag-grid

As we know, in infinite rowModelType, we have to set dataSource for the ag-grid.
const dataSource = {
rowCount: count
getRows: (params: IGetRowsParams) => this.getRows(params, [])
};
this.gridApi.setDatasource(dataSource);
Now, dataSource.getRows method is called whenever there is need to fetch the rows in the grid (due to scrolling) OR the filter is changed.
I need to decide how many ajax calls need to be made depending on this reason. Below code blocks explains this.
private getRows(params: IGetRowsParams, data: any) {
// two ajax calls can be made from here
// 1. getCount
// 2. getData
// if this getRows function is called due to scrolling in the grid,
// I just want to call getData - no need to call getCount as I already know it
// if this is called due to change in filter,
// I need to call getCount as well as the no of rows will be different
// How can I know here due to which above mentioned reasons, getRows is getting called?
}
Is there anyway to know it within getRows function?

Related

Angular Ag Grid - Has anyone figured out a way to wait for a cell node update to happen and THEN fire a function, like a callback?

I am utilizing cellChanged.node.setDataValue(fieldChanged, oldValue) inside of the (cellValueChanged) event emitter, and I'm having trouble figuring out how to call a function once the setDataValue function has finished executing. I need to do this to do a check to see if a user has the permission to update a cell.
Here is the full code that checks:
if(this.showPaywallNotification) {
// Okay, so the budget is above what we allow HOWEVER...
if(budget > BUDGET_AMOUNT) {
this.showPaywallNotification = false;
cellChanged.node.setDataValue(fieldChanged, oldValue)
// Note: This timeout is in place to prevent the infinite updating bug
// This is problematic because if the user changes the cells fast enough, they can get around the paywall. If I change the timeout to be smaller, the resulting change triggers the update, which ends up creating an infinite loop of updates.
setTimeout(() => {
this.showPaywallNotification = true;
}, 230)
}
}
Is there a way I can replace my setTimeout() function with something better that can always ensure the user can't get around my paywall by just updating the cell faster than the timeout can execute?
You don't have to do polling. setDataValue is a not an async function.
Also, onCellValueChanged won't get called again if you call node.setDataValue.
Have a look at this plunk: Cell Editing - Revert to old value. Try updating any Age value to negative.
onCellValueChanged($event) {
if ($event.colDef.field === 'age' && $event.newValue < 0) {
// debugger
$event.node.setDataValue('age', $event.oldValue);
console.log('value reverted');
}
}
Let me know if something is not clear, or this is not sufficient.

ag-grid - how to get parent node from child grid via context menu?

As an example, I have a Master\Detail grid.
Master\Detail defined as key-relation model and on getDetailRowData method parent node data exists in params
but how to get parent node data from child view?
Tried via context-menu:
On right click - getContextMenuItems got executed which has an input params
On this sample, child-grid doesn't have any row and node in context-params is null, it's ok,
but anyway it should be possible to retrieve the parent details at least via grid API, isn't it ?
Then I've tried to get parent via node:
but instead of detail_173 as you can see its ROOT_NODE_ID, and here is a confusion for me.
So question is that how to get parent node data (RecordID 173 just in case) through child-view context menu or any other possible way (without storing temp value, cuz multiple children's could be opened at the same time)
Yes, I've read this part Accessing Detail Grid API, and still unclear how to get parent-node via child-grid.
For React Hook users without access to lifecycle methods, use Ag-Grid Context to pass the parent node (or parent data) to the detail grid via detailGridOptions. No need to traverse the DOM or use a detailCellRenderer unless you want to :)
https://www.ag-grid.com/javascript-grid-context/
detailCellRendererParams: (masterGridParams) => ({
detailGridOptions: {
...
context: {
masterGrid: {
node: masterGridParams.node.parent,
data: masterGridParams.data
}
},
onCellClicked: detailGridParams => {
console.log(detailGridParams.context.masterGrid.node);
console.log(detailGridParams.context.masterGrid.data);
}
}
})
Able to achieve it. Have a look at the plunk I've created: Get master record from child record - Editing Cells with Master / Detail
Right click on any child grid's cell, and check the console log. You'll be able to see parent record in the log for the child record on which you've click.
The implementation is somewhat tricky. We need to traverse the DOM inside our component code. Luckily ag-grid has provided us the access of it.
Get the child grid's wrapper HTML node from the params - Here in the code, I get it as the 6th element of the gridOptionsWrapper.layoutElements array.
Get it's 3rd level parent element - which is the actual row of the parent. Get it's row-id.
Use it to get the row of the parent grid using parent grid's gridApi.
getContextMenuItems: (params): void => {
var masterId = params.node.gridOptionsWrapper.layoutElements[6]
.parentElement.parentElement.parentElement.getAttribute('row-id');
// get the parent's id
var id = masterId.replace( /^\D+/g, '');
console.log(id);
var masterRecord = this.gridApi.getRowNode(id).data;
console.log(masterRecord);
},
defaultColDef: { editable: true },
onFirstDataRendered(params) {
params.api.sizeColumnsToFit();
}
Note: Master grid's rowIds are defined with [getRowNodeId]="getRowNodeId" assuming that account is the primary key of the parent grid.
A very reliable solution is to create your own detailCellRenderer.
on the init method:
this.masterNode = params.node.parent;
when creating the detail grid:
detailGridOptions = {
...
onCellClicked: params => console.log(this.masterNode.data)
}
Here is a plunker demonstrating this:
https://next.plnkr.co/edit/8EIHpxQnlxsqe7EO
I struggled a lot in finding a solution to this problem without creating custom details renderer but I could not find any viable solution. So the real good solution is already answered. I am just trying to share another way to avoid creating custom renderer.
So What I did is that I changed the details data on the fly and added the field I required from the parent.
getDetailRowData: function (params: any) {
params?.data?.children?.forEach((child:any) => {
//You can assign any other parameter.
child.parentId= params?.data?.id;
});
params.successCallback(params?.data?.children);
}
When you expand the row to render details the method getDetailRowData gets called, so it takes in params as the current row for which we are expanding the details and the details table is set by invoking params.successCallback. So before setting the row data I am iterating and updating the parentId.

ag-grid programmatically selecting row does not highlight

Using Angular 4 (typescript), I have some code like below using ag-grid 12.0.2. All I'm trying to do is load my grid and automatically (programmatically) select the first row.
:
this.gridOptions = ....
suppressCellSelection = true;
rowSelection = 'single'
:
loadRowData() {
this.rowData = [];
// build the row data array...
this.gridOptions.api.setRowData(this.rowData);
let node = this.gridOptions.api.getRowNode(...);
// console logging here shows node holds the intended row
node.setSelected(true);
// console logging here shows node.selected == true
// None of these succeeded in highlighting the first row
this.gridOptions.api.redrawRows({ rowNodes: [node] });
this.gridOptions.api.redrawRows();
this.gridOptions.api.refreshCells({ rowNodes: [node], force: true });
First node is selected but the row refuses to highlight in the grid. Otherwise, row selection by mouse works just fine. This code pattern is identical to the sample code here: https://www.ag-grid.com/javascript-grid-refresh/#gsc.tab=0 but it does not work.
Sorry I am not allowed to post the actual code.
The onGridReady means the grid is ready but the data is not.
Use the onFirstDataRendered method:
<ag-grid-angular (firstDataRendered)="onFirstDataRendered($event)">
</ag-grid-angular>
onFirstDataRendered(params) {
this.gridApi.getDisplayedRowAtIndex(0).setSelected(true);
}
This will automatically select the top row in the grid.
I had a similar issue, and came to the conclusion that onGridReady() was called before the rows were loaded. Just because the grid is ready doesn't mean your rows are ready.(I'm using ag-grid community version 19) The solution is to setup your api event handlers after your data has loaded. For demonstration purposes, I'll use a simple setTimeout(), to ensure some duration of time has passed before I interact with the grid. In real life you'll want to use some callback that gets fired when your data is loaded.
My requirement was that the handler resizes the grid on window resize (not relevant to you), and that clicking or navigating to a cell highlights the entire row (relevant to you), and I also noticed that the row associated with the selected cell was not being highlighted.
setUpGridHandlers({api}){
setTimeout(()=>{
api.sizeColumnsToFit();
window.addEventListener("resize", function() {
setTimeout(function() {
api.sizeColumnsToFit();
});
});
api.addEventListener('cellFocused',({rowIndex})=>api.getDisplayedRowAtIndex(rowIndex).setSelected(true));
},5000);
}
Since you want to select the first row on page load, you can do onething in constructor. But your gridApi, should be initialized in OnGridReady($event) method
this.gridApi.forEachNode((node) => {
if (node.rowIndex === 0) {
node.setSelected(true);
}
It's setSelected(true) that does this.
We were using MasterDetail feature, its a nested grid and on expanding a row we needed to change the selection to expanded one.
Expanding a row was handled in
detailCellRendererParams: {
getDetailRowData: loadNestedData,
detailGridOptions: #nestedDetailGridOptionsFor('child'),
}
and withing loadNesteddata, we get params using we can select expanded row as
params.node.parent.setSelected(true)
Hope this helps.

Controller lifecycle: when to call table.autoResizeColumn()

I'm currently using onAfterRendering() hook to auto adjust the layout of a table like this:
onAfterRendering: function() {
var table = this.getView().byId('table');
for (var i = 0; i < table.getColumns().length; i++) {
table.autoResizeColumn(i);
}
}
The result is not usable: all columns are sized 100% of the parent's width.
If I add a simple button to invoke the exact same logic the table gets drawn nicely. It looks like the complete table needs to be present in the DOM before autoResizeColumn() works properly.
My question: is there a suitable hook/event I can use to invoke the resizing once the table is in the document?
You can use the onAfterRendering of the table as suggested and add an if statement with bResized boolean or some kind of counter to prevent the endless loop.
As others have answered you can put add a function to oTable.onAfterRendering but the trick is if you do any rendering in that function it will trigger another rendering event that will again trigger onAfterRendering.
The problem here is where to hang the boolean switch that determine you have done the thing you have done.
One way to get around that is to add some customer data to the table with oTable.addCustomData.
The pattern becomes :
```
oTable.onAfterRendering = function () {
// check the prototype
// read the custom data
var customData = this.getCustomData();
// confirm *your* custom data is in the array (and not some other custom data)
// if not do your rendering operation and then
// set a switch to custom data
this.addCustomData(new sap.ui.core.CustomData({
key: "myRenderingCheck",
value: "true",
"writeToDom": true
}));

How do I know when component's DOM is updated and finished transitions in Ractive?

I'm trying to wrap IScroll library into Ractive component. Is there a way to be notified when component's DOM changed and finished any transitions so that I can update the scroller? I see the following ways to achieve this:
Declare dependencies (of the component's template) explicitly, like
<Scroll context="{{...}}" > </Scroll>
I can observe context variable, but still can't wait for transitions to finish.
Patch Ractive.set() so that it emits custom event when used:
// Broadcast promise returned by `set`.
var oldset = Ractive.prototype.set;
Ractive.prototype.set = function () {
var ret = oldset.apply(this, arguments);
this.fire('set', ret);
return ret;
};
Then, in the component's initialization code, I can subscribe to the set event:
this._parent.on('set', function (ret){
ret.then(update);
});
This will not work for Ractive.push() and other methods. Also this will notify my component about all changes made by set-ting something, not only about those affecting component's DOM. Finally, I must explicitly refer to component's parent using this._parent, which means my components can not be nested.
So, is there are better way to achieve this in Ractive?
The initialization options allow oncomplete (or ractive.on('complete'... if you prefer) for initial render. See http://docs.ractivejs.org/latest/lifecycle-events
All of the data modification methods, set, animate, push, slice, etc. return a promise that will not be called until DOM is updated and transitions have completed.
Here's a simple example (http://jsfiddle.net/ctfyes7t/):
{{#if show}}
<li intro='fade:{ duration: 2000 }'>ta da!</li>
{{/if}}
r.set('show', true).then(function(){
// called when fade intro complete
});
The problem can be approached from another direction: changes to the specific parts of DOM can be observed using MutationObserver.
So, I end up with the following approach to wrapping external widgets:
basically, we get component's top node (and initialize 3rd party code) using intro transition, and then in oncomplete callback register observer with something like this:
this.observer = new MutationObserver(update);
this.observer.observe(this.node, {
childList: true,
subtree: true,
characterData: true,
attributes: true, // transitions may change attributes
});
where function update updates 3rd-party widget.
Here is the source code of the component.