AngularJS Drag and Drop Photo Directive

Wednesday, July 10, 2013

Continuing from previous posts on building a file input directive and a file reader service, this post contains my first try at a drag-n-drop directive that uses the file reader service to copy an image dropped from the desktop into an img element.

As always, I welcome suggestions!

Native HTML 5 drag-and-drop is easy to work with. The directive handles the dragover, dragleave, and drop events on the target element. Dragover and dragleave are mostly about manipulating classes on the element to style it as a droppable target, as well as using e.preventDefault(), which is required for the drop event to work.

module.directive("imageDrop",
  function ($parse, fileReader, resampler) {

    return {
        restrict: "EA",
        link: function (scope, element, attrs) {

            var expression = attrs.imageDrop;
            var accesor = $parse(expression);


            var onDragOver = function (e) {
                e.preventDefault();
                element.addClass("dragOver");
            };

            var onDragEnd = function (e) {
                e.preventDefault();
                element.removeClass("dragOver");
            };

            var placeImage = function (imageData) {
                accesor.assign(scope, imageData);
            };

            var resampleImage = function (imageData) {                       
                return resampler.resample(
                    imageData, element.width(),
                    element.height(), scope);
            };

            var loadFile = function (file) {
                fileReader
                    .readAsDataUrl(file, scope)
                    .then(resampleImage)
                    .then(placeImage);
            };


            element.bind("dragover", onDragOver)
                   .bind("dragleave", onDragEnd)
                   .bind("drop", function (e) {
                       onDragEnd(e);
                       loadFile(e.originalEvent.dataTransfer.files[0]);
                   });

            scope.$watch(expression, function () {
                element.attr("src", accesor(scope));
            });
        }
    };
});

Some of the code to ensure only images are being processed is omitted, but I do want to point out the  image resizing code. It’s wrapped by the resampler service below, which in turn uses Resampler.js from the post “100% Client Side Image Resizing”.

var resampler = function ($q) {

    var resample = function (imageData, width, height, scope) {
        var deferred = $q.defer();

        Resample(imageData, width, height, function (result) {

            scope.$apply(function () {
                deferred.resolve(result);
            });
        });

        return deferred.promise;

    };

    return {
        resample: resample
    };
};

module.factory("resampler", resampler);

Comments
gravatar Marcus Wednesday, August 14, 2013
Hi Scott, I've been following this series of posts and I feel I understand the previous posts, but there are a few lines in this post/example that have me confused. The lines are: var expression = attrs.imageDrop; var accesor = $parse(expression); Then the "placeImage" function has me totally lost as to what it's doing. What am I missing? Maybe an snippet of the directive in use in markup like you did in the previous two posts in this series? Thanks!
gravatar Evan Wednesday, August 14, 2013
Hi, thanks for sharing this directive! It's helped me out a lot. I had no clue why own drag and drop directive wasn't binding on the 'drop' event, but you've helped me realize that it's because 'dragover' must be handled first.
gravatar Scott Thursday, August 15, 2013
@Marcus: Sorry, rushed through this post. Will try to follow it up with another one with more detail. The sort story is that the expression parsing is taking an attribute out of the DOM and turning it into a function I can call to get / set a model value. "accessor" is like a setter function because it can write to a model value specified in the dom attribute div imageDrop="imageProperty".
Comments are now closed.
by K. Scott Allen K.Scott Allen
My Pluralsight Courses
The Podcast!