Skip to main content

Overview

The JoyDoc SDK provides comprehensive image upload functionality through image fields, supporting both single and multiple image uploads with flexible handling options. Architecture Overview When users interact with image fields, the SDK triggers upload events that your application must handle by implementing upload handlers. Flow:
  1. User selects image field → SDK creates upload event
  2. Your upload handler receives the event
  3. You handle the image processing and pass the image url back to SDK
  4. SDK updates the form with the provided URLs

Upload Methods

JoyDoc provides two methods for handling image uploads:
  • onFileUploadAsync (configured in field settings)
  • onUploadAsync (configured in JoyDoc component props)

Basic Implementation Examples

onFieldUploadAsync You can also configure onFileUploadAsync via field settings as documented in the Joyfill Customize Settings guide:
const fieldSettings = {
  field: {
    onFileUploadAsync: async (params, fileUploads) => {
      console.log(
        "onFileUploadAsync via field settings: ",
        params,
        fileUploads
      );

      const uploadPromises = fileUploads.map(async (file) => {
        const dataUri = await getDataUriForFileUpload(file);
        return uploadFileAsync(params.fieldIdentifier, dataUri);
      });

      const results = await Promise.all(uploadPromises);
      return results;
    },
  },
};

// Use with JoyDoc component
<JoyDoc
  doc={document}
  fieldSettings={fieldSettings}
  onChange={handleChange}
  mode="edit"
  width={816}
  height={600}
/>;

onUploadAsync

Configure upload handling at the document level:
<JoyDoc
  doc={document}
  onUploadAsync={async (params, fileUploads) => {
    console.log("onUploadAsync: ", params, fileUploads);

    const resultPromises = await fileUploads.map(async (fileUpload) => {
      const dataUri = await getDataUriForFileUpload(fileUpload);
      return uploadFileAsync(params.fieldIdentifier, dataUri);
    });

    return Promise.all(resultPromises)
      .then((responses) => {
        // Normalize response for different file types
        const finalResponse = Array.isArray(responses[0])
          ? responses[0]
          : responses;
        return finalResponse;
      })
      .catch((error) => {
        console.error("Upload error:", error);
        if (error) return;
      });
  }}
  // ... other props
/>;

Parameters

Both methods receive the same parameters:
  • params: Object containing field/document metadata
    • params.fileId: The file ID
    • params.pageId: The page ID
    • params.fieldId: The field ID
    • params.fieldPositionId: The field position ID
    • params.fieldIdentifier: The field identifier
    • params.documentId: The document ID
    • params.documentIdentifier: The document identifier
  • fileUploads: Array of File objects to be uploaded

Return Value

Both methods should return an array of upload results. Each result should contain:
  • _id: Unique identifier for the uploaded file
  • url: URL where the file can be accessed
  • fileName: Original filename
  • fileSize: Size of the file in bytes

Complete Working Examples

Example 1: onFileUploadAsync

Here’s a complete working example using onFileUploadAsync:
import React, { useState } from 'react';
import { JoyDoc, getDefaultDocument } from '@joyfill/components';

function OnFileUploadAsyncExample() {
  const [document, setDocument] = useState(() => {
    const doc = getDefaultDocument();

    const fields = [
      {
        _id: 'profileImage',
        identifier: 'profileImage',
        type: 'image',
        title: 'Profile Image',
        value: [],
        file: doc.files[0]._id,
        multi: false
      }
    ];

    doc.fields = fields;

    doc.files[0].pages[0].fieldPositions.push({
      _id: 'profileImage-position',
      field: 'profileImage',
      x: 0,
      y: 0,
      width: 1,
      height: 1,
      displayType: 'original'
    });

    return doc;
  });

  const handleChange = (changelogs, updatedDoc) => {
    setDocument(updatedDoc);
  };

  const fieldSettings = {
    field: {
      systemFilePicker: true,
      onFileUploadAsync: async (params, fileUploads) => {
        console.log('onFileUploadAsync triggered:', params, fileUploads);

        const uploadPromises = fileUploads.map(async (file) => {
          const dataUri = await getDataUriForFileUpload(file);
          return uploadFileAsync(params.fieldIdentifier, dataUri);
        });

        const results = await Promise.all(uploadPromises);
        console.log('Upload completed:', results);
        return results;
      }
    }
  };

  return (
    <div>
      <h1>onFileUploadAsync Example</h1>
      <JoyDoc
        doc={document}
        fieldSettings={fieldSettings}
        onChange={handleChange}
        mode="edit"
        width={816}
        height={600}
      />
    </div>
  );
}

