Drag and Drop Image Files into a FlexGrid Cell in Angular
Background
Wijmo FlexGrid does not include a built-in setting that automatically inserts files dragged from File Explorer into a cell. However, in Angular applications, you can handle the browser’s dragover and drop events on the FlexGrid host element.
The FlexGrid hitTest method identifies the cell being targeted. If the user drops an image into the Image column, the file can be read with FileReader, stored in the row data as a data URL, displayed with CellMaker.makeImage, and later sent to the server.
Steps to Complete
- Install Wijmo Angular packages.
- Import Wijmo modules and styles.
- Create the FlexGrid data and image template.
- Add the FlexGrid markup and Image column.
- Handle dragover and drop events.
- Save the updated grid data to the server.
Getting Started
Install Wijmo Angular packages
npm install @mescius/wijmo.angular2.grid @mescius/wijmo.grid @mescius/wijmo.grid.cellmaker @mescius/wijmo.styles
- These packages provide the Angular FlexGrid wrapper, the core grid APIs, the
CellMakerhelper, and the Wijmo stylesheet.
Import Wijmo modules and styles
// src/app.component.ts
import { Component } from '@angular/core';
import { WjGridModule } from '@mescius/wijmo.angular2.grid';
import { AllowResizing, FlexGrid, HitTestInfo } from '@mescius/wijmo.grid';
import { CellMaker } from '@mescius/wijmo.grid.cellmaker';
interface ProductRow {
id: number;
country: string;
product: string;
image: string | null;
downloads: number;
sales: number;
expenses: number;
}
@Component({
selector: 'app-root',
standalone: true,
imports: [WjGridModule],
templateUrl: './app.component.html'
})
export class AppComponent {
// Code from the following steps goes here.
}
/* src/styles.css */
@import '@mescius/wijmo.styles/wijmo.css';
body {
font-family: Arial, sans-serif;
}
.wj-flexgrid {
height: 500px;
}
.wj-flexgrid .cell-img img {
max-width: 100%;
max-height: 64px;
object-fit: contain;
}
- The global stylesheet is used so the styles apply to grid cells generated by Wijmo at runtime.
Create the FlexGrid data and image template
data: ProductRow[] = getSampleData(50);
imageCellTemplate = CellMaker.makeImage({
label: 'image not available'
});
initGrid(grid: FlexGrid): void {
grid.allowResizing = AllowResizing.Both;
grid.rows.defaultSize = 72;
}
function getSampleData(count: number): ProductRow[] {
const countries = 'US,Germany,UK,Japan,Italy,Greece'.split(',');
const products = 'Phones,Computers,Cameras,Stereos'.split(',');
const data: ProductRow[] = [];
for (let i = 0; i < count; i++) {
data.push({
id: i,
country: countries[i % countries.length],
product: products[i % products.length],
image: null,
downloads: Math.round(100 + Math.random() * 10000),
sales: Math.random() * 10000,
expenses: Math.random() * 5000
});
}
return data;
}
- The
imagefield stores the dropped image value.CellMaker.makeImagedisplays that value as an image in the grid cell.
Add the FlexGrid markup and Image column
<!-- src/app.component.html -->
<p>Drag an image from File Explorer into the Image column.</p>
<button type="button" (click)="saveGrid()">Save Grid</button>
<wj-flex-grid
#flex
[itemsSource]="data"
[autoGenerateColumns]="false"
(initialized)="initGrid(flex)"
(dragover)="onGridDragOver(flex, $event)"
(drop)="onGridDrop(flex, $event)"
>
<wj-flex-grid-column [binding]="'id'" [header]="'ID'"></wj-flex-grid-column>
<wj-flex-grid-column [binding]="'country'" [header]="'Country'"></wj-flex-grid-column>
<wj-flex-grid-column [binding]="'product'" [header]="'Product'"></wj-flex-grid-column>
<wj-flex-grid-column
[binding]="'image'"
[header]="'Image'"
[isReadOnly]="true"
[width]="140"
[cssClass]="'cell-img'"
[cellTemplate]="imageCellTemplate"
></wj-flex-grid-column>
<wj-flex-grid-column [binding]="'downloads'" [header]="'Downloads'"></wj-flex-grid-column>
<wj-flex-grid-column [binding]="'sales'" [header]="'Sales'" [format]="'n2'"></wj-flex-grid-column>
<wj-flex-grid-column [binding]="'expenses'" [header]="'Expenses'" [format]="'n2'"></wj-flex-grid-column>
</wj-flex-grid>
- The grid listens for
dragoveranddropevents. The#flextemplate variable passes the FlexGrid instance into the Angular event handlers.
Handle dragover and drop events
onGridDragOver(grid: FlexGrid, event: DragEvent): void {
const ht = this.getImageCellHitTest(grid, event);
event.preventDefault();
if (!event.dataTransfer || !ht || !this.hasImageItem(event.dataTransfer)) {
event.dataTransfer?.dropEffect && (event.dataTransfer.dropEffect = 'none');
return;
}
event.dataTransfer.dropEffect = 'copy';
}
onGridDrop(grid: FlexGrid, event: DragEvent): void {
const ht = this.getImageCellHitTest(grid, event);
event.preventDefault();
if (!event.dataTransfer || !ht) {
return;
}
const file = Array.from(event.dataTransfer.files).find(file => this.isImageFile(file));
if (!file) {
return;
}
const reader = new FileReader();
reader.onload = () => {
grid.setCellData(ht.row, ht.col, reader.result as string);
};
reader.readAsDataURL(file);
}
private getImageCellHitTest(grid: FlexGrid, event: DragEvent): HitTestInfo | null {
const ht = grid.hitTest(event);
if (ht.panel !== grid.cells || ht.row < 0 || ht.col < 0) {
return null;
}
const column = ht.panel.columns[ht.col];
return column.binding === 'image' ? ht : null;
}
private hasImageItem(dataTransfer: DataTransfer): boolean {
return Array.from(dataTransfer.items || []).some(item => {
return item.kind === 'file' && item.type.startsWith('image/');
});
}
private isImageFile(file: File): boolean {
return file.type.startsWith('image/') || /\.(png|gif|bmp|jpe?g|webp)$/i.test(file.name);
}
- The
dragoverhandler allows the drop only when the target cell is in theImagecolumn and the dragged item is an image. Thedrophandler reads the file as a data URL and stores it in the bound grid cell.
Save the updated grid data to the server
async saveGrid(): Promise<void> {
const response = await fetch('/api/products', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(this.data)
});
if (!response.ok) {
throw new Error('Unable to save grid data.');
}
}
-
Because the dropped image is stored in the row’s
imagefield, the updated grid data can be sent to the server with the rest of the row values.
For large images, consider uploading the image file separately and storing the returned image URL in the grid instead of storing a Base64 data URL.
Happy coding!