The Ignite UI for Web Components Row Dragging feature in Web Components Tree Grid is easily configurable and is used for rearranging rows within the grid by dragging and dropping them to a new position using the mouse. It is initialized on the root IgcTreeGridComponent component and is configurable via the RowDraggable input.
import { IgcPropertyEditorPanelModule } from'igniteui-webcomponents-layouts';
import'igniteui-webcomponents-grids/grids/combined';
import { ComponentRenderer, PropertyEditorPanelDescriptionModule, WebTreeGridDescriptionModule, WebPaginatorDescriptionModule } from'igniteui-webcomponents-core';
import { IgcTreeGridComponent, IgcGridToolbarTitleComponent } from'igniteui-webcomponents-grids/grids';
import { EmployeesNestedDataItem, EmployeesNestedDataItem_EmployeesItem, EmployeesNestedData } from'./EmployeesNestedData';
import"igniteui-webcomponents-grids/grids/themes/light/bootstrap.css";
import { ModuleManager } from'igniteui-webcomponents-core';
import"./index.css";
ModuleManager.register(
IgcPropertyEditorPanelModule
);
exportclassSample{
private treeGrid: IgcTreeGridComponent
private treeGrid2: IgcTreeGridComponent
private employees: IgcGridToolbarTitleComponent
private employeesData:EmployeesNestedData;
private _bind: () =>void;
constructor() {
var treeGrid = this.treeGrid = document.getElementById('treeGrid') as IgcTreeGridComponent;
var treeGrid2 = this.treeGrid2 = document.getElementById('treeGrid2') as IgcTreeGridComponent;
var employees = this.employees = document.getElementById('Employees') as IgcGridToolbarTitleComponent;
var employeesData = new EmployeesNestedData();
this._bind = () => {
treeGrid.data = this.employeesNestedData;
treeGrid2.data = [];
treeGrid2.emptyGridMessage = "Drag and Drop a row from the left grid to this grid";
treeGrid.addEventListener("rowDragEnd", this.onGridRowDragEnd.bind(this));
}
this._bind();
}
publicaddRowAndChildren(row:EmployeesNestedDataItem, newData:any[]) {
if(newData.includes(row)){
return;
}
elseif(newData.length>0 && row.Employees){
for(let i= row.Employees.length;i>=0;i--){
if(newData.includes(row.Employees[i])){
let index = newData.findIndex(element => element.ID === row.Employees[i].ID);
if (index > -1) {
newData.splice(index, 1);
}
}
}
}
for (let record of newData) {
if (record.Employees && record.Employees.includes(row)) {
return;
}
}
newData.push(row);
}
public onGridRowDragEnd(args: any): void {
const ghostElement = args.detail.dragDirective.ghostElement;
if (ghostElement != null) {
const dragElementPos = ghostElement.getBoundingClientRect();
const gridPosition = this.treeGrid2.getBoundingClientRect();
const withinXBounds = dragElementPos.x >= gridPosition.x && dragElementPos.x <= gridPosition.x + gridPosition.width;
const withinYBounds = dragElementPos.y >= gridPosition.y && dragElementPos.y <= gridPosition.y + gridPosition.height;
if (withinXBounds && withinYBounds) {
const newData = [...this.treeGrid2.data];
const draggedRowData = args.detail.dragData.data;
this.addRowAndChildren(draggedRowData, newData);
this.treeGrid2.data = newData;
}
}
}
private _employeesNestedData: EmployeesNestedData = null;
publicgetemployeesNestedData(): EmployeesNestedData {
if (this._employeesNestedData == null)
{
this._employeesNestedData = new EmployeesNestedData();
}
returnthis._employeesNestedData;
}
private _componentRenderer: ComponentRenderer = null;
publicgetrenderer(): ComponentRenderer {
if (this._componentRenderer == null) {
this._componentRenderer = new ComponentRenderer();
var context = this._componentRenderer.context;
PropertyEditorPanelDescriptionModule.register(context);
WebTreeGridDescriptionModule.register(context);
WebPaginatorDescriptionModule.register(context);
}
returnthis._componentRenderer;
}
}
new Sample();
ts
<!DOCTYPE html><html><head><title>Sample | Ignite UI | Web Components | infragistics</title><metacharset="UTF-8" /><linkrel="shortcut icon"href="https://static.infragistics.com/xplatform/images/browsers/wc.png" ><linkrel="stylesheet"href="https://fonts.googleapis.com/icon?family=Material+Icons" /><linkrel="stylesheet"href="https://fonts.googleapis.com/css?family=Kanit&display=swap" /><linkrel="stylesheet"href="https://fonts.googleapis.com/css?family=Titillium Web" /><linkrel="stylesheet"href="https://static.infragistics.com/xplatform/css/samples/shared.v8.css" /><linkrel="stylesheet"href="/src/index.css"type="text/css" /></head><body><divid="root"><divclass="container sample ig-typography"><divclass="container horizontal"><divclass="container vertical"style="padding: 0.5rem;"><igc-tree-gridauto-generate="false"name="treeGrid"id="treeGrid"child-data-key="Employees"row-draggable="true"
><igc-columnfield="Name"header="Name"data-type="string"sortable="true"editable="true"resizable="true"
></igc-column><igc-columnfield="HireDate"header="Hire Date"data-type="date"sortable="true"editable="true"resizable="true"></igc-column><igc-columnfield="Age"header="Age"data-type="number"sortable="true"editable="true"resizable="true"></igc-column></igc-tree-grid></div><divclass="container vertical"style="padding: 0.5rem;"><igc-tree-gridauto-generate="false"name="treeGrid2"id="treeGrid2"child-data-key="Employees"
><igc-columnfield="Name"header="Name"data-type="string"sortable="true"editable="true"resizable="true"
></igc-column><igc-columnfield="HireDate"header="Hire Date"data-type="date"sortable="true"editable="true"resizable="true"></igc-column><igc-columnfield="Age"header="Age"data-type="number"sortable="true"editable="true"resizable="true"></igc-column></igc-tree-grid></div></div></div></div><!-- This script is needed only for parcel and it will be excluded for webpack -->
<% if (false) { %><scriptsrc="src/index.ts"></script><% } %>
</body></html>html
/* shared styles are loaded from: *//* https://static.infragistics.com/xplatform/css/samples */css
Configuration
In order to enable row-dragging for your IgcTreeGridComponent, all you need to do is set the grid's RowDraggable to true. Once this is enabled, a row-drag handle will be displayed on each row. This handle can be used to initiate row dragging. Clicking on the drag-handle and moving the cursor while holding down the button will cause the grid's RowDragStart event to fire. Releasing the click at any time will cause RowDragEnd event to fire.
The drag handle icon can be templated using the grid's DragIndicatorIconTemplate. In the example we're building, let's change the icon from the default one (drag_indicator) to drag_handle.
constructor() {
var tGrid = this.tGrid = document.getElementById('tGrid') as IgcTreeGridComponent;
tGrid.addEventListener("rowDragStart", this.webTreeGridReorderRowStartHandler);
tGrid.addEventListener("rowDragEnd", this.webTreeGridReorderRowHandler);
}
ts
Make sure that there is a PrimaryKey specified for the grid! The logic needs an unique identifier for the rows so they can be properly reordered.
Once RowDraggable is enabled and a drop zone has been defined, you need to implement a simple handler for the drop event. When a row is dragged, check the following:
Is the row expanded? If so, collapse it.
Was the row dropped inside of the grid?
If so, on which other row was the dragged row dropped?
Once you've found the target row, swap the records' places in the data array
Was the row initially selected? If so, mark it as selected.
Below, you can see this implemented:
publicwebTreeGridReorderRowStartHandler(args: CustomEvent<IgcRowDragStartEventArgs){
const draggedRow = args.detail.dragElement;
const grid = this.treeGrid;
const row = grid.getRowByIndex(draggedRow.getAttribute('data-rowindex'));
if(row.expanded){
row.expanded = false;
}
}
public webTreeGridReorderRowHandler(args: CustomEvent<IgcRowDragEndEventArgs>): void {
const ghostElement = args.detail.dragDirective.ghostElement;
const dragElementPos = ghostElement.getBoundingClientRect();
const grid = this.treeGrid;
const rows = Array.prototype.slice.call(document.getElementsByTagName("igx-tree-grid-row"));
const currRowIndex = this.getCurrentRowIndex(rows,
{ x: dragElementPos.x, y: dragElementPos.y });
if (currRowIndex === -1) { return; }
const draggedRow = args.detail.dragData.data;
const childRows = this.findChildRows(grid.data, draggedRow);
//remove the row that was dragged and place it onto its new location
grid.deleteRow(args.detail.dragData.key);
grid.data.splice(currRowIndex, 0, args.detail.dragData.data);
// reinsert the child rows
childRows.reverse().forEach(childRow => {
grid.data.splice(currRowIndex + 1, 0, childRow);
});
}
private findChildRows(rows: any[], parent: any): any[] {
const childRows: any[] = [];
rows.forEach(row => {
if (row.ParentID === parent.ID) {
childRows.push(row);
// Recursively find children of current rowconst grandchildren = this.findChildRows(rows, row);
childRows.push(...grandchildren);
}
});
return childRows;
}
publicgetCurrentRowIndex(rowList: any[], cursorPosition: any) {
for (const row of rowList) {
const rowRect = row.getBoundingClientRect();
if (cursorPosition.y > rowRect.top + window.scrollY && cursorPosition.y < rowRect.bottom + window.scrollY &&
cursorPosition.x > rowRect.left + window.scrollX && cursorPosition.x < rowRect.right + window.scrollX) {
// return the index of the targeted rowreturnparseInt(row.attributes["data-rowindex"].value);
}
}
return -1;
}
ts
With these few easy steps, you've configured a grid that allows reordering rows via drag/drop! You can see the above code in action in the following demo.
Notice that we also have row selection enabled and we preserve the selection when dropping the dragged row.
import'igniteui-webcomponents-grids/grids/combined';
import { ComponentRenderer, WebTreeGridDescriptionModule } from'igniteui-webcomponents-core';
import { IgcTreeGridComponent } from'igniteui-webcomponents-grids/grids';
import { EmployeesNestedTreeDataItem, EmployeesNestedTreeData } from'./EmployeesNestedTreeData';
import { IgcRowDragStartEventArgs, IgcRowDragEndEventArgs } from'igniteui-webcomponents-grids/grids';
import"igniteui-webcomponents-grids/grids/themes/light/bootstrap.css";
import"./index.css";
exportclassSample{
private treeGrid: IgcTreeGridComponent
private _bind: () =>void;
constructor() {
var treeGrid = this.treeGrid = document.getElementById('treeGrid') as IgcTreeGridComponent;
this.webTreeGridReorderRowStartHandler = this.webTreeGridReorderRowStartHandler.bind(this);
this.webTreeGridReorderRowHandler = this.webTreeGridReorderRowHandler.bind(this);
this._bind = () => {
treeGrid.data = this.employeesNestedTreeData;
treeGrid.addEventListener("rowDragStart", this.webTreeGridReorderRowStartHandler);
treeGrid.addEventListener("rowDragEnd", this.webTreeGridReorderRowHandler);
}
this._bind();
}
private _employeesNestedTreeData: EmployeesNestedTreeData = null;
publicgetemployeesNestedTreeData(): EmployeesNestedTreeData {
if (this._employeesNestedTreeData == null)
{
this._employeesNestedTreeData = new EmployeesNestedTreeData();
}
returnthis._employeesNestedTreeData;
}
private _componentRenderer: ComponentRenderer = null;
publicgetrenderer(): ComponentRenderer {
if (this._componentRenderer == null) {
this._componentRenderer = new ComponentRenderer();
var context = this._componentRenderer.context;
WebTreeGridDescriptionModule.register(context);
}
returnthis._componentRenderer;
}
publicwebTreeGridReorderRowStartHandler(args: CustomEvent<IgcRowDragStartEventArgs>){
const draggedRow = args.detail.dragData;
if(draggedRow.expanded){
draggedRow.expanded = false;
}
}
public webTreeGridReorderRowHandler(args: CustomEvent<IgcRowDragEndEventArgs>): void {
const ghostElement = args.detail.dragDirective.ghostElement;
const dragElementPos = ghostElement.getBoundingClientRect();
const grid = this.treeGrid;
const rows = Array.prototype.slice.call(document.getElementsByTagName("igx-tree-grid-row"));
const currRowIndex = this.getCurrentRowIndex(rows,
{ x: dragElementPos.x, y: dragElementPos.y });
if (currRowIndex === -1) { return; }
const draggedRow = args.detail.dragData.data;
const childRows = this.findChildRows(grid.data, draggedRow);
//remove the row that was dragged and place it onto its new location
grid.deleteRow(args.detail.dragData.key);
grid.data.splice(currRowIndex, 0, args.detail.dragData.data);
// reinsert the child rows
childRows.reverse().forEach(childRow => {
grid.data.splice(currRowIndex + 1, 0, childRow);
});
}
private findChildRows(rows: any[], parent: any): any[] {
const childRows: any[] = [];
rows.forEach(row => {
if (row.ParentID === parent.ID) {
childRows.push(row);
// Recursively find children of current rowconst grandchildren = this.findChildRows(rows, row);
childRows.push(...grandchildren);
}
});
return childRows;
}
publicgetCurrentRowIndex(rowList: any[], cursorPosition: any) {
for (const row of rowList) {
const rowRect = row.getBoundingClientRect();
if (cursorPosition.y > rowRect.top + window.scrollY && cursorPosition.y < rowRect.bottom + window.scrollY &&
cursorPosition.x > rowRect.left + window.scrollX && cursorPosition.x < rowRect.right + window.scrollX) {
// return the index of the targeted rowreturnparseInt(row.attributes["data-rowindex"].value);
}
}
return -1;
}
}
new Sample();
ts
<!DOCTYPE html><html><head><title>Sample | Ignite UI | Web Components | infragistics</title><metacharset="UTF-8" /><linkrel="shortcut icon"href="https://static.infragistics.com/xplatform/images/browsers/wc.png" ><linkrel="stylesheet"href="https://fonts.googleapis.com/icon?family=Material+Icons" /><linkrel="stylesheet"href="https://fonts.googleapis.com/css?family=Kanit&display=swap" /><linkrel="stylesheet"href="https://fonts.googleapis.com/css?family=Titillium Web" /><linkrel="stylesheet"href="https://static.infragistics.com/xplatform/css/samples/shared.v8.css" /><linkrel="stylesheet"href="/src/index.css"type="text/css" /></head><body><divid="root"><divclass="container sample ig-typography"><divclass="container fill"><igc-tree-gridauto-generate="false"name="treeGrid"id="treeGrid"id="treeGrid"primary-key="ID"foreign-key="ParentID"row-draggable="true"><igc-columnfield="Name"header="Full Name"data-type="string"resizable="true"sortable="true"filterable="true"editable="true"></igc-column><igc-columnfield="Age"data-type="number"resizable="false"sortable="false"filterable="false"editable="true"></igc-column><igc-columnfield="Title"data-type="string"resizable="true"sortable="true"filterable="true"editable="true"></igc-column><igc-columnfield="HireDate"header="Hire Date"data-type="date"resizable="true"sortable="true"filterable="true"editable="true"></igc-column></igc-tree-grid></div></div></div><!-- This script is needed only for parcel and it will be excluded for webpack -->
<% if (false) { %><scriptsrc="src/index.ts"></script><% } %>
</body></html>html
/* shared styles are loaded from: *//* https://static.infragistics.com/xplatform/css/samples */css
Limitations
Currently, there are no known limitations for the RowDraggable.