jkuchar/MultipleFileUpload

View on GitHub
public/plupload/js/jquery.plupload.queue/jquery.plupload.queue.js

Summary

Maintainability
D
1 day
Test Coverage
/**
 * jquery.plupload.queue.js
 *
 * Copyright 2009, Moxiecode Systems AB
 * Released under GPL License.
 *
 * License: http://www.plupload.com/license
 * Contributing: http://www.plupload.com/contributing
 */

/* global jQuery:true, alert:true */

/**
jQuery based implementation of the Plupload API - multi-runtime file uploading API.

To use the widget you must include _jQuery_. It is not meant to be extended in any way and is provided to be
used as it is.

@example
    <!-- Instantiating: -->
    <div id="uploader">
        <p>Your browser doesn't have Flash, Silverlight or HTML5 support.</p>
    </div>

    <script>
        $('#uploader').pluploadQueue({
            url : '../upload.php',
            filters : [
                {title : "Image files", extensions : "jpg,gif,png"}
            ],
            rename: true,
            flash_swf_url : '../../js/Moxie.swf',
            silverlight_xap_url : '../../js/Moxie.xap',
        });
    </script>

@example
    // Retrieving a reference to plupload.Uploader object
    var uploader = $('#uploader').pluploadQueue();

    uploader.bind('FilesAdded', function() {
        
        // Autostart
        setTimeout(uploader.start, 1); // "detach" from the main thread
    });

@class pluploadQueue
@constructor
@param {Object} settings For detailed information about each option check documentation.
    @param {String} settings.url URL of the server-side upload handler.
    @param {Number|String} [settings.chunk_size=0] Chunk size in bytes to slice the file into. Shorcuts with b, kb, mb, gb, tb suffixes also supported. `e.g. 204800 or "204800b" or "200kb"`. By default - disabled.
    @param {String} [settings.file_data_name="file"] Name for the file field in Multipart formated message.
    @param {Array} [settings.filters=[]] Set of file type filters, each one defined by hash of title and extensions. `e.g. {title : "Image files", extensions : "jpg,jpeg,gif,png"}`. Dispatches `plupload.FILE_EXTENSION_ERROR`
    @param {String} [settings.flash_swf_url] URL of the Flash swf.
    @param {Object} [settings.headers] Custom headers to send with the upload. Hash of name/value pairs.
    @param {Number|String} [settings.max_file_size] Maximum file size that the user can pick, in bytes. Optionally supports b, kb, mb, gb, tb suffixes. `e.g. "10mb" or "1gb"`. By default - not set. Dispatches `plupload.FILE_SIZE_ERROR`.
    @param {Number} [settings.max_retries=0] How many times to retry the chunk or file, before triggering Error event.
    @param {Boolean} [settings.multipart=true] Whether to send file and additional parameters as Multipart formated message.
    @param {Object} [settings.multipart_params] Hash of key/value pairs to send with every file upload.
    @param {Boolean} [settings.multi_selection=true] Enable ability to select multiple files at once in file dialog.
    @param {Boolean} [settings.prevent_duplicates=false] Do not let duplicates into the queue. Dispatches `plupload.FILE_DUPLICATE_ERROR`.
    @param {String|Object} [settings.required_features] Either comma-separated list or hash of required features that chosen runtime should absolutely possess.
    @param {Object} [settings.resize] Enable resizng of images on client-side. Applies to `image/jpeg` and `image/png` only. `e.g. {width : 200, height : 200, quality : 90, crop: true}`
        @param {Number} [settings.resize.width] If image is bigger, it will be resized.
        @param {Number} [settings.resize.height] If image is bigger, it will be resized.
        @param {Number} [settings.resize.quality=90] Compression quality for jpegs (1-100).
        @param {Boolean} [settings.resize.crop=false] Whether to crop images to exact dimensions. By default they will be resized proportionally.
    @param {String} [settings.runtimes="html5,flash,silverlight,html4"] Comma separated list of runtimes, that Plupload will try in turn, moving to the next if previous fails.
    @param {String} [settings.silverlight_xap_url] URL of the Silverlight xap.
    @param {Boolean} [settings.unique_names=false] If true will generate unique filenames for uploaded files.

    @param {Boolean} [settings.dragdrop=true] Enable ability to add file to the queue by drag'n'dropping them from the desktop.
    @param {Boolean} [settings.rename=false] Enable ability to rename files in the queue.
    @param {Boolean} [settings.multiple_queues=true] Re-activate the widget after each upload procedure.
*/
;(function($, o) {
    var uploaders = {};

    function _(str) {
        return plupload.translate(str) || str;
    }

    function renderUI(id, target) {
        // Remove all existing non plupload items
        target.contents().each(function(i, node) {
            node = $(node);

            if (!node.is('.plupload')) {
                node.remove();
            }
        });

        target.prepend(
            '<div class="plupload_wrapper plupload_scroll">' +
                '<div id="' + id + '_container" class="plupload_container">' +
                    '<div class="plupload">' +
                        '<div class="plupload_header">' +
                            '<div class="plupload_header_content">' +
                                '<div class="plupload_header_title">' + _('Select files') + '</div>' +
                                '<div class="plupload_header_text">' + _('Add files to the upload queue and click the start button.') + '</div>' +
                            '</div>' +
                        '</div>' +

                        '<div class="plupload_content">' +
                            '<div class="plupload_filelist_header">' +
                                '<div class="plupload_file_name">' + _('Filename') + '</div>' +
                                '<div class="plupload_file_action">&nbsp;</div>' +
                                '<div class="plupload_file_status"><span>' + _('Status') + '</span></div>' +
                                '<div class="plupload_file_size">' + _('Size') + '</div>' +
                                '<div class="plupload_clearer">&nbsp;</div>' +
                            '</div>' +

                            '<ul id="' + id + '_filelist" class="plupload_filelist"></ul>' +

                            '<div class="plupload_filelist_footer">' +
                                '<div class="plupload_file_name">' +
                                    '<div class="plupload_buttons">' +
                                        '<a href="#" class="plupload_button plupload_add" id="' + id + '_browse">' + _('Add Files') + '</a>' +
                                        '<a href="#" class="plupload_button plupload_start">' + _('Start Upload') + '</a>' +
                                    '</div>' +
                                    '<span class="plupload_upload_status"></span>' +
                                '</div>' +
                                '<div class="plupload_file_action"></div>' +
                                '<div class="plupload_file_status"><span class="plupload_total_status">0%</span></div>' +
                                '<div class="plupload_file_size"><span class="plupload_total_file_size">0 b</span></div>' +
                                '<div class="plupload_progress">' +
                                    '<div class="plupload_progress_container">' +
                                        '<div class="plupload_progress_bar"></div>' +
                                    '</div>' +
                                '</div>' +
                                '<div class="plupload_clearer">&nbsp;</div>' +
                            '</div>' +
                        '</div>' +
                    '</div>' +
                '</div>' +
                '<input type="hidden" id="' + id + '_count" name="' + id + '_count" value="0" />' +
            '</div>'
        );
    }

    $.fn.pluploadQueue = function(settings) {
        if (settings) {
            this.each(function() {
                var uploader, target, id, contents_bak;

                target = $(this);
                id = target.attr('id');

                if (!id) {
                    id = plupload.guid();
                    target.attr('id', id);
                }

                contents_bak = target.html();
                renderUI(id, target);

                settings = $.extend({
                    dragdrop : true,
                    browse_button : id + '_browse',
                    container : id
                }, settings);

                // Enable drag/drop (see PostInit handler as well)
                if (settings.dragdrop) {
                    settings.drop_element = id + '_filelist';
                }

                uploader = new plupload.Uploader(settings);

                uploaders[id] = uploader;

                function handleStatus(file) {
                    var actionClass;

                    if (file.status == plupload.DONE) {
                        actionClass = 'plupload_done';
                    }

                    if (file.status == plupload.FAILED) {
                        actionClass = 'plupload_failed';
                    }

                    if (file.status == plupload.QUEUED) {
                        actionClass = 'plupload_delete';
                    }

                    if (file.status == plupload.UPLOADING) {
                        actionClass = 'plupload_uploading';
                    }

                    var icon = $('#' + file.id).attr('class', actionClass).find('a').css('display', 'block');
                    if (file.hint) {
                        icon.attr('title', file.hint);    
                    }
                }

                function updateTotalProgress() {
                    $('span.plupload_total_status', target).html(uploader.total.percent + '%');
                    $('div.plupload_progress_bar', target).css('width', uploader.total.percent + '%');
                    $('span.plupload_upload_status', target).html(
                        o.sprintf(_('Uploaded %d/%d files'), uploader.total.uploaded, uploader.files.length)
                    );
                }

                function updateList() {
                    var fileList = $('ul.plupload_filelist', target).html(''), inputCount = 0, inputHTML;

                    $.each(uploader.files, function(i, file) {
                        inputHTML = '';

                        if (file.status == plupload.DONE) {
                            if (file.target_name) {
                                inputHTML += '<input type="hidden" name="' + id + '_' + inputCount + '_tmpname" value="' + plupload.xmlEncode(file.target_name) + '" />';
                            }

                            inputHTML += '<input type="hidden" name="' + id + '_' + inputCount + '_name" value="' + plupload.xmlEncode(file.name) + '" />';
                            inputHTML += '<input type="hidden" name="' + id + '_' + inputCount + '_status" value="' + (file.status == plupload.DONE ? 'done' : 'failed') + '" />';
    
                            inputCount++;

                            $('#' + id + '_count').val(inputCount);
                        }

                        fileList.append(
                            '<li id="' + file.id + '">' +
                                '<div class="plupload_file_name"><span>' + file.name + '</span></div>' +
                                '<div class="plupload_file_action"><a href="#"></a></div>' +
                                '<div class="plupload_file_status">' + file.percent + '%</div>' +
                                '<div class="plupload_file_size">' + plupload.formatSize(file.size) + '</div>' +
                                '<div class="plupload_clearer">&nbsp;</div>' +
                                inputHTML +
                            '</li>'
                        );

                        handleStatus(file);

                        $('#' + file.id + '.plupload_delete a').click(function(e) {
                            $('#' + file.id).remove();
                            uploader.removeFile(file);

                            e.preventDefault();
                        });
                    });

                    $('span.plupload_total_file_size', target).html(plupload.formatSize(uploader.total.size));

                    if (uploader.total.queued === 0) {
                        $('span.plupload_add_text', target).html(_('Add Files'));
                    } else {
                        $('span.plupload_add_text', target).html(o.sprintf(_('%d files queued'), uploader.total.queued));
                    }

                    $('a.plupload_start', target).toggleClass('plupload_disabled', uploader.files.length == (uploader.total.uploaded + uploader.total.failed));

                    // Scroll to end of file list
                    fileList[0].scrollTop = fileList[0].scrollHeight;

                    updateTotalProgress();

                    // Re-add drag message if there is no files
                    if (!uploader.files.length && uploader.features.dragdrop && uploader.settings.dragdrop) {
                        $('#' + id + '_filelist').append('<li class="plupload_droptext">' + _("Drag files here.") + '</li>');
                    }
                }

                function destroy() {
                    delete uploaders[id];
                    uploader.destroy();
                    target.html(contents_bak);
                    uploader = target = contents_bak = null;
                }

                uploader.bind("UploadFile", function(up, file) {
                    $('#' + file.id).addClass('plupload_current_file');
                });

                uploader.bind('Init', function(up, res) {
                    // Enable rename support
                    if (!settings.unique_names && settings.rename) {
                        target.on('click', '#' + id + '_filelist div.plupload_file_name span', function(e) {
                            var targetSpan = $(e.target), file, parts, name, ext = "";

                            // Get file name and split out name and extension
                            file = up.getFile(targetSpan.parents('li')[0].id);
                            name = file.name;
                            parts = /^(.+)(\.[^.]+)$/.exec(name);
                            if (parts) {
                                name = parts[1];
                                ext = parts[2];
                            }

                            // Display input element
                            targetSpan.hide().after('<input type="text" />');
                            targetSpan.next().val(name).focus().blur(function() {
                                targetSpan.show().next().remove();
                            }).keydown(function(e) {
                                var targetInput = $(this);

                                if (e.keyCode == 13) {
                                    e.preventDefault();

                                    // Rename file and glue extension back on
                                    file.name = targetInput.val() + ext;
                                    targetSpan.html(file.name);
                                    targetInput.blur();
                                }
                            });
                        });
                    }

                    $('#' + id + '_container').attr('title', 'Using runtime: ' + res.runtime);

                    $('a.plupload_start', target).click(function(e) {
                        if (!$(this).hasClass('plupload_disabled')) {
                            uploader.start();
                        }

                        e.preventDefault();
                    });

                    $('a.plupload_stop', target).click(function(e) {
                        e.preventDefault();
                        uploader.stop();
                    });

                    $('a.plupload_start', target).addClass('plupload_disabled');
                });

                uploader.bind("Error", function(up, err) {
                    var file = err.file, message;

                    if (file) {
                        message = err.message;

                        if (err.details) {
                            message += " (" + err.details + ")";
                        }

                        if (err.code == plupload.FILE_SIZE_ERROR) {
                            alert(_("Error: File too large:") + " " + file.name);
                        }

                        if (err.code == plupload.FILE_EXTENSION_ERROR) {
                            alert(_("Error: Invalid file extension:") + " " + file.name);
                        }
                        
                        file.hint = message;
                        $('#' + file.id).attr('class', 'plupload_failed').find('a').css('display', 'block').attr('title', message);
                    }

                    if (err.code === plupload.INIT_ERROR) {
                        setTimeout(function() {
                            destroy();
                        }, 1);
                    }
                });

                uploader.bind("PostInit", function(up) {
                    // features are populated only after input components are fully instantiated
                    if (up.settings.dragdrop && up.features.dragdrop) {
                        $('#' + id + '_filelist').append('<li class="plupload_droptext">' + _("Drag files here.") + '</li>');
                    }
                });

                uploader.init();

                uploader.bind('StateChanged', function() {
                    if (uploader.state === plupload.STARTED) {
                        $('li.plupload_delete a,div.plupload_buttons', target).hide();
                        uploader.disableBrowse(true);

                        $('span.plupload_upload_status,div.plupload_progress,a.plupload_stop', target).css('display', 'block');
                        $('span.plupload_upload_status', target).html('Uploaded ' + uploader.total.uploaded + '/' + uploader.files.length + ' files');

                        if (settings.multiple_queues) {
                            $('span.plupload_total_status,span.plupload_total_file_size', target).show();
                        }
                    } else {
                        updateList();
                        $('a.plupload_stop,div.plupload_progress', target).hide();
                        $('a.plupload_delete', target).css('display', 'block');

                        if (settings.multiple_queues && uploader.total.uploaded + uploader.total.failed == uploader.files.length) {
                            $(".plupload_buttons,.plupload_upload_status", target).css("display", "inline");
                            uploader.disableBrowse(false);

                            $(".plupload_start", target).addClass("plupload_disabled");
                            $('span.plupload_total_status,span.plupload_total_file_size', target).hide();
                        }
                    }
                });

                uploader.bind('FilesAdded', updateList);

                uploader.bind('FilesRemoved', function() {
                    // since the whole file list is redrawn for every change in the queue
                    // we need to scroll back to the file removal point to avoid annoying
                    // scrolling to the bottom bug (see #926)
                    var scrollTop = $('#' + id + '_filelist').scrollTop();
                    updateList();
                    $('#' + id + '_filelist').scrollTop(scrollTop);
                });

                uploader.bind('FileUploaded', function(up, file) {
                    handleStatus(file);
                });

                uploader.bind("UploadProgress", function(up, file) {
                    // Set file specific progress
                    $('#' + file.id + ' div.plupload_file_status', target).html(file.percent + '%');

                    handleStatus(file);
                    updateTotalProgress();
                });

                // Call setup function
                if (settings.setup) {
                    settings.setup(uploader);
                }
            });

            return this;
        } else {
            // Get uploader instance for specified element
            return uploaders[$(this[0]).attr('id')];
        }
    };
})(jQuery, mOxie);