Skip to main content Skip to footer

Drag and Drop Image Files into a FlexGrid Cell in JavaScript

Background

Users can drag image files from File Explorer and drop them into a Wijmo FlexGrid cell. In vanilla JavaScript, this can be handled by adding dragover and drop event listeners to the FlexGrid host element.

The FlexGrid hitTest method identifies the cell under the cursor. If the target cell is in the Image column, the dropped image 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

  1. Install Wijmo JavaScript packages.
  2. Add the grid host element and Save button.
  3. Import Wijmo modules and styles.
  4. Create the FlexGrid data and Image column.
  5. Handle dragover and drop events.
  6. Save the updated grid data to the server.

Getting Started

Install Wijmo JavaScript packages

npm install @mescius/wijmo.grid @mescius/wijmo.grid.cellmaker @mescius/wijmo.styles
  • These packages provide the FlexGrid control, the CellMaker helper, and the Wijmo stylesheet.

 

Add the grid host element and Save button

<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8" />
  <title>Drag and Drop Images into FlexGrid</title>
</head>
<body>
  <p>Drag an image from File Explorer into the Image column.</p>

  <button id="saveGrid" type="button">Save Grid</button>

  <div id="grid"></div>

  <script type="module" src="/src/index.js"></script>
</body>
</html>
  • The div element is the FlexGrid host. The Save button sends the updated row data to the server.

 

Import Wijmo modules and styles

// src/index.js
import '@mescius/wijmo.styles/wijmo.css';
import './styles.css';

import { AllowResizing, FlexGrid } from '@mescius/wijmo.grid';
import { CellMaker } from '@mescius/wijmo.grid.cellmaker';
  • FlexGrid creates the grid instance, and CellMaker.makeImage displays image URLs or data URLs inside grid cells.
/* src/styles.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;
}
  • These styles give the grid visible height and keep dropped images contained within the cell.

 

Create the FlexGrid data and Image column

const data = getSampleData(50);

const grid = new FlexGrid('#grid', {
  itemsSource: data,
  autoGenerateColumns: false,
  columns: [
    { binding: 'id', header: 'ID' },
    { binding: 'country', header: 'Country' },
    { binding: 'product', header: 'Product' },
    {
      binding: 'image',
      header: 'Image',
      isReadOnly: true,
      width: 140,
      cssClass: 'cell-img',
      cellTemplate: CellMaker.makeImage({
        label: 'image not available'
      })
    },
    { binding: 'downloads', header: 'Downloads' },
    { binding: 'sales', header: 'Sales', format: 'n2' },
    { binding: 'expenses', header: 'Expenses', format: 'n2' }
  ]
});

grid.allowResizing = AllowResizing.Both;
grid.rows.defaultSize = 72;

function getSampleData(count) {
  const countries = 'US,Germany,UK,Japan,Italy,Greece'.split(',');
  const products = 'Phones,Computers,Cameras,Stereos'.split(',');
  const rows = [];

  for (let i = 0; i < count; i++) {
    rows.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 rows;
}
  • The image field stores the dropped image value. Since the Image column uses CellMaker.makeImage, the stored data URL is displayed as an image.

 

Handle dragover and drop events

grid.hostElement.addEventListener('dragover', e => {
  const ht = getImageCellHitTest(grid, e);

  e.preventDefault();

  if (!e.dataTransfer || !ht || !hasImageItem(e.dataTransfer)) {
    if (e.dataTransfer) {
      e.dataTransfer.dropEffect = 'none';
    }
    return;
  }

  e.dataTransfer.dropEffect = 'copy';
});

grid.hostElement.addEventListener('drop', e => {
  const ht = getImageCellHitTest(grid, e);

  e.preventDefault();

  if (!e.dataTransfer || !ht) {
    return;
  }

  const file = Array.from(e.dataTransfer.files).find(isImageFile);

  if (!file) {
    return;
  }

  const reader = new FileReader();

  reader.onload = () => {
    grid.setCellData(ht.row, ht.col, reader.result);
  };

  reader.readAsDataURL(file);
});

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;
}

function hasImageItem(dataTransfer) {
  return Array.from(dataTransfer.items || []).some(item => {
    return item.kind === 'file' && item.type.startsWith('image/');
  });
}

function isImageFile(file) {
  return file.type.startsWith('image/') || /\.(png|gif|bmp|jpe?g|webp)$/i.test(file.name);
}
  • The dragover handler allows dropping only when the target cell is in the Image column and the dragged item is an image. The drop handler reads the image file as a data URL and stores it in the bound grid cell.

 

Save the updated grid data to the server

document.getElementById('saveGrid').addEventListener('click', saveGrid);

async function saveGrid() {
  const response = await fetch('/api/products', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(data)
  });

  if (!response.ok) {
    throw new Error('Unable to save grid data.');
  }
}
  • Because the dropped image is stored in each row’s image field, the updated grid data can be sent to the server with the rest of the row values.

 

For large 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