Integrating Uppy with Doka a JavaScript Image Editor

Transloadit recently released the 1.0 version of their Uppy file uploader. In this tutorial we’ll integrate Uppy with Doka to visually optimise and compress image data before it’s uploaded to the server.

Compressing and cropping images before they’re send to the server can save bandwidth, save manual cropping work, and allows the user to have more control over their own content. Use the techniques described in this article to enable your users to edit profile pictures, photo albums, or any other imagery that they upload to your server. All while still being able to set content restrictions, like crop aspect ratio, minimum image size, and available filters.

We’ll start with the default Uppy implementation. Once we have that up and running we’ll add the Doka integration.

Setting up Uppy

Let’s begin by adding the Uppy stylesheet and script to the page.

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Uppy</title>

    <!-- add the Uppy styles to the head -->
    <link href="https://transloadit.edgly.net/releases/uppy/v1.2.0/uppy.min.css" rel="stylesheet">
  
</head>
<body>

    <!-- add the Uppy script to right before the closing body element -->
    <script src="https://transloadit.edgly.net/releases/uppy/v1.2.0/uppy.min.js"></script>

</body>
</html>

Now we’ll add the HTML element that will contain the Uppy instance.

Note that I’ve shortened the style and script tags to keep the code snippet readable.

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Uppy</title>
    <link href="uppy.min.css" rel="stylesheet">
</head>
<body>
    <!-- add the Uppy HTML element -->
    <div id="drag-drop-area"></div>

    <script src="uppy.min.js"></script>
</body>
</html>

All right, let’s extend the script to load Uppy.

<body>
    <div id="drag-drop-area"></div>
    <script src="uppy.min.js"></script>

    <!-- add the Uppy script init logic -->
    <script>
      var uppy = Uppy.Core()
        .use(Uppy.Dashboard, {
          inline: true,
          target: '#drag-drop-area'
        })
        .use(Uppy.Tus, {endpoint: 'https://master.tus.io/files/'})

      uppy.on('complete', (result) => {
        console.log('Upload complete! We’ve uploaded these files:', result.successful)
      })
    </script>
</body>

Refresh your browser window and Uppy should now appear like shown below:

Uppy as it shows on first render

You can drop files and upload them as well, it’ll all work automagically.

Let’s move on to the Doka integration.

Integrating Doka with Uppy

To allow editing of the image we need to load Doka when an image is dropped. We can do this by leveraging the onBeforeFileAdded function.

First however we’ll need to add the Doka scripts and styles.

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Uppy</title>
    <link href="uppy.min.css" rel="stylesheet">

    <!-- add the Doka style tag -->
    <link href="doka.min.css" rel="stylesheet">
</head>
<body>
    <div id="drag-drop-area"></div>
    <script src="uppy.min.js"></script>

    <!-- add the Doka script tag -->
    <script src="doka.min.js"></script>
</body>
</html>

Now we’ll link up with the Uppy onBeforeFileAdded function. We’ll zoom in on the JavaScript code to make things easier to read.

The onBeforeFileAdded function allows us to validate the dropped file. If we return false the file will be rejected, if we return true, it will be added to the list. We need to edit the file and then add the result of the user edit to the list of files.

What we need to do:

  1. Prevent the original file from being added
  2. Edit the original file with Doka
  3. Add the file returned by Doka to the list

Let’s start by preventing the file from being added.

// our uppy options object
var uppyOptions = {
    onBeforeFileAdded: function(file) {

        // prevent the file from being added
        return false;

    }
}

// initialise Uppy
var uppy = Uppy.Core(uppyOptions)
    .use(Uppy.Dashboard, {
        inline: true,
        target: '#drag-drop-area'
    })
    .use(Uppy.Tus, {endpoint: 'https://master.tus.io/files/'})

Now we need to edit the file we just rejected with Doka. We’ll zoom in on the uppyOptions object.

We create our Doka instance and then we edit the file data with Doka.

// create our Doka instance
var doka = Doka.create({
    utils: ['crop', 'filter', 'color']
});

var uppyOptions = {
    onBeforeFileAdded: function(file) {

        // edit the file data with Doka
        doka.edit(file.data);

        return false;
    }
}

Great, the Doka image editor now opens when dropping a file.

Doka as it shows on first render

The Doka.edit function returns a Promise which is resolved with a file object when the Done button is pressed in the Doka editor. When this happens we want to return the resulting file to Uppy.

var uppyOptions = {
    onBeforeFileAdded: function(file) {

        doka.edit(file.data).then(output => {

            // add the Doka edit result back to Uppy
            uppy.addFile({
                ...file,
                data: output.file
            });
            
        });

        return false;
    }
}

That’s all fine, but now we’re running in circles. When the file is edited by Doka the output file is added to Uppy and again, is edited by Doka, and this continues untill there are no more bytes left to edit. Not good.

We need to tell Uppy that the file that is being added has already been edited. Let’s do this by adding a special property to the file.

var uppyOptions = {
    onBeforeFileAdded: function(file) {

        // test if special boolean property is present, if so, allow file
        if (file.handledByDoka) return true;

        doka.edit(file.data).then(output => {
            uppy.addFile({
                ...file,
                data: output.file,
                
                // add a special boolean to the file
                handledByDoka: true
            });
        });

        return false;
    }
}

That’s it!

When a user drops an image the image is now sent to Doka, the user can then edit the image and the resulting data is sent to Uppy. We remember the edit by setting the handledByDoka property to true.

By setting the crop aspect ratio, minimum image size, available utilities you can now control how much the user is allowed to edit the image data before it is uploaded to your server.

Please note that the example above doesn’t handle multiple dropped files, this would trigger multiple image edits at once. The Doka library ships with a handy useDokaWithUppy function that takes care of queueing the images so they nicely wait their turn to be edited.

Using this function the JavaScript code snippet now looks like this:

// connect with `useDokaWithUppy` and pass Doka properties
var uppyOptions = {
    onBeforeFileAdded: useDokaWithUppy({
        utils: ['crop', 'filter', 'color']
    })
};

// initialise Uppy
var uppy = Uppy.Core(uppyOptions)
    .use(Uppy.Dashboard, {
        inline: true,
        target: '#drag-drop-area'
    })
    .use(Uppy.Tus, {endpoint: 'https://master.tus.io/files/'})

You can now use the Uppy file upload library together with Doka to handle image uploads.

See the results below.

Uppy and Doka working together

Find out more about Doka on the product site

If you’re using DropZoneJS you might also be interested in Integrating DropzoneJS with a JavaScript Image Cropper to Optimise Image Uploads

Rik Schennink

Web enthusiast

to pqina.nl