Drag and Drop Image Files into a FlexGrid Cell in React
Background
Wijmo FlexGrid does not include a built-in setting that automatically inserts files dragged from File Explorer into a cell. However, in React applications, you can handle the browser’s dragover and drop events on the FlexGrid host element.
By using FlexGrid’s hitTest method, you can determine which cell the user is dragging over, allow drops only in the Image column, read the dropped image with FileReader, and store the result in the grid’s data source. The grid data can then be sent to the server.
Steps to Complete
- Install the required Wijmo React packages.
- Import the FlexGrid and CellMaker modules.
- Create sample data with an
imagefield. - Add helper functions for image drag-and-drop.
- Configure the image column and drop behavior.
- Add grid styling.
Getting Started
Install the required Wijmo React packages
npm install @mescius/wijmo.react.grid @mescius/wijmo.grid.cellmaker @mescius/wijmo.styles
- The
wijmo.react.gridpackage provides the React FlexGrid components. - The
wijmo.grid.cellmakerpackage providesCellMaker.makeImage, which displays image URLs or data URLs in grid cells.
Import the FlexGrid and CellMaker modules
import { useCallback, useMemo, useState } from 'react';
import '@mescius/wijmo.styles/wijmo.css';
import { FlexGrid, FlexGridColumn } from '@mescius/wijmo.react.grid';
import { CellMaker } from '@mescius/wijmo.grid.cellmaker';
import './App.css';
- These imports allow the FlexGrid and its columns to be declared directly in JSX.
- The Wijmo CSS file is required for the grid to render correctly.
Create sample data with an image field
function getSampleData(count) {
const countries = 'US,Germany,UK,Japan,Italy,Greece'.split(',');
const products = 'Phones,Computers,Cameras,Stereos'.split(',');
const data = [];
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 as a data URL. - Because the value is stored in the row data, it can be included when saving the grid to the server.
Add helper functions for image drag-and-drop
function isImageFile(file) {
return file && (
file.type.startsWith('image/') ||
/\.(png|gif|bmp|jpe?g|webp)$/i.test(file.name)
);
}
function hasImageItem(dataTransfer) {
const items = dataTransfer?.items;
if (!items || !items.length) {
return false;
}
return Array.from(items).some(item => {
return item.kind === 'file' && item.type.startsWith('image/');
});
}
function getImageCellHitTest(grid, event) {
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;
}
getImageCellHitTestmakes sure the user is dropping into a regular grid cell in theimagecolumn.isImageFilevalidates the dropped file before it is read.
Configure the image column and drop behavior
export default function App() {
const [data] = useState(() => getSampleData(50));
const imageCellTemplate = useMemo(() => {
return CellMaker.makeImage({
label: 'image not available'
});
}, []);
const initGrid = useCallback(grid => {
grid.allowResizing = 'Both';
grid.rows.defaultSize = 72;
if (grid.hostElement.dataset.imageDropInitialized === 'true') {
return;
}
grid.hostElement.dataset.imageDropInitialized = 'true';
grid.hostElement.addEventListener('dragover', e => {
const ht = getImageCellHitTest(grid, e);
e.preventDefault();
if (!ht || !hasImageItem(e.dataTransfer)) {
e.dataTransfer.dropEffect = 'none';
return;
}
e.dataTransfer.dropEffect = 'copy';
});
grid.hostElement.addEventListener('drop', e => {
const ht = getImageCellHitTest(grid, e);
e.preventDefault();
if (!ht) {
return;
}
const file = Array.from(e.dataTransfer.files).find(isImageFile);
if (!file) {
return;
}
const reader = new FileReader();
reader.onload = args => {
grid.setCellData(ht.row, ht.col, args.target.result);
};
reader.readAsDataURL(file);
});
}, []);
const saveGrid = useCallback(async () => {
await fetch('/api/products', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
}, [data]);
return (
<>
<p>Drag an image from File Explorer into the Image column.</p>
<button type="button" onClick={saveGrid}>
Save Grid
</button>
<FlexGrid
initialized={initGrid}
itemsSource={data}
autoGenerateColumns={false}
>
<FlexGridColumn binding="id" header="ID" />
<FlexGridColumn binding="country" header="Country" />
<FlexGridColumn binding="product" header="Product" />
<FlexGridColumn
binding="image"
header="Image"
isReadOnly={true}
width={140}
cssClass="cell-img"
cellTemplate={imageCellTemplate}
/>
<FlexGridColumn binding="downloads" header="Downloads" />
<FlexGridColumn binding="sales" header="Sales" format="n2" />
<FlexGridColumn binding="expenses" header="Expenses" format="n2" />
</FlexGrid>
</>
);
}
- The
dragoverevent enables dropping only when the target cell is in theImagecolumn and the dragged item is an image. - The
dropevent reads the image file withFileReader.readAsDataURL. grid.setCellDatastores the data URL in the boundimagefield, andCellMaker.makeImagedisplays it in the cell.- The
saveGridfunction sends the grid’s current data to the server as JSON.
Add grid styling
.wj-flexgrid {
height: 500px;
}
.wj-flexgrid .cell-img img {
max-width: 100%;
max-height: 64px;
object-fit: contain;
}
- The grid height gives FlexGrid visible space on the page.
- The image styling keeps dropped images contained within the cell.
With this setup, users can drag image files from File Explorer into the Image column, preview the image in the FlexGrid cell, and save the updated grid data to the server.
For larger production images, consider uploading the original image file separately and storing the returned image URL in the grid instead of storing a Base64 data URL.
Happy coding!