export default OnFileUploadAsyncExample;

Example 2: onUploadAsync

Here’s a complete working example using onUploadAsync:
import React, { useState } from "react";
import { JoyDoc, getDefaultDocument } from "@joyfill/components";

function OnUploadAsyncExample() {
  const [document, setDocument] = useState(() => {
    const doc = getDefaultDocument();

    const fields = [
      {
        _id: "galleryImages",
        identifier: "galleryImages",
        type: "image",
        title: "Image Gallery",
        value: [],
        file: doc.files[0]._id,
        multi: true,
      },
    ];

    doc.fields = fields;

    doc.files[0].pages[0].fieldPositions.push({
      _id: "galleryImages-position",
      field: "galleryImages",
      x: 0,
      y: 0,
      width: 1,
      height: 1,
      displayType: "original",
    });

    return doc;
  });

  const handleChange = (changelogs, updatedDoc) => {
    setDocument(updatedDoc);
  };

  const handleUpload = async (params, fileUploads) => {
    console.log("onUploadAsync triggered:", params, fileUploads);

    const resultPromises = await fileUploads.map(async (fileUpload) => {
      const dataUri = await getDataUriForFileUpload(fileUpload);
      return uploadFileAsync(params.fieldIdentifier, dataUri);
    });

    return Promise.all(resultPromises)
      .then((responses) => {
        // Normalize response for different file types
        const finalResponse = Array.isArray(responses[0])
          ? responses[0]
          : responses;
        console.log("Upload completed:", finalResponse);
        return finalResponse;
      })
      .catch((error) => {
        console.error("Upload error:", error);
        if (error) return;
      });
  };

  return (
    <div>
      <h1>onUploadAsync Example</h1>
      <JoyDoc
        doc={document}
        onUploadAsync={handleUpload}
        onChange={handleChange}
        mode="edit"
        width={816}
        height={600}
      />
    </div>
  );
}

export default OnUploadAsyncExample;

Setup steps

Step 1: Create Joyfill Account
  • To begin working with Joyfill, go to Joyfill’s Platform and create an account (jump to step 2 if you already have an account).
  • By creating an account you will add yourself as the first user to your newly created Joyfill Organization and be placed inside the Joyfill Manager.
Step 2: Generate Your userAccessToken
  • Once you’re inside the Joyfill Manager you will want to select from the top navigation bar Settings & Users -> Manager Users -> and click “Access Tokens” button next to your user.
  • Once the modal appears select “Add Access Token”. Copy and securely store your access token for later use.
Step3 Api Url
  • The Api url is http://api-joy.joyfill.io

Helper Functions

These utility functions are used in both upload methods:

getDataUriForFileUpload

Converts a File object to a data URI string:
const getDataUriForFileUpload = async (fileUpload) => {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.readAsDataURL(fileUpload);
    reader.onloadend = async () => {
      resolve(reader.result);
    };
    reader.onerror = () => {
      reject(new Error('Failed to read file'));
    };
  });
};

uploadFileAsync

Uploads a data URI to the server:

const uploadFileAsync = async (docIdentifier, dataUri) => {
  const response = await fetch(`${apiUrl}/v1/documents/${docIdentifier}/files/datauri`, {
    method: 'POST',
    mode: 'cors',
    headers: getHeaders(),
    body: JSON.stringify({ file: dataUri })
  });

  const data = await response.json();
  return data;
};

getHeaders

Returns the necessary headers for API requests:
const getHeaders = () => {
  return {
    Authorization: `Bearer ${userAccessToken}`,
    'Content-Type': 'application/json'
  };
};