Chris@1464: /* Redmine - project management software Chris@1494: Copyright (C) 2006-2014 Jean-Philippe Lang */ Chris@1464: Chris@1464: function addFile(inputEl, file, eagerUpload) { Chris@1464: Chris@1464: if ($('#attachments_fields').children().length < 10) { Chris@1464: Chris@1464: var attachmentId = addFile.nextAttachmentId++; Chris@1464: Chris@1464: var fileSpan = $('', { id: 'attachments_' + attachmentId }); Chris@1464: Chris@1464: fileSpan.append( Chris@1464: $('', { type: 'text', 'class': 'filename readonly', name: 'attachments[' + attachmentId + '][filename]', readonly: 'readonly'} ).val(file.name), Chris@1464: $('', { type: 'text', 'class': 'description', name: 'attachments[' + attachmentId + '][description]', maxlength: 255, placeholder: $(inputEl).data('description-placeholder') } ).toggle(!eagerUpload), Chris@1464: $(' ').attr({ href: "#", 'class': 'remove-upload' }).click(removeFile).toggle(!eagerUpload) Chris@1464: ).appendTo('#attachments_fields'); Chris@1464: Chris@1464: if(eagerUpload) { Chris@1464: ajaxUpload(file, attachmentId, fileSpan, inputEl); Chris@1464: } Chris@1464: Chris@1464: return attachmentId; Chris@1464: } Chris@1464: return null; Chris@1464: } Chris@1464: Chris@1464: addFile.nextAttachmentId = 1; Chris@1464: Chris@1464: function ajaxUpload(file, attachmentId, fileSpan, inputEl) { Chris@1464: Chris@1464: function onLoadstart(e) { Chris@1464: fileSpan.removeClass('ajax-waiting'); Chris@1464: fileSpan.addClass('ajax-loading'); Chris@1464: $('input:submit', $(this).parents('form')).attr('disabled', 'disabled'); Chris@1464: } Chris@1464: Chris@1464: function onProgress(e) { Chris@1464: if(e.lengthComputable) { Chris@1464: this.progressbar( 'value', e.loaded * 100 / e.total ); Chris@1464: } Chris@1464: } Chris@1464: Chris@1464: function actualUpload(file, attachmentId, fileSpan, inputEl) { Chris@1464: Chris@1464: ajaxUpload.uploading++; Chris@1464: Chris@1464: uploadBlob(file, $(inputEl).data('upload-path'), attachmentId, { Chris@1464: loadstartEventHandler: onLoadstart.bind(progressSpan), Chris@1464: progressEventHandler: onProgress.bind(progressSpan) Chris@1464: }) Chris@1464: .done(function(result) { Chris@1464: progressSpan.progressbar( 'value', 100 ).remove(); Chris@1464: fileSpan.find('input.description, a').css('display', 'inline-block'); Chris@1464: }) Chris@1464: .fail(function(result) { Chris@1464: progressSpan.text(result.statusText); Chris@1464: }).always(function() { Chris@1464: ajaxUpload.uploading--; Chris@1464: fileSpan.removeClass('ajax-loading'); Chris@1464: var form = fileSpan.parents('form'); Chris@1464: if (form.queue('upload').length == 0 && ajaxUpload.uploading == 0) { Chris@1464: $('input:submit', form).removeAttr('disabled'); Chris@1464: } Chris@1464: form.dequeue('upload'); Chris@1464: }); Chris@1464: } Chris@1464: Chris@1464: var progressSpan = $('
').insertAfter(fileSpan.find('input.filename')); Chris@1464: progressSpan.progressbar(); Chris@1464: fileSpan.addClass('ajax-waiting'); Chris@1464: Chris@1464: var maxSyncUpload = $(inputEl).data('max-concurrent-uploads'); Chris@1464: Chris@1464: if(maxSyncUpload == null || maxSyncUpload <= 0 || ajaxUpload.uploading < maxSyncUpload) Chris@1464: actualUpload(file, attachmentId, fileSpan, inputEl); Chris@1464: else Chris@1464: $(inputEl).parents('form').queue('upload', actualUpload.bind(this, file, attachmentId, fileSpan, inputEl)); Chris@1464: } Chris@1464: Chris@1464: ajaxUpload.uploading = 0; Chris@1464: Chris@1464: function removeFile() { Chris@1464: $(this).parent('span').remove(); Chris@1464: return false; Chris@1464: } Chris@1464: Chris@1464: function uploadBlob(blob, uploadUrl, attachmentId, options) { Chris@1464: Chris@1464: var actualOptions = $.extend({ Chris@1464: loadstartEventHandler: $.noop, Chris@1464: progressEventHandler: $.noop Chris@1464: }, options); Chris@1464: Chris@1464: uploadUrl = uploadUrl + '?attachment_id=' + attachmentId; Chris@1464: if (blob instanceof window.File) { Chris@1464: uploadUrl += '&filename=' + encodeURIComponent(blob.name); Chris@1464: } Chris@1464: Chris@1464: return $.ajax(uploadUrl, { Chris@1464: type: 'POST', Chris@1464: contentType: 'application/octet-stream', Chris@1516: beforeSend: function(jqXhr, settings) { Chris@1464: jqXhr.setRequestHeader('Accept', 'application/js'); Chris@1516: // attach proper File object Chris@1516: settings.data = blob; Chris@1464: }, Chris@1464: xhr: function() { Chris@1464: var xhr = $.ajaxSettings.xhr(); Chris@1464: xhr.upload.onloadstart = actualOptions.loadstartEventHandler; Chris@1464: xhr.upload.onprogress = actualOptions.progressEventHandler; Chris@1464: return xhr; Chris@1464: }, Chris@1464: data: blob, Chris@1464: cache: false, Chris@1464: processData: false Chris@1464: }); Chris@1464: } Chris@1464: Chris@1464: function addInputFiles(inputEl) { Chris@1464: var clearedFileInput = $(inputEl).clone().val(''); Chris@1464: Chris@1464: if (inputEl.files) { Chris@1464: // upload files using ajax Chris@1464: uploadAndAttachFiles(inputEl.files, inputEl); Chris@1464: $(inputEl).remove(); Chris@1464: } else { Chris@1464: // browser not supporting the file API, upload on form submission Chris@1464: var attachmentId; Chris@1464: var aFilename = inputEl.value.split(/\/|\\/); Chris@1464: attachmentId = addFile(inputEl, { name: aFilename[ aFilename.length - 1 ] }, false); Chris@1464: if (attachmentId) { Chris@1464: $(inputEl).attr({ name: 'attachments[' + attachmentId + '][file]', style: 'display:none;' }).appendTo('#attachments_' + attachmentId); Chris@1464: } Chris@1464: } Chris@1464: Chris@1516: clearedFileInput.insertAfter('#attachments_fields').on('change', function(){addInputFiles(this);}); Chris@1464: } Chris@1464: Chris@1464: function uploadAndAttachFiles(files, inputEl) { Chris@1464: Chris@1464: var maxFileSize = $(inputEl).data('max-file-size'); Chris@1464: var maxFileSizeExceeded = $(inputEl).data('max-file-size-message'); Chris@1464: Chris@1464: var sizeExceeded = false; Chris@1464: $.each(files, function() { Chris@1464: if (this.size && maxFileSize != null && this.size > parseInt(maxFileSize)) {sizeExceeded=true;} Chris@1464: }); Chris@1464: if (sizeExceeded) { Chris@1464: window.alert(maxFileSizeExceeded); Chris@1464: } else { Chris@1464: $.each(files, function() {addFile(inputEl, this, true);}); Chris@1464: } Chris@1464: } Chris@1464: Chris@1464: function handleFileDropEvent(e) { Chris@1464: Chris@1464: $(this).removeClass('fileover'); Chris@1464: blockEventPropagation(e); Chris@1464: Chris@1464: if ($.inArray('Files', e.dataTransfer.types) > -1) { Chris@1464: uploadAndAttachFiles(e.dataTransfer.files, $('input:file.file_selector')); Chris@1464: } Chris@1464: } Chris@1464: Chris@1464: function dragOverHandler(e) { Chris@1464: $(this).addClass('fileover'); Chris@1464: blockEventPropagation(e); Chris@1464: } Chris@1464: Chris@1464: function dragOutHandler(e) { Chris@1464: $(this).removeClass('fileover'); Chris@1464: blockEventPropagation(e); Chris@1464: } Chris@1464: Chris@1464: function setupFileDrop() { Chris@1464: if (window.File && window.FileList && window.ProgressEvent && window.FormData) { Chris@1464: Chris@1464: $.event.fixHooks.drop = { props: [ 'dataTransfer' ] }; Chris@1464: Chris@1464: $('form div.box').has('input:file').each(function() { Chris@1464: $(this).on({ Chris@1464: dragover: dragOverHandler, Chris@1464: dragleave: dragOutHandler, Chris@1464: drop: handleFileDropEvent Chris@1464: }); Chris@1464: }); Chris@1464: } Chris@1464: } Chris@1464: Chris@1464: $(document).ready(setupFileDrop);