Skip to main content Skip to footer

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

  1. Install the required Wijmo React packages.
  2. Import the FlexGrid and CellMaker modules.
  3. Create sample data with an image field.
  4. Add helper functions for image drag-and-drop.
  5. Configure the image column and drop behavior.
  6. 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.grid package provides the React FlexGrid components.
  • The wijmo.grid.cellmaker package provides CellMaker.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 image field 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;
}
  • getImageCellHitTest makes sure the user is dropping into a regular grid cell in the image column.
  • isImageFile validates 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 dragover event enables dropping only when the target cell is in the Image column and the dragged item is an image.
  • The drop event reads the image file with FileReader.readAsDataURL.
  • grid.setCellData stores the data URL in the bound image field, and CellMaker.makeImage displays it in the cell.
  • The saveGrid function 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!

Andrew Peterson

Technical Engagement Engineer