API Docs for:
Show:

File: packages/ember-droplet/ember-droplet-mixin.js

/**
 * @module App
 * @class EmberDropletController
 * @type Ember.Mixin
 * @extends Ember.Mixin
 */
window.DropletController = Ember.Mixin.create({

    /**
     * @property mimeTypes
     * @type {Array}
     */
    mimeTypes: ['image/jpeg', 'image/jpg', 'image/gif', 'image/png', 'text/plain'],

    /**
     * @property requestHeaders
     * @type {Object}
     * Contains a list of headers to be included in the request made by
     * uploadAllFiles()
     */
    requestHeaders: {},

    /**
     * @property postData
     * @type {Object}
     * Contains a dictonary of extra POST data to be included in the
     * request made by uploadAllFiles()
     */
    postData: {},

    /**
     * @property files
     * @type {Array}
     * @default []
     * Contains a list of files, both valid, deleted, and invalid.
     */
    files: [],

    /**
     * @property uploadStatus
     * @type {Object}
     */
    uploadStatus: { uploading: false, percentComplete: 0, error: false },

    /**
     * @constructor
     * @method init
     * Clears the file array for each instantiation.
     * @return {void}
     */
    init: function() {
        Ember.set(this, 'files', []);
        this._super();
    },

    /**
     * @property actions
     * @type {Object}
     */
    actions: {

        /**
         * @method addValidFile
         * @param file {File}
         * Adds a valid file to the collection.
         * @return {Object}
         */
        addValidFile: function(file) {
            return this._addFile(file, true);
        },

        /**
         * @method addInvalidFile
         * @param file {File}
         * Adds an invalid file to the collection.
         * @return {Object}
         */
        addInvalidFile: function(file) {
            return this._addFile(file, false);
        },

        /**
         * @method deleteFile
         * @param file
         * Deletes a file from the collection.
         * @return {Object}
         */
        deleteFile: function(file) {
            Ember.set(file, 'deleted', true);
            return file;
        },

        /**
         * @method clearAllFiles
         * Clears all of the files from the collection.
         * @return {void}
         */
        clearAllFiles: function() {
            Ember.set(this, 'files', []);
        },

        /**
         * @method uploadAllFiles
         * Uploads all of the files that haven't been uploaded yet, but are valid files.
         * @return {Object|Boolean} jQuery promise, or false if there are no files to upload.
         */
        uploadAllFiles: function() {

            if (Ember.get(this, 'validFiles').length === 0) {
                // Determine if there are even files to upload.
                return false;
            }

            // Find the URL, set the uploading status, and create our promise.
            var url             = Ember.get(this, 'dropletUrl'),
                deferred        = new jQuery.Deferred(),
                postData        = this.get('postData'),
                requestHeaders  = this.get('requestHeaders');

            Ember.set(this, 'uploadStatus.uploading', true);
            Ember.set(this, 'uploadStatus.error', false);

            // Assert that we have the URL specified in the controller that implements the mixin.
            Ember.assert('You must specify the `dropletUrl` parameter in order to upload files.', !!url);

            // Create a new XHR request object.
            var request = new XMLHttpRequest();
            request.open('post', url, true);

            // Create a new form data instance.
            var formData = new FormData();

            // Iterate over each file, and append it to the form data.
            Ember.EnumerableUtils.forEach(Ember.get(this, 'validFiles'), function(file) {
                formData.append('file', file.file);
            }, this);

            // Add any extra POST data specified in the controller
            for (var index in postData) {
                if (postData.hasOwnProperty(index)) {
                    formData.append(index, postData[index]);
                }
            }

            // Add all of the event listeners.
            this._addProgressListener(request.upload);
            this._addSuccessListener(request.upload, deferred);
            this._addErrorListener(request.upload, deferred);

            // Resolve the promise when we've finished uploading all the files.
            request.onreadystatechange = function() {

                if (request.readyState === 4) {
                    var files = JSON.parse(request.responseText);
                    deferred.resolve(files);
                }

            };
            // Set the request size, and then we can upload the files!
            request.setRequestHeader('X-File-Size', this._getSize());

            // Assign any request headers specified in the controller.
            for (index in requestHeaders) {
                if (requestHeaders.hasOwnProperty(index)) {
                    request.setRequestHeader(index, requestHeaders[index]);
                }
            }

            request.send(formData);

            // Return the promise.
            return deferred.promise();

        }

    },

    /**
     * @property validFiles
     * Finds a list of files that aren't deleted, and are of a valid MIME type.
     * @return {Array}
     */
    validFiles: Ember.computed(function() {
        return this._filesByProperties({ valid: true, deleted: false, uploaded: false });
    }).property('files.length', 'files.@each.deleted', 'files.@each.uploaded'),

    /**
     * @property invalidFiles
     * Finds a list of files that have an unsupported MIME type.
     * @return {Array}
     */
    invalidFiles: Ember.computed(function() {
        return this._filesByProperties({ valid: false });
    }).property('files.length', 'files.@each.deleted'),

    /**
     * @property uploadedFiles
     * Finds a list of files that have been successfully uploaded.
     * @return {Array}
     */
    uploadedFiles: Ember.computed(function() {
        return this._filesByProperties({ uploaded: true });
    }).property('files.length', 'files.@each.uploaded'),

    /**
     * @property deletedFiles
     * Finds a list of files that have been deleted by the user.
     * @return {Array}
     */
    deletedFiles: Ember.computed(function() {
        return this._filesByProperties({ deleted: true });
    }).property('files.length', 'files.@each.deleted'),

    /**
     * @method _filesByProperties
     * @param maps {Object}
     * Accepts a map of properties that each file must have.
     * @return {Array}
     * @private
     */
    _filesByProperties: function(maps) {

        // Iterate over each of the files.
        return Ember.get(this, 'files').filter(function(file) {

            for (var property in maps) {

                if (maps.hasOwnProperty(property)) {

                    // If the current property doesn't match what we're after from the map,
                    // then the file is invalid.
                    if (file[property] !== maps[property]) {
                        return false;
                    }

                }

            }

            // Voila! We have a good file that matches our criteria.
            return true;

        });

    },

    /**
     * @method _getSize
     * Determine the size of the request.
     * @return {Number}
     * @private
     */
    _getSize: function() {

        var size = 0;

        // Iterate over all of the files to determine the size of all valid files.
        Ember.EnumerableUtils.forEach(Ember.get(this, 'validFiles'), function(file) {
            size += file.file.size;
        });

        return size;

    },

    /**
     * @method _addSuccessListener
     * @param request
     * @private
     */
    _addSuccessListener: function(request) {

        // Once the files have been successfully uploaded.
        request.addEventListener('load', function() {

            // Set the `uploaded` parameter to true once we've successfully // uploaded the files.
            Ember.EnumerableUtils.forEach(Ember.get(this, 'validFiles'), function(file) {
                Ember.set(file, 'uploaded', true);
            });

            // We want to revert the upload status.
            Ember.set(this, 'uploadStatus.uploading', false);

        }.bind(this), false);

    },

    /**
     * @method _addErrorListener
     * @param request
     * @param [deferred = null]
     * @return {void}
     * @private
     */
    _addErrorListener: function(request, deferred) {

        request.addEventListener('error', function() {

            // As an error occurred, we need to revert everything.
            Ember.set(this, 'uploadStatus.uploading', false);
            Ember.set(this, 'uploadStatus.error', true);

            if (deferred) {
                // Reject the promise if we have one.
                deferred.reject();
            }

        }.bind(this));

    },

    /**
     * @method _addProgressListener
     * @param request
     * @return {void}
     * @private
     */
    _addProgressListener: function(request) {

        request.addEventListener('progress', function (event) {

            if (!event.lengthComputable) {
                // There's not much we can do if the request is not computable.
                return;
            }

            // Calculate the percentage remaining.
            var percentageLoaded = (event.loaded / this._getSize()) * 100;
            Ember.set(this, 'uploadStatus.percentComplete', Math.round(percentageLoaded));

        }.bind(this), false);

    },

    /**
     * @method _addFile
     * @param file {File}
     * @param valid {Boolean}
     * Adds a file based on whether it's valid or invalid.
     * @return {Object}
     * @private
     */
    _addFile: function(file, valid) {

        // Extract the file's extension which allows us to style accordingly.
        var className = 'extension-%@'.fmt(file.name.match(/\.(.+)$/i)[1]).toLowerCase();

        // Create the record with its default parameters, and then add it to the collection.
        var record = { file: file, valid: valid, uploaded: false, deleted: false, className: className };
        Ember.get(this, 'files').pushObject(record);

        // Voila!
        return record;

    }

});