Skip to content

df-image Component

The df-image component provides an image upload field: a preview of the current image, a drag & drop zone, and a button that opens the browser's file dialog. It shares its backend communication contract with df-file.

Basic Usage

Below is an example of the df-image component used with DynamicForms:

Image Upload Example
Field value (image URL):
data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cmVjdCB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgZmlsbD0iIzE5NzZkMiIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmaWxsPSJ3aGl0ZSIgZm9udC1zaXplPSIyMCIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZG9taW5hbnQtYmFzZWxpbmU9Im1pZGRsZSI+UHJvZmlsZTwvdGV4dD48L3N2Zz4=

Features

  • Preview of the current image, shown as soon as one is set
  • Drag & drop, and a click-to-browse dialog
  • Upload progress indication
  • Image deletion support
  • Downloading the currently set image - its value is already a URL the browser can fetch, so no comms method is needed for this, unlike df-file's getDownloadUrl
  • Automatic periodic "touches" to keep uploaded images active
  • Customizable labels, hints, and error messages
  • Backend communication abstraction through the same FileComms interface as df-file

Props

In addition to common props from InputBase, this component requires:

PropTypeDefaultDescription
commsFileCommsRequiredObject with methods for image operations
touchIntervalnumber60000Milliseconds between keep-alive touches. See Configuration for setting this application-wide instead

Inherited Props

This component inherits all common props from InputBase, including:

  • control - DynamicForms field object
  • modelValue - The image URL (v-model)
  • label - Input label
  • hint - Hint text
  • And more...

Value Format

The component stores and displays a URL string, not the file itself:

  1. comms.upload resolves to the URL the component renders as the image's src - there is no separate lookup for turning an identifier into a display URL, so whatever it returns must already be usable as one.
  2. The same value is used in subsequent operations (delete, touch) to reference the image.
  3. It is stored in the form data when using DynamicForms.

FileComms Interface

The comms prop requires an object implementing the same FileComms interface df-file uses:

typescript
interface FileComms {
  /**
   * Called when an image is picked, dropped, or selected via the dialog, and needs to be uploaded
   * @param file The image file to be uploaded
   * @param progressCallback Callback function for upload progress
   * @return Promise resolving to the URL the component displays the image from
   */
  upload: (file: File, progressCallback?: FileProgressCallback) => Promise<string>;

  /**
   * Called when the image is removed. Throw FileGoneError if the backend already reports the identifier as
   * gone; any other thrown error is treated as transient.
   * @param fileIdentifier The URL that was returned by upload
   */
  delete: (fileIdentifier: string) => Promise<void>;

  /**
   * Called periodically to keep the image active. Throw FileGoneError if the backend reports the identifier
   * no longer exists — the component then clears the field and, where a `control` is bound, shows the
   * error's `errorText`. Any other thrown error is treated as transient and left to the consumer.
   * @param fileIdentifier The URL that was returned by upload
   */
  touch: (fileIdentifier: string) => Promise<void>;
}

// Progress callback type
type FileProgressCallback = (loaded: number, total: number) => void;

// Thrown by touch/delete to report that the backend has already discarded the image
class FileGoneError extends Error {
  constructor(public errorText: string) {
    super(errorText);
  }
}

FileComms also declares an optional getDownloadUrl, for df-file's use — df-image ignores it, since its value already is the URL a download link needs.

Upload Progress

The component displays a progress bar during upload, using the values provided by the progressCallback in the upload method.

After an image has been uploaded to the backend, it is touched every touchInterval milliseconds (60 seconds by default) to let the backend know that it's still relevant. If a touch rejects with a FileGoneError, the field and preview are cleared and, where a control is bound, the error's errorText is shown as a validation error. Any other rejection is treated as a transient failure and left to the consumer.

Non-image Files

A dropped or picked file whose type is not image/* is rejected without calling comms.upload.

Events

This component emits all common events from InputBase:

  • update:modelValue - When the image URL changes

Examples

Basic Example with Direct API Communication

vue
<template>
  <df-image
    v-model="imageUrl"
    :comms="imageComms"
    label="Profile picture"
    hint="PNG or JPG, max 5MB"
  />
</template>

<script setup>
import { ref } from 'vue';
import axios from 'axios';
import { DfImage } from '@dynamicforms/vuetify-inputs';
import { FileGoneError } from '@dynamicforms/vuetify-inputs';

const imageUrl = ref(null);

// Implementation of FileComms for API communication
const imageComms = {
  upload: async (file, progressCallback) => {
    const formData = new FormData();
    formData.append('file', file);

    const response = await axios.post('/api/images', formData, {
      onUploadProgress: (progressEvent) => {
        if (progressCallback) {
          progressCallback(progressEvent.loaded, progressEvent.total);
        }
      }
    });

    return response.data.url;
  },

  delete: async (imageUrl) => {
    await axios.delete('/api/images', { params: { url: imageUrl } });
  },

  touch: async (imageUrl) => {
    try {
      await axios.post('/api/images/touch', { url: imageUrl });
    } catch (err) {
      if (err.response?.status === 404) {
        throw new FileGoneError('This image is no longer available. Please upload it again.');
      }
      throw err;
    }
  }
};
</script>

With DynamicForms Integration

vue
<template>
  <df-image
    :control="form.fields.avatar"
    :comms="imageComms"
    label="Profile picture"
  />
</template>

<script setup>
import { Group, Field } from '@dynamicforms/vue-forms';
import { DfImage } from '@dynamicforms/vuetify-inputs';

const form = new Group({
  avatar: new Field({ value: null })
});

// Implementation of FileComms (same as above)
const imageComms = {
  // ... implementation as in previous example
};
</script>

Released under the MIT License.