Skip to main content Skip to footer

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

  1. Install Wijmo Angular packages.
  2. Import Wijmo modules and styles.
  3. Create the FlexGrid data and image template.
  4. Add the FlexGrid markup and Image column.
  5. Handle dragover and drop events.
  6. 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 CellMaker helper, 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 image field stores the dropped image value. CellMaker.makeImage displays 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 dragover and drop events. The #flex template 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 dragover handler allows the drop only when the target cell is in the Image column and the dragged item is an image. The drop handler 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 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 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