I would like to delete the values in all selected cells when the user presses backspace or delete, how do I iterate through the selected cells?
I'm using React and Typescript
The docs are out of date, this worked for me, in react / typescript
(
// The type of this is shown below, but is impossible to work with
event: any
// | CellKeyPressEvent<CountryLineProductRow>
// | FullWidthCellKeyPressEvent<CountryLineProductRow>
) => {
if (event.event.key === 'Backspace' || event.event.key === 'Delete') {
event.api.getCellRanges().forEach((cellRange: CellRange) => {
if (cellRange.startRow && cellRange.endRow) {
const startRow = Math.min(
cellRange.endRow.rowIndex,
cellRange.startRow.rowIndex
);
const endRow = Math.min(
cellRange.endRow.rowIndex,
cellRange.startRow.rowIndex
);
cellRange.columns.forEach((column: Column) => {
if (column.getColDef().editable) {
for (
let rowNumber = startRow;
rowNumber <= endRow;
rowNumber++
) {
const model = event.api.getModel();
const rowNode = model.rowsToDisplay[rowNumber];
rowNode.setDataValue(column.getColDef().field, null);
}
}
});
}
});
}
}
Related
Im trying to build a table with nested tree folder inside.
When trying to add nested data into the datasource data the structure will not updated and will not toggle anymore.
Code below:
https://stackblitz.com/edit/angular-table-tree-example-k2zqmt?file=app%2Ftable-basic-example.ts&file=app%2Ftable-basic-example.html,app%2Ftable-basic-example.ts
Environment
Angular:
Material Table
Material tree system
These are the things that are happening when logNode method is called
The item is getting added but the treeControl.toggle method does not work anymore.
When you are assigning a new dataset to the dataSource all the nodes get reset and the tree closes, so this.treeControl.toggle is trying to toggle a node that does not exist.
You need to find the node to be toggled from the list you get from treeControl.dataNodes
I would suggest having the toggle code in a separate method and adding a node code in a separate method, and a separate button to add the node.
The below code should work for your scenario, also remove this line from your HTML, (click)="treeControl.toggle(data)"
interface ExampleFlatNode {
expandable: boolean;
RoleName: string;
Access: boolean;
level: number;
CatId: number;
}
private transformer = (node: FoodNode, level: number) => {
return {
expandable:
!!node.CategoryPermissions && node.CategoryPermissions.length > 0,
RoleName: node.RoleName,
Access: node.Access,
level: level,
CatId: node.CatId,
};
};
tempNodes = []
constructor() {
this.dataSource.data = TREE_DATA;
}
logNode(clickedNode) {
this.tempNodes = [];
this.treeControl.dataNodes.forEach((node) =>
this.tempNodes.push({
...node,
expanded: this.treeControl.isExpanded(node),
})
);
if (!this.treeControl.isExpanded(clickedNode)) {
const temp = {
Access: true,
RoleName: 'test 1 2',
CatId: 113,
};
const clickedNodeIdx = this.treeControl.dataNodes.findIndex(
(node: any) =>
node.CatId === clickedNode.CatId &&
node.RoleName === clickedNode.RoleName &&
node.level === clickedNode.level
);
const childIdx = 1;
let child;
if (clickedNode.level === 0) {
child =
this.dataSource.data[clickedNodeIdx].CategoryPermissions[childIdx];
} else {
this.dataSource.data.forEach(
(item) => (child = this.findDataSource(item, clickedNode))
);
}
child.CategoryPermissions.push(temp);
this.dataSource.data = this.dataSource.data;
const addedNode = this.treeControl.dataNodes.find(
(node: any) =>
node.CatId === temp.CatId && node.RoleName === temp.RoleName
);
this.expandParent(addedNode);
this.setPreviousState();
} else {
this.treeControl.collapse(clickedNode);
}
}
findDataSource(item, node) {
if (item.RoleName === node.RoleName) {
return item;
} else if (item.CategoryPermissions) {
let matchedItem;
item.CategoryPermissions.forEach((e) => {
const temp = this.findDataSource(e, node);
if (temp) {
matchedItem = temp;
}
});
return matchedItem;
}
}
setPreviousState() {
for (let i = 0, j = 0; i < this.treeControl.dataNodes.length; i++) {
if (
this.tempNodes[j] &&
this.treeControl.dataNodes[i].RoleName === this.tempNodes[j].RoleName &&
this.treeControl.dataNodes[i].CatId === this.tempNodes[j].CatId &&
this.treeControl.dataNodes[i].level === this.tempNodes[j].level
) {
if (this.tempNodes[j].expanded) {
this.treeControl.expand(this.treeControl.dataNodes[i]);
}
j++;
}
}
}
expandParent(node: ExampleFlatNode) {
const { treeControl } = this;
const currentLevel = treeControl.getLevel(node);
const index = treeControl.dataNodes.indexOf(node) - 1;
for (let i = index; i >= 0; i--) {
const currentNode = treeControl.dataNodes[i];
if (currentLevel === 0) {
this.treeControl.expand(currentNode);
return null;
}
if (treeControl.getLevel(currentNode) < currentLevel) {
this.treeControl.expand(currentNode);
this.expandParent(currentNode);
break;
}
}
}
My Svelte component is set up to find all people in a MongoDB document and list them in a table. When clicking on a column header the collection should sort by that column/field and it should toggle the sort direction with subsequent clicks.
My script section:
$: sortColumn = 'name';
$: sortDirection = 'asc';
$: sortParameters = setSortParams(sortColumn, sortDirection);
$: contactList = [];
function getContactList(sortObj) {
contactList = Contacts.find({
isBlocked: false,
isDeleted: { $ne: true }
},
{
sort: sortObj
}).fetch();
contactList = contactList;
}
onMount(() => {
getContactList(setSortParams(sortColumn, sortDirection));
});
function changeSortDirection() {
if (sortDirection === 'asc') {
sortDirection = 'desc';
} else {
sortDirection = 'asc';
}
}
function sortByColumn(col) {
if (col === sortColumn) {
changeSortDirection();
} else {
sortDirection = 'asc';
}
sortColumn = col;
getContactList(setSortParams(sortColumn, sortDirection));
}
function setSortParams(sortField, sDirection) {
let sortParams = [];
let direction = sDirection || 1;
let field = sortField || 'name';
if (direction === 'asc') {
direction = 1;
} else {
direction = -1;
}
if (field === 'name') {
sortParams.push(['firstName', direction]);
sortParams.push(['lastName', direction]);
} else {
sortParams.push([field, direction]);
}
sortParams = sortParams;
return sortParams;
}
And the relevant part of my svelte file:
{#each columns as column}
<th class="contact-table__column contact-table__column-header"
on:click={() => sortByColumn(column.type)}>
<span class="contact-table__title">{column.display} {sortDirection}</span>
</th>
{/each}
The collection reorders when I click on a different column header, but it doesn't reorder when I click on the same header (it should switch between ASC and DESC sort order).
I'm new to Svelte and Meteor so I'm sure there's a few things I'm doing wrong. I appreciate any help.
I'm looking for a way to insert a checkbox into a select and fetch each column individually, all using DataTables. I found an excellent example in https://jsfiddle.net/Lxytynm3/2/ but for some reason, when selecting all records, filtering does not display the data as expected. Would anyone have a solution to work properly?
Thanks in advance.
The link application code is as follows:
$(document).ready(function() {
function cbDropdown(column) {
return $('<ul>', {
'class': 'cb-dropdown'
}).appendTo($('<div>', {
'class': 'cb-dropdown-wrap'
}).appendTo(column));
}
$('#example').DataTable({
initComplete: function() {
this.api().columns().every(function() {
var column = this;
var ddmenu = cbDropdown($(column.header()))
// -------------------------------------------------------
.on('change', ':checkbox', function() {
var active;
var vals = $(':checked', ddmenu).map(function(index, element) {
active = true;
return $.fn.dataTable.util.escapeRegex($(element).val());
}).toArray().join('|');
column
.search(vals.length > 0 ? '^(' + vals + ')$' : '', true, false)
.draw();
// -------------------------------------------------------
// Highlight the current item if selected.
if (this.checked) {
$(this).closest('li').addClass('active');
// If 'Select all / none' clicked ON
if ($(this).val() === "1") {
$(ddmenu).find('input[type="checkbox"]').prop('checked', this.checked)
//$(".cb-dropdown li").prop('checked', true);
//$('.cb-dropdown').closest('li').find('input[type="checkbox"]').prop('checked', true);
// $('this input[type="checkbox"]').prop('checked', true); works!
// $( 'input[type="checkbox"]' ).prop('checked', this.checked);
// $(this).find('input[type="checkbox"]').prop('checked', this.checked)
//$('div.cb-dropdown-wrap.active').children().find('input[type="checkbox"]').prop('checked', this.checked)
}
} else // 'Select all / none' clicked OFF
{
$(this).closest('li').removeClass('active');
// test if select none
if ($(this).val() === "1") {
// code to untick all
$(ddmenu).find('input[type="checkbox"]').prop('checked', false)
}
}
// Highlight the current filter if selected.
var active2 = ddmenu.parent().is('.active');
if (active && !active2) {
ddmenu.parent().addClass('active');
// Change Container title to "Filter on" and green
//$(this).parent().find('.cb-dropdown li:nth-child(n+1)').css('color','red');
$('active2 li label:contains("Filter OFF")').text('Yeees');
} else if (!active && active2) {
ddmenu.parent().removeClass('active');
}
});
// -------------------------------------------------------
var mytopcount = '0'; // Counter that ensures only 1 entry per container
// loop to assign all options in container filter
column.data().unique().sort().each(function(d, j) {
// Label
var $label = $('<label>'),
$text = $('<span>', {
text: d
}),
// Checkbox
$cb = $('<input>', {
type: 'checkbox',
value: d
});
$text.appendTo($label);
$cb.appendTo($label);
ddmenu.append($('<li>').append($label));
// -----------------
// Add 'Select all / none' to each dropdown container
if (mytopcount == '0') // Counter that ensures only 1 entry per container
{
$label = $('<label>'), $text = $('<span>', {
text: "Select all / none"
}),
$cb = $('<input>', {
type: 'checkbox',
value: 1
});
$text.prependTo($label).css('margin-bottom', '6px');
$cb.prependTo($label);
ddmenu.prepend($('<li>').prepend($label).css({
'border-bottom': '1px solid grey',
'margin-bottom': '6px'
}));
mytopcount = '1' // This stops this section running again in cotainer
}
});
});
}
});
});
It seems as though the issue was with the select all checkbox. One solution would be to check for "1" within the vals initialisation, this seems to work:
var vals = $(':checked', ddmenu).map(function(index, element) {
if($(element).val() !== "1"){
return $.fn.dataTable.util.escapeRegex($(element).val());
}
}).toArray().join('|');
That should see you with some results upon the top checkbox being checked. Hope that helps.
As per title, is there a way to customise the "ghost text" with the (unmanaged) row-dragging implementation in AgGrid via the API?
I found workaround using valueGetter property to change this text.
Example configuration of column:
private dragDropColumn= {
rowDrag: true,
...
valueGetter: (params) => {
return params.data.myVariableFromRow;
}
}
Hope this helps
You can now use colDef.rowDragText to set a callback function to set the dragged text.
https://www.ag-grid.com/javascript-grid-row-dragging/#custom-row-drag-text
onRowDragEnter event,
you could search for the ghost element
and then append your custom class to it.
document.querySelectorAll('.ag-dnd-ghost')[0]
.classlist.add('my-custom-ghost-element');
Dont forget to follow the hierarchy of classes, otherwise you would end up using !important in the custom class :-)
// For updating text
search for the element with className ag-dnd-ghost-label
document.querySelectorAll('.ag-dnd-ghost-label')[0]
.innerHTML = 'your_custom_text';
Ghost element is added only during drag, once drag is ended, its removed from dom by Ag-Grid.
Hope this helps
I've researched this and there is nothing built in to ag-grid. I accomplished this by attaching to the onRowDragMove and onRowDragMove events.
I set a class variable 'isRowDragging' to only do this once while dragging.
I use the Angular Renderer2 class (this.ui) to find and update the Element of the ghost label.
All of this is available with vanilla javascript or other supported ag-grid frameworks.
this.gridOptions.onRowDragMove = (params: RowDragMoveEvent) => {
const overNode = params.overNode;
const movingNode = params.node;
if (!this.isRowDragging) {
this.isRowDragging = true;
if (!movingNode.isSelected()) {
if (params.event && params.event.ctrlKey) {
movingNode.setSelected(true);
}
if (params.event && !params.event.ctrlKey) {
movingNode.setSelected(true, true);
}
}
let labelText: string = '';
const selectedNodes = this.gridOptions.api.getSelectedNodes();
if (selectedNodes.length === 1) {
labelText = selectedNodes[0].data.Name;
}
else {
const guids: string[] = [];
let folderCount: number = 0;
let docCount: number = 0;
selectedNodes.forEach((node: RowNode) => {
guids.push((node.data.FolderGuid || node.data.DocumentGuid).toLowerCase());
if (node.data.FolderGuid !== undefined) {
folderCount++;
}
else {
docCount++;
}
});
if (folderCount === 1) {
labelText = '1 folder';
}
else if (folderCount > 1) {
labelText = folderCount + ' folders';
}
if (docCount === 1) {
labelText += (labelText !== '' ? ', ' : '') + '1 document';
}
else if (docCount > 1) {
labelText += (labelText !== '' ? ', ' : '') + docCount + ' documents';
}
}
console.log(this.ui.find(document.body, '.ag-dnd-ghost').outerHTML);
this.ui.find(document.body, '.ag-dnd-ghost-label').innerHTML = labelText;
}
}
this.gridOptions.onRowDragEnd = (event: RowDragEndEvent) => {
this.isRowDragging = false;
}
I am using ag-grid with react and using cellRendererFramework property of ColDef to use custom react component, my value getter returns object, so when I start dragging I am getting [object object], I added toString method to returning object and did the trick, below is my sample code
const col1 = {
rowDrag: true,
...
valueGetter: (params) => {
const {id, name} = params.data;
return {id, name, toString: () => name}; // so when dragging it will display name property
}
}
I've customized the arrow navigation keys using the callback 'navigateToNextCell'.
But now I want to customize 'Home' and 'End' key.
There is any way to do it?
It's not super easy at the moment... here are some relevant "enhancements" that have been requested on the github page:
support page up/ down, home and end keys
allow overriding of keyboard events
Suggestion: Allow any key for navigation (not just tab)
That last link has something useful. There is a comment on how to disable any propagation of the 'home' and 'end' keys:
// note, this is angular 2 code, `this.el.nativeElement` is just the grid component
document.documentElement.addEventListener(
'keydown',
(e: KeyboardEvent) => {
// this runs on the document element, so check that we're in the grid
if (this.el.nativeElement.contains(e.target)) {
if (e.key === 'Tab' || e.key === 'Home' || e.key === 'End') {
e.stopImmediatePropagation();
e.stopPropagation();
}
if (e.key === 'Home' || e.key === 'End') {
// we don't want to prevent the default tab behaviour
e.preventDefault();
}
}
},
true
);
AFTER you have done that, you could add new event listeners to the grid, or depending on what you are trying to do, you could add listeners to specific cells as it mentions in the Keyboard Navigation section of the Ag-Grid docs under Custom Actions:
Custom Actions
Custom cell renderers can listen to key presses on the focused div.
The grid element that receives the focus is provided to the cell
renderers via the eGridCell parameter. You can add your own listeners
to this cell. Via this method you can listen to any key press and do
your own action on the cell eg hitting 'x' may execute a command in
your application for that cell.
I initially wrote this code for navigating home/end with command+arrow key, but have put comments where logic differs since 98% will be the same. Comments are untested but should work
I will divide this into 4 parts:
suppress the event so ag-grid does nothing by default
find a way for us to place a event listener on the right keys
compute which cell should be navigated to next
tell ag grid which cell should be selected
For #1, we can use suppressKeyboardEvent and either set it on defaultColDef(in `GridOptions´) or on every col def.
Mine looks like this:
const suppressKeyboardEventFn: ColDef["suppressKeyboardEvent"] = (
params: SuppressKeyboardEventParams
) => {
const key = params.event.key;
const isControl = isControlKey(params.event);
// For end/home, test for key === "Home" || key === "End" instead
const isNavigation =
key === "ArrowUp" ||
key === "ArrowDown" ||
key === "ArrowRight" ||
key === "ArrowLeft";
if (isNavigation && isControl) {
return true;
}
return false;
};
isControlKey is so it works on both mac&windows, and is written as recommended by MDN (not relevant if using home/end key):
const isMac = navigator.platform.startsWith("Mac");
export const isControlKey = (event: KeyboardEvent) => {
return isMac ? event.metaKey : event.ctrlKey;
};
For #2 where we want to handle the event ourselves, we can use onCellKeyDown:
const onCellKeyDownFn: GridOptions["onCellKeyDown"] = (cellKeyDown) => {
if (cellKeyDown.event == null) {
return;
}
const event = cellKeyDown.event as KeyboardEvent;
const key = event.key;
const isControl = isControlKey(event);
// Instead write cases here for case "Home": and case "End":
switch (key) {
case "ArrowUp": {
if (isControl) {
dispatch(navigateCell({ direction: "up", end: true }));
}
break;
}
case "ArrowDown": {
if (isControl) {
dispatch(navigateCell({ direction: "down", end: true }));
}
break;
}
case "ArrowRight": {
if (isControl) {
dispatch(navigateCell({ direction: "right", end: true }));
}
break;
}
case "ArrowLeft": {
if (isControl) {
dispatch(navigateCell({ direction: "left", end: true }));
}
break;
}
}
};
Using the same isControlKey function. I have implemented redux navigation actions that handles navigating to home and end.
For #3, we can implement this in different ways. Here is how I FIND the correct cell to navigate to. Either one step to a direction, or to the end/home (which is our scenario, since I pass end: true)
// For using Home/End, add a case for each and mix logic from
// below to find the correct cell. Last column logic is in
// right when end===true and last row is in down when end===true etc
switch (params.direction) {
case "up": {
let newRowIndex;
if (params.end === true) {
newRowIndex = 0;
} else {
newRowIndex = selectedCell.lastKnownRowIndex - 1;
if (newRowIndex < 0) {
return;
}
}
const node = gridApi.getDisplayedRowAtIndex(newRowIndex);
if (node?.id == null) {
log.warn("Could not find Node ID");
return;
}
dispatch(
selectCell({
nodeId: node.id,
colKey: selectedCell.colKey,
})
);
break;
}
case "down": {
let newRowIndex;
if (params.end === true) {
newRowIndex = maxRowIndex;
} else {
newRowIndex = selectedCell.lastKnownRowIndex + 1;
if (newRowIndex > maxRowIndex) {
return;
}
}
const node = gridApi.getDisplayedRowAtIndex(newRowIndex);
if (node?.id == null) {
log.warn("Could not find Node ID");
return;
}
dispatch(
selectCell({
nodeId: node.id,
colKey: selectedCell.colKey,
})
);
break;
}
case "right": {
let nextColumn;
if (params.end === true) {
const columns = columnApi.getAllGridColumns();
nextColumn = columns[columns.length - 1];
} else {
const column = columnApi.getColumn(selectedCell.colKey);
if (column == null) {
log.warn("Could not find column");
return;
}
nextColumn = columnApi.getDisplayedColAfter(column);
}
if (nextColumn == null) {
return;
}
dispatch(
selectCell({
nodeId: selectedCell.nodeId,
colKey: nextColumn.getColId(),
})
);
break;
}
case "left": {
let nextColumn;
if (params.end === true) {
const columns = columnApi.getAllGridColumns();
nextColumn = columns[1];
} else {
const column = columnApi.getColumn(selectedCell.colKey);
if (column == null) {
log.warn("Could not find column");
return;
}
nextColumn = columnApi.getDisplayedColBefore(column);
}
if (nextColumn == null) {
return;
}
dispatch(
selectCell({
nodeId: selectedCell.nodeId,
colKey: nextColumn.getColId(),
})
);
break;
}
default: {
assertNever(params.direction);
}
}
Lastly #4, here is parts of my selectCell implementation that can give you some idea of how to make sure ag-grid focuses the right cell.
This code will focus the cell, add a range highlight, add a row selection, and make sure that it is visible if the viewport needs to jump:
// no difference in logic when using Home/End!
// just make sure correct cell is passed
gridApi.ensureNodeVisible(node);
gridApi.ensureColumnVisible(params.colKey);
gridApi.clearRangeSelection();
gridApi.addCellRange({
rowStartIndex: rowIndex,
rowEndIndex: rowIndex,
columns: [params.colKey],
});
gridApi.setFocusedCell(rowIndex, params.colKey);
node.setSelected(true, true)