Skip to content
This repository was archived by the owner on Jun 1, 2025. It is now read-only.

Commit ec99672

Browse files
authored
Merge pull request #1464 from ghiscoding/chore/drag-recycle
feat: new Drag to Recycle Bin demo
2 parents b393d90 + 93750d8 commit ec99672

File tree

7 files changed

+429
-0
lines changed

7 files changed

+429
-0
lines changed

src/app/app-routing.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { GridCompositeEditorComponent } from './examples/grid-composite-editor.c
1010
import { GridContextMenuComponent } from './examples/grid-contextmenu.component';
1111
import { GridCustomTooltipComponent } from './examples/grid-custom-tooltip.component';
1212
import { GridDraggableGroupingComponent } from './examples/grid-draggrouping.component';
13+
import { GridDragRecycleComponent } from './examples/grid-drag-recycle.component';
1314
import { GridEditorComponent } from './examples/grid-editor.component';
1415
import { GridFooterTotalsComponent } from './examples/grid-footer-totals.component';
1516
import { GridExcelFormulaComponent } from './examples/grid-excel-formula.component';
@@ -55,6 +56,7 @@ const routes: Routes = [
5556
{ path: 'composite-editor', component: GridCompositeEditorComponent },
5657
{ path: 'context', component: GridContextMenuComponent },
5758
{ path: 'custom-tooltip', component: GridCustomTooltipComponent },
59+
{ path: 'drag-recycle', component: GridDragRecycleComponent },
5860
{ path: 'editor', component: GridEditorComponent },
5961
{ path: 'excel-formula', component: GridExcelFormulaComponent },
6062
{ path: 'footer-totals', component: GridFooterTotalsComponent },

src/app/app.component.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,11 @@
191191
40- Infinite Scroll from JSON data
192192
</a>
193193
</li>
194+
<li class="nav-item">
195+
<a class="nav-link" routerLinkActive="active" [routerLink]="['/drag-recycle']">
196+
41- Row Reordering & Drag to Recycle Bin
197+
</a>
198+
</li>
194199
</ul>
195200
</section>
196201

src/app/app.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { GridContextMenuComponent } from './examples/grid-contextmenu.component'
2727
import { GridCompositeEditorComponent } from './examples/grid-composite-editor.component';
2828
import { GridCustomTooltipComponent } from './examples/grid-custom-tooltip.component';
2929
import { GridDraggableGroupingComponent } from './examples/grid-draggrouping.component';
30+
import { GridDragRecycleComponent } from './examples/grid-drag-recycle.component';
3031
import { GridEditorComponent } from './examples/grid-editor.component';
3132
import { GridExcelFormulaComponent } from './examples/grid-excel-formula.component';
3233
import { GridFooterTotalsComponent } from './examples/grid-footer-totals.component';
@@ -110,6 +111,7 @@ export function appInitializerFactory(translate: TranslateService, injector: Inj
110111
GridContextMenuComponent,
111112
GridCustomTooltipComponent,
112113
GridDraggableGroupingComponent,
114+
GridDragRecycleComponent,
113115
GridEditorComponent,
114116
GridExcelFormulaComponent,
115117
GridFooterTotalsComponent,
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<div class="container-fluid">
2+
<h2>
3+
Example 41: Row Reordering & Drag to Recycle Bin
4+
<span class="float-end">
5+
<a style="font-size: 18px"
6+
target="_blank"
7+
href="https://github.com/ghiscoding/Angular-Slickgrid/blob/master/src/app/examples/grid-drag-recycle.component.ts">
8+
<span class="mdi mdi-link-variant"></span> code
9+
</a>
10+
</span>
11+
</h2>
12+
13+
<h6 class="subtitle italic">
14+
<ul>
15+
<li>Click to select, Ctrl-click to toggle selection(s).</li>
16+
<li>Drag one or more rows by the handle icon to reorder.</li>
17+
<li>Drag one or more rows by selection and drag to the recycle bin to delete.</li>
18+
</ul>
19+
</h6>
20+
21+
<div class="row">
22+
<div class="col">
23+
<div class="grid41">
24+
<angular-slickgrid gridId="grid41"
25+
[columnDefinitions]="columnDefinitions"
26+
[gridOptions]="gridOptions"
27+
[dataset]="dataset"
28+
(onAngularGridCreated)="angularGridReady($event.detail)"
29+
(onDragInit)="handleOnDragInit($event.detail.eventData)"
30+
(onDragStart)="handleOnDragStart($event.detail.eventData)"
31+
(onDrag)="handleOnDrag($event.detail.eventData, $event.detail.args)"
32+
(onDragEnd)="handleOnDragEnd($event.detail.eventData, $event.detail.args)"
33+
>
34+
<ng-template #slickgridHeader>
35+
<div class="grid-header">
36+
<label>Santa's TODO list:</label>
37+
</div>
38+
</ng-template>
39+
</angular-slickgrid>
40+
</div>
41+
</div>
42+
43+
<div class="col">
44+
<div id="dropzone" class="recycle-bin mt-4">
45+
Recycle Bin
46+
</div>
47+
</div>
48+
</div>
49+
</div>
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
.drag-message {
2+
position: absolute;
3+
display: inline-block;
4+
padding: 4px 10px;
5+
background: #e0e0e0;
6+
border: 1px solid gray;
7+
z-index: 99999;
8+
border-radius: 8px;
9+
box-shadow: 2px 2px 6px silver;
10+
}
11+
12+
.grid-header {
13+
display: flex;
14+
align-items: center;
15+
box-sizing: border-box;
16+
font-weight: bold;
17+
height: 35px;
18+
padding-left: 8px;
19+
width: 100%;
20+
}
21+
22+
.recycle-bin {
23+
background: transparent;
24+
cursor: default;
25+
width: 120px;
26+
border: 2px solid #e4e4e4;
27+
background: beige;
28+
padding: 4px;
29+
font-size: 12pt;
30+
font-weight: bold;
31+
color: black;
32+
text-align: center;
33+
border-radius: 10px;
34+
35+
&.drag-dropzone {
36+
border: 2px dashed pink;
37+
}
38+
&.drag-hover {
39+
background: pink;
40+
cursor: crosshair;
41+
}
42+
}
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
import { Component, OnInit, ViewEncapsulation, } from '@angular/core';
2+
import {
3+
AngularGridInstance,
4+
Column,
5+
Editors,
6+
Formatters,
7+
GridOption,
8+
SlickGlobalEditorLock,
9+
} from '../modules/angular-slickgrid';
10+
11+
@Component({
12+
templateUrl: './grid-drag-recycle.component.html',
13+
styleUrls: ['./grid-drag-recycle.component.scss'],
14+
encapsulation: ViewEncapsulation.None,
15+
})
16+
export class GridDragRecycleComponent implements OnInit {
17+
angularGrid!: AngularGridInstance;
18+
gridOptions!: GridOption;
19+
columnDefinitions!: Column[];
20+
dataset!: any[];
21+
dragHelper?: HTMLElement;
22+
dragRows: number[] = [];
23+
dragMode = '';
24+
25+
ngOnInit(): void {
26+
this.defineGrids();
27+
28+
// mock a dataset
29+
this.dataset = this.mockData();
30+
}
31+
32+
angularGridReady(angularGrid: AngularGridInstance) {
33+
this.angularGrid = angularGrid;
34+
}
35+
36+
isBrowserDarkModeEnabled() {
37+
return window.matchMedia?.('(prefers-color-scheme: dark)').matches ?? false;
38+
}
39+
40+
/* Define grid Options and Columns */
41+
defineGrids() {
42+
this.columnDefinitions = [
43+
{
44+
id: 'name',
45+
name: 'Name',
46+
field: 'name',
47+
width: 300,
48+
cssClass: 'cell-title',
49+
editor: { model: Editors.Text, },
50+
validator: this.requiredFieldValidator
51+
},
52+
{
53+
id: 'complete',
54+
name: 'Complete',
55+
width: 60,
56+
cssClass: 'cell-effort-driven',
57+
field: 'complete',
58+
cannotTriggerInsert: true,
59+
formatter: Formatters.checkmarkMaterial,
60+
editor: { model: Editors.Checkbox },
61+
}
62+
];
63+
64+
this.gridOptions = {
65+
enableAutoResize: false,
66+
gridHeight: 225,
67+
gridWidth: 800,
68+
rowHeight: 33,
69+
enableCellNavigation: true,
70+
enableRowSelection: true,
71+
enableRowMoveManager: true,
72+
rowSelectionOptions: {
73+
// True (Single Selection), False (Multiple Selections)
74+
selectActiveRow: false
75+
},
76+
rowMoveManager: {
77+
columnIndexPosition: 0,
78+
cancelEditOnDrag: true,
79+
disableRowSelection: true,
80+
hideRowMoveShadow: false,
81+
onBeforeMoveRows: this.onBeforeMoveRows.bind(this),
82+
onMoveRows: this.onMoveRows.bind(this),
83+
84+
// you can also override the usability of the rows, for example make every 2nd row the only moveable rows,
85+
// usabilityOverride: (row, dataContext, grid) => dataContext.id % 2 === 1
86+
},
87+
};
88+
}
89+
90+
mockData() {
91+
return [
92+
{ id: 0, name: 'Make a list', complete: true },
93+
{ id: 1, name: 'Check it twice', complete: false },
94+
{ id: 2, name: `Find out who's naughty`, complete: false },
95+
{ id: 3, name: `Find out who's nice`, complete: false }
96+
];
97+
}
98+
99+
onBeforeMoveRows(e: MouseEvent | TouchEvent, data: { rows: number[]; insertBefore: number; }) {
100+
for (const dataRow of data.rows) {
101+
// no point in moving before or after itself
102+
if (dataRow === data.insertBefore || dataRow === data.insertBefore - 1) {
103+
e.stopPropagation();
104+
return false;
105+
}
106+
}
107+
return true;
108+
}
109+
110+
onMoveRows(_e: MouseEvent | TouchEvent, args: { rows: number[]; insertBefore: number; }) {
111+
const extractedRows: any[] = [];
112+
const rows = args.rows;
113+
const insertBefore = args.insertBefore;
114+
const left = this.dataset.slice(0, insertBefore);
115+
const right = this.dataset.slice(insertBefore, this.dataset.length);
116+
117+
rows.sort((a, b) => a - b);
118+
119+
for (const row of rows) {
120+
extractedRows.push(this.dataset[row]);
121+
}
122+
123+
rows.reverse();
124+
125+
for (const row of rows) {
126+
if (row < insertBefore) {
127+
left.splice(row, 1);
128+
} else {
129+
right.splice(row - insertBefore, 1);
130+
}
131+
}
132+
133+
this.dataset = left.concat(extractedRows.concat(right));
134+
135+
const selectedRows: number[] = [];
136+
for (let i = 0; i < rows.length; i++) {
137+
selectedRows.push(left.length + i);
138+
}
139+
140+
this.angularGrid.slickGrid?.resetActiveCell();
141+
this.dataset = [...this.dataset];
142+
}
143+
144+
handleOnDragInit(e: CustomEvent) {
145+
// prevent the grid from cancelling drag'n'drop by default
146+
e.stopImmediatePropagation();
147+
}
148+
149+
handleOnDragStart(e: CustomEvent) {
150+
const cell = this.angularGrid.slickGrid?.getCellFromEvent(e);
151+
152+
if (!cell || cell.cell === 0) {
153+
this.dragMode = '';
154+
return;
155+
}
156+
157+
const row = cell.row;
158+
if (!this.dataset[row]) {
159+
return;
160+
}
161+
162+
if (SlickGlobalEditorLock.isActive()) {
163+
return;
164+
}
165+
166+
e.stopImmediatePropagation();
167+
this.dragMode = 'recycle';
168+
169+
let selectedRows: number[] = this.angularGrid.slickGrid?.getSelectedRows() || [];
170+
171+
if (!selectedRows.length || selectedRows.findIndex(row => row === row) === -1) {
172+
selectedRows = [row];
173+
this.angularGrid.slickGrid?.setSelectedRows(selectedRows);
174+
}
175+
176+
this.dragRows = selectedRows;
177+
const dragCount = selectedRows.length;
178+
179+
const dragMsgElm = document.createElement('span');
180+
dragMsgElm.className = 'drag-message';
181+
dragMsgElm.textContent = `Drag to Recycle Bin to delete ${dragCount} selected row(s)`;
182+
this.dragHelper = dragMsgElm;
183+
document.body.appendChild(dragMsgElm);
184+
document.querySelector<HTMLDivElement>('#dropzone')?.classList.add('drag-dropzone');
185+
186+
return dragMsgElm;
187+
}
188+
189+
handleOnDrag(e: MouseEvent, args: any) {
190+
if (this.dragMode !== 'recycle') {
191+
return;
192+
}
193+
if (this.dragHelper instanceof HTMLElement) {
194+
this.dragHelper.style.top = `${e.pageY + 5}px`;
195+
this.dragHelper.style.left = `${e.pageX + 5}px`;
196+
}
197+
198+
// add/remove pink background color when hovering recycle bin
199+
const dropzoneElm = document.querySelector<HTMLDivElement>('#dropzone')!;
200+
if (args.target instanceof HTMLElement && (args.target.id === 'dropzone' || args.target === dropzoneElm)) {
201+
dropzoneElm.classList.add('drag-hover'); // OR: dd.target.style.background = 'pink';
202+
} else {
203+
dropzoneElm.classList.remove('drag-hover');
204+
}
205+
}
206+
207+
handleOnDragEnd(e: CustomEvent, args: any) {
208+
if (this.dragMode != 'recycle') {
209+
return;
210+
}
211+
this.dragHelper?.remove();
212+
document.querySelector<HTMLDivElement>('#dropzone')?.classList.remove('drag-dropzone', 'drag-hover');
213+
214+
if (this.dragMode != 'recycle' || args.target.id !== 'dropzone') {
215+
return;
216+
}
217+
218+
// reaching here means that we'll remove the row that we started dragging from the dataset
219+
const rowsToDelete = this.dragRows.sort().reverse();
220+
for (const rowToDelete of rowsToDelete) {
221+
this.dataset.splice(rowToDelete, 1);
222+
}
223+
this.angularGrid.slickGrid?.invalidate();
224+
this.angularGrid.slickGrid?.setSelectedRows([]);
225+
this.dataset = [...this.dataset];
226+
}
227+
228+
requiredFieldValidator(value: any) {
229+
if (value == null || value == undefined || !value.length) {
230+
return { valid: false, msg: 'This is a required field' };
231+
} else {
232+
return { valid: true, msg: null };
233+
}
234+
}
235+
}

0 commit comments

Comments
 (0)