Upload multiple files with Slim Image Cropper

Slim Image Cropper is a pretty nifty way to let users upload cropped images to your server. But it’s focused on cropping images and does not offer an out of the box solution to upload multiple images at once. In this article we’ll setup a script that works together with Slim to setup basic multiple file upload.

If you’re not familiar with Slim you can read more about it on the Slim Image Cropper Product Page.

Alright, let’s get started shall we?

First things first, we’ll need a place to drop multiple images. We’ll create a simple <div> element for that. Inside the div we will add a text so the user knows where to drop the images. For this text we can use a <label> element. If we then bind this label element to a <input> of type file (using the for attribute with the input id as value), we’re good to go! Oh, if you add the multiple attribute to the input the user can select multiple files.

This label / input combo will help us out in a couple of situations. The first advantage is this, if our JavaScript fails, we’ll still have our classic file upload.

<div class="file-drop-area">
  <label for="files">Drop your files here</label>
  <input name="files[]" id="files" type="file" multiple>
</div>

Alright, with the HTML setup we can now move on to add a some style. We style the label to look like the drop area. We also give it a cursor that looks like a pointer, when the user hovers over the label the browser reveals that it’s clickable.

Now for the second advantage of our label / input combo. Because we’ve bound or <label> to our <input> our eventual solution will work both when the user drops images on the drop area and when the user clicks the label.

.file-drop-area label {
  display: block;
  padding: 2em;
  background: #eee;
  text-align: center;
  cursor: pointer;
}

On to JavaScript!

  1. We’ll have to handle the file drag and drop events. drag needs to be defaultPrevented (I don’t know if that’s even a word) for drop to work. Also, we’ll bind the change event on the file input so manual file selection also works.
  2. For each dropped or selected item we’ll have figure out if it’s an actual file and if it matches our expectations.
  3. Create an Image Cropper for each of the dropped files that met our expectations.
  4. If all our script ran successfully we can hide the file input element.
// 1. Handling the various events
// - get references to different elements we need
// - listen to drag, drop and change events
// - handle dropped and selected files

// get a reference to the file drop area and the file input
var fileDropArea = document.querySelector('.file-drop-area');
var fileInput = fileDropArea.querySelector('input');
var fileInputName = fileInput.name;

// listen to events for dragging and dropping
fileDropArea.addEventListener('dragover', handleDragOver);
fileDropArea.addEventListener('drop', handleDrop);
fileInput.addEventListener('change', handleFileSelect);

// need to block dragover to allow drop
function handleDragOver(e) {
  e.preventDefault();
}

// deal with dropped items,
function handleDrop(e) {
  e.preventDefault();
  handleFileItems(e.dataTransfer.items || e.dataTransfer.files);
}

// handle manual selection of files using the file input
function handleFileSelect(e) {
  handleFileItems(e.target.files);
}

// 2. Handle the dropped items
// - test if the item is a File or a DataTransferItem
// - do some expectation matching

// loops over a list of items and passes
// them to the next function for handling
function handleFileItems(items) {
  var l = items.length;
  for (var i=0; i<l; i++) {
    handleItem(items[i]);
  }
}

// handles the dropped item, could be a DataTransferItem
// so we turn all items into files for easier handling
function handleItem(item) {

  // get file from item
  var file = item;
  if (item.getAsFile && item.kind =='file') {
    file = item.getAsFile();
  }

  handleFile(file);
}

// now we're sure each item is a file
// the function below can test if the files match
// our expectations
function handleFile(file) {

  /*
  // you can check if the file fits all requirements here
  // for example:
  // if file is bigger then 1 MB don't load
  if (file.size > 1000000) {
    return;
  }
  */

  // if it does, create a cropper
  createCropper(file);
}

// 3. Create the Image Cropper
// - create an element for the cropper to bind to
// - add the element after the drop area
// - creat the cropper and bind the remove button so it
//   removes the entire cropper when clicked.

// create an Image Cropper for each passed file
function createCropper(file) {

  // create container element for cropper
  var cropper = document.createElement('div');

  // insert this element after the file drop area
  fileDropArea.parentNode.insertBefore(cropper, fileDropArea.nextSibling);

  // create a Slim Cropper
  Slim.create(cropper, {
    ratio: '16:9',
    defaultInputName: fileInputName,
    didInit: function() {

      // load the file to our slim cropper
      this.load(file);

    },
    didRemove: function() {

      // detach from DOM
      cropper.parentNode.removeChild(cropper);

      // destroy the slim cropper
      this.destroy();

    }
  });

}

// 4. Disable the file input element

// hide file input, we can now upload with JavaScript
fileInput.style.display = 'none';

// remove file input name so it's value is
// not posted to the server
fileInput.removeAttribute('name');

To run the script either:

For the above script to function you’ll have to embed Slim Image Cropper on your web page. Of course with some tweaks to the createCropper function the above script can be used with other croppers as well.

In this example I use the global version of the script, but the createCropper function can easily be rewritten to work with jQuery as you can see below.

Literally replace Slim.create(cropper, with $(cropper).slim( and you’re good to go.

If you want to stay up to date on new articles follow me on Twitter below. If you have any questions just message me on Twitter as well.

to pqina.nl