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