Skip to content

Commit

Permalink
feat: new Drag to Recycle Bin demo (#1257)
Browse files Browse the repository at this point in the history
  • Loading branch information
ghiscoding authored Aug 24, 2024
1 parent 40f2c34 commit 640a93c
Show file tree
Hide file tree
Showing 6 changed files with 403 additions and 1 deletion.
2 changes: 1 addition & 1 deletion packages/demo/src/examples/slickgrid/example40.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ <h2>
</span>
</h2>

<h6 class="title is-6 italic content">
<h6 class="title italic content">
<ul>
<li>
Infinite scrolling allows the grid to lazy-load rows from the server when reaching the scroll bottom (end) position.
Expand Down
42 changes: 42 additions & 0 deletions packages/demo/src/examples/slickgrid/example41.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<div class="container-fluid">
<h2>
Example 41: Row Reordering & Drag to Recycle Bin
<span class="float-end">
<a style="font-size: 18px" target="_blank"
href="https://github.com/ghiscoding/aurelia-slickgrid/blob/master/packages/demo/src/examples/slickgrid/example41.ts">
<span class="mdi mdi-link-variant"></span> code
</a>
</span>
</h2>

<h6 class="subtitle italic">
<ul>
<li>Click to select, Ctrl-click to toggle selection(s).</li>
<li>Drag one or more rows by the handle icon to reorder.</li>
<li>Drag one or more rows by selection and drag to the recycle bin to delete.</li>
</ul>
</h6>

<div class="row">
<div class="col">
<aurelia-slickgrid grid-id="grid41"
column-definitions.bind="columnDefinitions"
grid-options.bind="gridOptions"
dataset.bind="dataset"
on-aurelia-grid-created.trigger="aureliaGridReady($event.detail)"
on-drag-init.trigger="handleOnDragInit($event.detail.eventData)"
on-drag-start.trigger="handleOnDragStart($event.detail.eventData)"
on-drag.trigger="handleOnDrag($event.detail.eventData, $event.detail.args)"
on-drag-end.trigger="handleOnDragEnd($event.detail.eventData, $event.detail.args)">
<div au-slot="slickgrid-header" class="grid-header">
<label>Santa's TODO list:</label>
</div>
</aurelia-slickgrid>
</div>
<div class="col">
<div id="dropzone" class="recycle-bin mt-4">
Recycle Bin
</div>
</div>
</div>
</div>
42 changes: 42 additions & 0 deletions packages/demo/src/examples/slickgrid/example41.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
.drag-message {
position: absolute;
display: inline-block;
padding: 4px 10px;
background: #e0e0e0;
border: 1px solid gray;
z-index: 99999;
border-radius: 8px;
box-shadow: 2px 2px 6px silver;
}

.grid-header {
display: flex;
align-items: center;
box-sizing: border-box;
font-weight: bold;
height: 35px;
padding-left: 8px;
width: 100%;
}

.recycle-bin {
background: transparent;
cursor: default;
width: 120px;
border: 2px solid #e4e4e4;
background: beige;
padding: 4px;
font-size: 12pt;
font-weight: bold;
color: black;
text-align: center;
border-radius: 10px;

&.drag-dropzone {
border: 2px dashed pink;
}
&.drag-hover {
background: pink;
cursor: crosshair;
}
}
223 changes: 223 additions & 0 deletions packages/demo/src/examples/slickgrid/example41.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
import {
type AureliaGridInstance,
type Column,
Formatters,
type GridOption,
SlickGlobalEditorLock,
} from 'aurelia-slickgrid';

import './example41.scss';

export class Example41 {
aureliaGrid!: AureliaGridInstance;
gridOptions!: GridOption;
columnDefinitions!: Column[];
dataset: any[] = [];
dragHelper?: HTMLElement;
dragRows: number[] = [];
dragMode = '';

constructor() {
this.defineGrid();

// mock a dataset
this.dataset = this.mockData();
}

aureliaGridReady(aureliaGrid: AureliaGridInstance) {
this.aureliaGrid = aureliaGrid;
}

/* Define grid Options and Columns */
defineGrid() {
this.columnDefinitions = [
{
id: 'name',
name: 'Name',
field: 'name',
width: 300,
cssClass: 'cell-title',
},
{
id: 'complete',
name: 'Complete',
width: 60,
cssClass: 'cell-effort-driven',
field: 'complete',
cannotTriggerInsert: true,
formatter: Formatters.checkmarkMaterial,
}
];

this.gridOptions = {
enableAutoResize: false,
gridHeight: 225,
gridWidth: 800,
rowHeight: 33,
enableCellNavigation: true,
enableRowSelection: true,
enableRowMoveManager: true,
rowSelectionOptions: {
// True (Single Selection), False (Multiple Selections)
selectActiveRow: false
},
rowMoveManager: {
columnIndexPosition: 0,
cancelEditOnDrag: true,
disableRowSelection: true,
hideRowMoveShadow: false,
onBeforeMoveRows: this.onBeforeMoveRows.bind(this),
onMoveRows: this.onMoveRows.bind(this),

// you can also override the usability of the rows, for example make every 2nd row the only moveable rows,
// usabilityOverride: (row, dataContext, grid) => dataContext.id % 2 === 1
},
};
}

mockData() {
return [
{ id: 0, name: 'Make a list', complete: true },
{ id: 1, name: 'Check it twice', complete: false },
{ id: 2, name: `Find out who's naughty`, complete: false },
{ id: 3, name: `Find out who's nice`, complete: false }
];
}

onBeforeMoveRows(e: MouseEvent | TouchEvent, data: { rows: number[]; insertBefore: number; }) {
for (const dataRow of data.rows) {
// no point in moving before or after itself
if (dataRow === data.insertBefore || dataRow === data.insertBefore - 1) {
e.stopPropagation();
return false;
}
}
return true;
}

onMoveRows(_e: MouseEvent | TouchEvent, args: { rows: number[]; insertBefore: number; }) {
const extractedRows: any[] = [];
const rows = args.rows;
const insertBefore = args.insertBefore;
const left = this.dataset.slice(0, insertBefore);
const right = this.dataset.slice(insertBefore, this.dataset.length);

rows.sort((a, b) => a - b);

for (const row of rows) {
extractedRows.push(this.dataset[row]);
}

rows.reverse();

for (const row of rows) {
if (row < insertBefore) {
left.splice(row, 1);
} else {
right.splice(row - insertBefore, 1);
}
}

this.dataset = left.concat(extractedRows.concat(right));

const selectedRows: number[] = [];
for (let i = 0; i < rows.length; i++) {
selectedRows.push(left.length + i);
}

this.aureliaGrid.slickGrid?.resetActiveCell();
this.aureliaGrid.slickGrid?.invalidate();
}

handleOnDragInit(e: CustomEvent) {
// prevent the grid from cancelling drag'n'drop by default
e.stopImmediatePropagation();
}

handleOnDragStart(e: CustomEvent) {
const cell = this.aureliaGrid.slickGrid?.getCellFromEvent(e);

if (!cell || cell.cell === 0) {
this.dragMode = '';
return;
}

const row = cell.row;
if (!this.dataset[row]) {
return;
}

if (SlickGlobalEditorLock.isActive()) {
return;
}

e.stopImmediatePropagation();
this.dragMode = 'recycle';

let selectedRows: number[] = this.aureliaGrid.slickGrid?.getSelectedRows() || [];

if (!selectedRows.length || selectedRows.findIndex(row => row === row) === -1) {
selectedRows = [row];
this.aureliaGrid.slickGrid?.setSelectedRows(selectedRows);
}

this.dragRows = selectedRows;
const dragCount = selectedRows.length;

const dragMsgElm = document.createElement('span');
dragMsgElm.className = 'drag-message';
dragMsgElm.textContent = `Drag to Recycle Bin to delete ${dragCount} selected row(s)`;
this.dragHelper = dragMsgElm;
document.body.appendChild(dragMsgElm);
document.querySelector<HTMLDivElement>('#dropzone')?.classList.add('drag-dropzone');

return dragMsgElm;
}

handleOnDrag(e: MouseEvent, args: any) {
if (this.dragMode !== 'recycle') {
return;
}
if (this.dragHelper instanceof HTMLElement) {
this.dragHelper.style.top = `${e.pageY + 5}px`;
this.dragHelper.style.left = `${e.pageX + 5}px`;
}

// add/remove pink background color when hovering recycle bin
const dropzoneElm = document.querySelector<HTMLDivElement>('#dropzone')!;
if (args.target instanceof HTMLElement && (args.target.id === 'dropzone' || args.target === dropzoneElm)) {
dropzoneElm.classList.add('drag-hover'); // OR: dd.target.style.background = 'pink';
} else {
dropzoneElm.classList.remove('drag-hover');
}
}

handleOnDragEnd(e: CustomEvent, args: any) {
if (this.dragMode != 'recycle') {
return;
}
this.dragHelper?.remove();
document.querySelector<HTMLDivElement>('#dropzone')?.classList.remove('drag-dropzone', 'drag-hover');

if (this.dragMode != 'recycle' || args.target.id !== 'dropzone') {
return;
}

// reaching here means that we'll remove the row that we started dragging from the dataset
const rowsToDelete = this.dragRows.sort().reverse();
for (const rowToDelete of rowsToDelete) {
this.dataset.splice(rowToDelete, 1);
}
this.aureliaGrid.slickGrid?.invalidate();
this.aureliaGrid.slickGrid?.setSelectedRows([]);
this.dataset = [...this.dataset];
}

requiredFieldValidator(value: any) {
if (value == null || value == undefined || !value.length) {
return { valid: false, msg: 'This is a required field' };
} else {
return { valid: true, msg: null };
}
}
}
1 change: 1 addition & 0 deletions packages/demo/src/my-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export class MyApp {
{ path: 'example38', component: () => import('./examples/slickgrid/example38'), title: '38- Infinite Scroll with OData' },
{ path: 'example39', component: () => import('./examples/slickgrid/example39'), title: '39- Infinite Scroll with GraphQL' },
{ path: 'example40', component: () => import('./examples/slickgrid/example40'), title: '40- Infinite Scroll from JSON data' },
{ path: 'example41', component: () => import('./examples/slickgrid/example41'), title: '41- Row Reordering & Drag to Recycle Bin' },
{ path: 'home', component: () => import('./home-page'), title: 'Home' },
];

Expand Down
Loading

0 comments on commit 640a93c

Please sign in to comment.