Skip to main content Skip to footer

Drag and Drop Image Files into a FlexGrid Cell in Vue

Background

Wijmo FlexGrid does not include a built-in setting that automatically inserts files dragged from File Explorer into a cell. However, in Vue applications, you can handle the browser’s dragover and drop events on the FlexGrid host element.

The FlexGrid hitTest method identifies the target cell. If the drop target is in the Image column, the dropped image 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 Vue packages.
  2. Register the Wijmo Grid module and import styles.
  3. Create the FlexGrid data and image template.
  4. Initialize the grid and attach drag-and-drop handlers.
  5. Add the FlexGrid markup and Image column.
  6. Save the updated grid data to the server.

Getting Started

Install Wijmo Vue packages

npm install @mescius/wijmo.vue2.grid @mescius/wijmo.grid @mescius/wijmo.grid.cellmaker @mescius/wijmo.styles
  • These packages provide the Vue FlexGrid wrapper, the core grid APIs, the CellMaker helper, and the Wijmo stylesheet.

 

Register the Wijmo Grid module and import styles

// src/main.js
import { createApp } from 'vue';
import { registerGrid } from '@mescius/wijmo.vue2.grid';
import '@mescius/wijmo.styles/wijmo.css';

import App from './App.vue';

const app = createApp(App);

registerGrid(app);

app.mount('#app');
  • registerGrid(app) makes the wj-flex-grid and wj-flex-grid-column components available in Vue templates.
/* src/style.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;
}

 

Create the FlexGrid data and image template

<!-- src/App.vue -->
<script setup>
import { ref } from 'vue';
import * as wjGrid from '@mescius/wijmo.grid';
import { CellMaker } from '@mescius/wijmo.grid.cellmaker';

const data = ref(getSampleData(50));

const imageCellTemplate = CellMaker.makeImage({
  label: 'image not available'
});

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;
}
</script>
  • The image field stores the dropped image value. CellMaker.makeImage displays the stored data URL as an image in the grid cell.

 

Initialize the grid and attach drag-and-drop handlers

function initGrid(grid) {
  grid.allowResizing = wjGrid.AllowResizing.Both;
  grid.rows.defaultSize = 72;

  const host = grid.hostElement;

  if (host.dataset.imageDropInitialized === 'true') {
    return;
  }

  host.dataset.imageDropInitialized = 'true';

  host.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';
  });

  host.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 user is over the Image column and the dragged item is an image. The drop handler reads the file as a data URL and saves it into the bound grid cell.

 

Add the FlexGrid markup and Image column

<template>
  <p>Drag an image from File Explorer into the Image column.</p>

  <button type="button" @click="saveGrid">
    Save Grid
  </button>

  <wj-flex-grid
    :itemsSource="data"
    :autoGenerateColumns="false"
    :initialized="initGrid"
  >
    <wj-flex-grid-column binding="id" header="ID" />
    <wj-flex-grid-column binding="country" header="Country" />
    <wj-flex-grid-column binding="product" header="Product" />

    <wj-flex-grid-column
      binding="image"
      header="Image"
      :isReadOnly="true"
      :width="140"
      cssClass="cell-img"
      :cellTemplate="imageCellTemplate"
    />

    <wj-flex-grid-column binding="downloads" header="Downloads" />
    <wj-flex-grid-column binding="sales" header="Sales" format="n2" />
    <wj-flex-grid-column binding="expenses" header="Expenses" format="n2" />
  </wj-flex-grid>
</template>
  • The initialized callback provides the FlexGrid instance, where the drag-and-drop event listeners are attached.

 

Save the updated grid data to the server

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

  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