I need to re-order rows within ag-grid by using drag-drop. The drag drop methods are simple enough but when the drop occurs there seems no easy way to switch the rows locations.
I attempted a
gridOptions.api.removeItems(selectedNode);
to clear the current and then
gridOptions.api.insertItemsAtIndex(2, savedNode);
but the drop event appeared to re-fire preventing that approach. Plus the insertItems (when ran first) falls over in internal ag-grid looping.
I would rather not re-sort the gridRows manually and then reset the gridRow data which would be somewhat clunky. This seems a common request on most grids so i assume it can be done but have just missed the relevant documentation. Thanks for any help..
K have finally got an Angular 2 method working, though currently I have to switch off sorting and filtering on the grid.
This particular example is relying on row selection being enabled (due to how it is finding some records). Grid dragdrop should be disabled (as that drags the grid visually which sucks) Instead use a processRowPostCreate to set up draggable params at the row level. This sets the dragged option to the row which looks much nicer.
in gridOptions
processRowPostCreate: (params) => {
this.generateRowEvents(params);
},
which calls
private generateRowEvents(params) {
params.eRow.draggable = true;
params.eRow.ondragstart = this.onDrag(event);
params.eRow.ondragover = this.onDragOver(event);
params.eRow.ondrop = this.onDrop(event);
}
I track the source recrord in the onDrag method
var targetRowId: any = $event.target.attributes.row.value;
this.savedDragSourceData = targetRowId;
onDragOver as usual
On drop we have to protect against an infinite loop (ag-grid appears to recall live methods when items are added to the grid hence the ondrop occurs multiple times) and then insert, delete and splice over both the grid and its data source ( I will carry on looking at using the grid to populate the data as opposed to the source data as that would allow source/filter, currently doign that inserts blank rows). An event (in this case) is then emitted to ask the owning component to 'save' the adjusted data.
private onDrop($event) {
if ($event && !this.infiniteLoopCheck) {
if ($event.dataTransfer) {
if (this.enableInternalDragDrop) {
this.infiniteLoopCheck= true;
var draggedRows: any = this.gridRows[this.savedDragSourceData];
// get the destination row
var targetRowId: any = $event.target.offsetParent.attributes.row.value;
// remove from the current location
this.gridOptions.api.removeItems(this.gridOptions.api.getSelectedNodes());
// remove from source Data
this.gridRows.splice(this.savedDragSourceData, 1);
if (draggedRows) {
// insert into specified drop location
this.gridOptions.api.insertItemsAtIndex(targetRowId, [draggedRows]);
// re-add rows to source data..
this.gridRows.splice(targetRowId, 0, checkAdd);
this.saveRequestEvent.emit(this.gridRows);// shout out that a save is needed }
this.v= false;
}
else {
this.onDropEvent.emit($event);
}
}
}
}
NOTE we are wrapping the grid in our own class to allow us to write one set of grid methods and not replicate over the application whenever the grid is used.
I will be refining this over the next few days but as a basic this allows the ag-grid in angular 2 to drag drop rows to sort records.
If you don't find a solution within ag-grid then you can do this by adding one more directive("ngDraggable") and integrate it with ag-grid.
Please find the following working plnkr for this.
https://embed.plnkr.co/qLy0EX/
ngDraggable
Hope this helps..
Related
I am working on an editable table in Angular application with ag-grid library. I would like to keep editing cells (in full row edit mode) until I finish with it and then close the editors manually via API. The problem is that the editor is closing on other cell click/focus (on some other line) as described here:
The grid will stop editing when any of the following happen:
Other Cell Focus: If focus in the grid goes to another cell, the editing will stop.
I cannot figure out how to disable this, if it is possible. Installing the onCellMouseDown() hook does not help, because the cellFocused event is fired before cellMouseDown. Therefore, the editing stops before I have a chance to intercept the mousedown event.
Here is my stackblitz little extract with related pieces of code.
The need for such scenario is that I want to validate the entry and not to allow a used to quit the editing if the form is not valid. The only workaround I found so far is that on any click outside of editing cells when the editor closing I reopen it right away in onRowEditingStopped() hook unless the editor has been closed via 'OK' button.
After all, I have managed to provide a custom solution that fits exactly into this problem which I was facing also.
First thing is to disable pointer events to non edited rows when a specific row is currently being edited. On Ag-grid's 'cellEditingStarted' callback I have added the following code:
public cellEditingStarted(event: any): void {
//not all rows are on dom ag-grid takes care of it
const nonSelectedGridRows = document.querySelectorAll('.ag-grid-custom-row:not(.ag-row-selected):not(.ag-row-editing):not(.pointer-events-none)');
forEach(nonSelectedGridRows, row => {
row.classList.add("pointer-events-none");
});
}
Because not all rows exist on dom (Ag-grid creates and destroys while you are scrolling )when a specific cell is being edited, I have also added a rowClassRule which is applied when rows are being created:
this.rowClassRules = {
'pointer-events-none': params => {
if (params.api.getEditingCells().length > 0) {
return true;
}
return false;
}
};
scss:
.pointer-events-none {
pointer-events: none
}
By disabling pointer events, when you click on a non edited cell the cell won't get focus and thus the currently edited cell will stil remain on edit mode. You can provide your own custom validation solution and close the editor manually through API. When you are done, you have to enable pointer events to all grid rows back again:
private enablePointerEvents(): void {
//not all rows are on dom ag-grid takes care of it
const nonSelectedGridRows = document.querySelectorAll('.ag-grid-custom-row.pointer-events-none');
forEach(nonSelectedGridRows, row => {
row.classList.remove("pointer-events-none");
});
}
I implemented the same above approach in Ag-Grid React.
I used getRowStyle callback for adding the css pointerEvents: none on dynemic basis.
It seems to be working for me fine.
Please refer the below code
const getRowStyle = (params) => {
// this is not initialized in read mode
// condition for me ==> currentEditRowIndex.current !== null && params.node.rowIndex !== currentEditRowIndex.current
if (someCondition for Row other than inline edit row) {
return { pointerEvents: "none" };
}
return null;
};
After adding this whenver you start the editing..You will need to call redrawRows so that css changes can be applied.
Hope this will help. Thank You!!
Thought I would share another solution that has been working out okay for me so far.
Using 'pointer-events-none' as suggested in the other answer is flawed because the Enter key can also close the editor.
In my case, I want to prevent the editor from closing when client side validation has failed and the data is invalid. When my conditions are met, I call stopPropagation() on the events to prevent the editor close from happening in the first place. It still has potential problems:
It cancels mousedown, dblclick, keydown, focusout and click for all elements that have a class name starting with ag- so if you happen to use this class prefix for other controls on the page, it could interfere. It also means any controls within the grid (sorting, resizing, etc.) don't work while the condition is met.
Calling stopPropagation() could potentially interfere with your own custom controls. So far I've been okay if I dont use the ag- prefix within the markup from my own custom cell editors and renderers
I hope they can add a proper API function to cancel the row/cell stopEditing function in the future.
["mousedown", "dblclick", "keydown", "focusout", "click"].forEach(function (eventName) {
document.addEventListener(eventName, function (e) {
if ( conditionForCancelingIsMet() ) {
// this appears to cancel some events in agGrid, it works for
// preventing editor closing on clicking other cells for example.
// It would be ideal if this worked for all ag-grid specific events
// and had a proper public API to use!
e["__ag_Grid_Stop_Propagation"] = true;
}
// convert element classList to normal js array so we can use some()
var classArray = [].slice.apply(e.target.classList);
if ( conditionForCancelingIsMet() && classArray.some(c => c.startsWith("ag-")) ) {
// unfortunately some events like pressing the 'enter' key still
// require stopPropagation() and could potentially interfere with unrelated controls
e.stopPropagation();
}
}, true);
});
When rows are selected by the user, I save which rows are selected in some state. When the grid is rendered, I want those rows to still be selected. I've tried in onModelChanged calling setSelected on all the selected rows. However, this is not performant when lots of rows are selected. Also, there is a visible moment before the rows are selected, which is not ideal.
Is there any way I can pre-select rows before the grid is rendered?
I managed to select a row by using firstDataRendered event.
gridAPI.addEventListener(
"firstDataRendered",
({ api }) => {
// Has to wait until the next tick
setTimeout(() => {
api.getDisplayedRowAtIndex(0).selectThisNode(true);
}, 0);
},
);
Is there any way I can pre-select rows before the grid is rendered?
I assume that you are looking for configuration like editable for columns (its configurable), columns exist after gridReady event, but rows - only after firstRenderer event.
On top of that there are no properties for rows and as far as I know (and also double-checked on doc) there is no settings for that.
I mean you can configure lots of things, but the selection is out of it.
And on their examples, they are using forEach for that.
Yes you can preselect rows like below example.
onGridReady(params) {
this.gridOptions.api.forEachNode(node=> node.rowIndex === 1 ? node.setSelected (true) : node.setSelected(false));
}
You can make condition based on your state.
You can use firstDataRendered. Then loop on each node to set the data as selected. Hope this helps you!
Adding rows is pretty straightforward - just have to add a new row in my Redux store, which then updates my table.
However if I delete a row from my Redux store the row data still seems to hang around somewhere - I have a few custom cell renderers which will then trigger errors as the data gets passed to them from Ag-Grid, but the row no longer exists in my store creating the errors.
Code example would be helpful here to figure out the source of issue- in meantime please check if after manipulating your store, you do refresh the grid data - something like:
gridOptions.api.setRowData(gridOptions.rowData)
More about ag-grid row data updating can be found here:
https://www.ag-grid.com/javascript-grid-data-update/
https://www.ag-grid.com/react-redux-integration-pt1/
Data binding in React can be achieved by using a controlled input. A controlled input is achieved by binding the value to a state variable and a onChange event to change the state as the input value changes.
See the below snippet:
onRemoveSelected() {
var selectedData = this.gridApi.getSelectedRows();
var res = this.gridApi.updateRowData({ remove: selectedData });
this.props.actions.delete(selectedData.id);
}
Unfortunately neither of these suggestions worked in my case.
Setting a debugger in my custom cellRenderer showed that the row had been removed from this.props.api.gridOptionsWrapper.gridOptions.rowData, yet the cellRenderer was still called for the deleted row.
The quick fix solution I came up with was just to test if the row (this.props.data) using the cellRenderer existed in this.props.api.gridOptionsWrapper.gridOptions.rowData in a shouldComponentUpdate within the cellRenderer.
shouldComponentUpdate(nextProps) {
const { api, data } = nextProps;
const gridRowDataIds = api.gridOptionsWrapper.gridOptions.rowData.map(
row => {
return JSON.parse(row.rowMetadata).id;
}
);
const dataPropsId = JSON.parse(data.rowMetadata).id;
return gridRowDataIds.includes(dataPropsId);
}
Note: rowMetadata is just a hidden column used to hold various properties regarding the row, such as an id, visibility, if it has been edited, etc.
In an app using AG-Grid Enterprise, React and Redux, I would like to programmatically add a new row to a grid that has sorting enabled. Is is possible to ignore grid sorting and insert this new row in a specific grid position?
To clarify this scenario, I forked the Simple Immutable Store Plnkr available at AG-Grid's website and created a new one here.
In the original Plnkr, button Append will add items to the end of the list. In the forked version, due to column Price ascending sort and due to the fact that new items do not have a price defined anymore, all appended items will show up in the first positions of the grid.
My understanding is that this happens because in the last line from the snippet below (this.gridApi.setRowData), AG-Grid will add new rows and trigger sort, filters, etc.
addFiveItems(append) {
var newStore = immutableStore.slice();
for (var i = 0; i < 5; i++) {
var newItem = createItem(append);
if (append) {
newStore.push(newItem);
} else {
newStore.splice(0, 0, newItem);
}
}
immutableStore = newStore;
this.gridApi.setRowData(immutableStore);
}
With that in mind, is there any AG-Grid API that will allow items to be added in specific grid positions while sort is activated? The goal is to keep Append button original behaviour: append items to the end of the grid, even if sort is defined in this AG-Grid Enterprise, React and Redux scenario.
Turns out that from AG-Grid 17+, a new callback, called postSort is available (see https://www.ag-grid.com/javascript-grid-sorting/#post-sort).
As detailed in the documentation above, this callback may be used to tweak row order even after sort is applied.
I have a FilterRowHeaderComposite layer where a user can input a filter to filter the displayed rows. I also display a count of the current # of rows that are showing.
I was wondering what the best approach would be to update the displayed row count when someone inputs a filter and the number of rows change. Would it be to capture some particular event, extend the FilterRowHeaderComposite and fire some event, etc?
Thanks!
Update:
This is what I ended up doing after Dirks comment
nattable.addLayerListener(event -> {
if (event instanceof RowStructuralRefreshEvent) {
// Code to update count to user
}
});
The GlazedListsEventLayer fires either a RowStructuralRefreshEvent or a VisualRefreshEvent in the UI thread the NatTable stack upwards if a list change occurs. So you can listen to that. Or you do this by creating a GlazedLists ListEventListener that you register on the FilterList and listen directly to changes on the list itself.