Added delete attachment feature.
This commit is contained in:
parent
7c2e588c33
commit
e7fb98380a
8 changed files with 399 additions and 19 deletions
|
@ -711,6 +711,75 @@ class App.ControllerForm extends App.Controller
|
|||
mode: attribute.type
|
||||
maxlength: attribute.maxlength
|
||||
)
|
||||
if attribute.upload
|
||||
item.append( $( App.view('generic/attachment')( attribute: attribute ) ) )
|
||||
|
||||
renderAttachment = (file) =>
|
||||
item.find('.attachments').append( App.view('generic/attachment_item')(
|
||||
fileName: file.filename
|
||||
fileSize: @humanFileSize(file.size)
|
||||
store_id: file.store_id
|
||||
))
|
||||
item.on(
|
||||
'click'
|
||||
"[data-id=#{file.store_id}]", (e) =>
|
||||
@attachments = _.filter(
|
||||
@attachments,
|
||||
(item) ->
|
||||
return if item.id isnt file.store_id
|
||||
item
|
||||
)
|
||||
store_id = $(e.currentTarget).data('id')
|
||||
App.Ajax.request(
|
||||
type: 'DELETE'
|
||||
url: App.Config.get('api_path') + '/ticket_attachment_upload'
|
||||
data: JSON.stringify( { store_id: store_id } ),
|
||||
processData: false
|
||||
success: (data, status, xhr) =>
|
||||
)
|
||||
$(e.currentTarget).closest('.attachment').empty()
|
||||
)
|
||||
|
||||
@attachments = []
|
||||
@progressBar = item.find('.attachmentUpload-progressBar')
|
||||
@progressText = item.find('.js-percentage')
|
||||
@attachmentPlaceholder = item.find('.attachmentPlaceholder')
|
||||
@attachmentUpload = item.find('.attachmentUpload')
|
||||
@attachmentsHolder = item.find('.attachments')
|
||||
html5Upload.initialize(
|
||||
uploadUrl: App.Config.get('api_path') + '/ticket_attachment_upload',
|
||||
dropContainer: item.find( ".attachmentPlaceholder" ).get(0),
|
||||
inputField: item.find( "input" ).get(0),
|
||||
key: 'File',
|
||||
data: { form_id: @form_id },
|
||||
maxSimultaneousUploads: 2,
|
||||
onFileAdded: (file) =>
|
||||
|
||||
@attachmentPlaceholder.addClass('hide')
|
||||
@attachmentUpload.removeClass('hide')
|
||||
|
||||
file.on(
|
||||
# Called after received response from the server
|
||||
onCompleted: (response) =>
|
||||
|
||||
response = JSON.parse(response)
|
||||
@attachments.push response.data
|
||||
|
||||
@attachmentPlaceholder.removeClass('hide')
|
||||
@attachmentUpload.addClass('hide')
|
||||
|
||||
renderAttachment(response.data)
|
||||
console.log('upload complete', response.data )
|
||||
|
||||
# Called during upload progress, first parameter
|
||||
# is decimal value from 0 to 100.
|
||||
onProgress: (progress, fileSize, uploadedBytes) =>
|
||||
@progressBar.width(progress + "%")
|
||||
@progressText.text(progress)
|
||||
console.log('uploadProgress ', progress)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
# textarea
|
||||
else if attribute.tag is 'textarea'
|
||||
|
|
|
@ -1,3 +1,18 @@
|
|||
###
|
||||
App.Ajax.request(
|
||||
id: 'search'
|
||||
type: 'GET'
|
||||
url: url
|
||||
data:
|
||||
term: term
|
||||
processData: true,
|
||||
success: (data, status, xhr) =>
|
||||
console.log(data, status)
|
||||
error: (xhr, statusText, error) =>
|
||||
console.log(statusText, error)
|
||||
)
|
||||
###
|
||||
|
||||
class App.Ajax
|
||||
_instance = undefined # Must be declared here to force the closure on the class
|
||||
@request: (args) -> # Must be a static method
|
||||
|
|
229
app/assets/javascripts/app/lib/base/html5Upload.js
Normal file
229
app/assets/javascripts/app/lib/base/html5Upload.js
Normal file
|
@ -0,0 +1,229 @@
|
|||
/*jslint unparam: true, browser: true, devel: true */
|
||||
/*global define*/
|
||||
|
||||
|
||||
var module = {},
|
||||
noop = function () { },
|
||||
console = window.console || { log: noop },
|
||||
supportsFileApi;
|
||||
|
||||
// Upload manager constructor:
|
||||
function UploadManager(options) {
|
||||
var self = this;
|
||||
self.dropContainer = options.dropContainer;
|
||||
self.inputField = options.inputField;
|
||||
self.uploadsQueue = [];
|
||||
self.activeUploads = 0;
|
||||
self.data = options.data;
|
||||
self.key = options.key;
|
||||
self.maxSimultaneousUploads = options.maxSimultaneousUploads || -1;
|
||||
self.onFileAdded = options.onFileAdded || noop;
|
||||
self.uploadUrl = options.uploadUrl;
|
||||
self.onFileAddedProxy = function (upload) {
|
||||
console.log('Event: onFileAdded, file: ' + upload.fileName);
|
||||
self.onFileAdded(upload);
|
||||
};
|
||||
|
||||
self.initialize();
|
||||
}
|
||||
|
||||
// FileUpload proxy class:
|
||||
function FileUpload(file) {
|
||||
var self = this;
|
||||
|
||||
self.file = file;
|
||||
self.fileName = file.name;
|
||||
self.fileSize = file.size;
|
||||
self.uploadSize = file.size;
|
||||
self.uploadedBytes = 0;
|
||||
self.eventHandlers = {};
|
||||
self.events = {
|
||||
onProgress: function (fileSize, uploadedBytes) {
|
||||
var progress = uploadedBytes / fileSize * 100;
|
||||
console.log('Event: upload onProgress, progress = ' + progress + ', fileSize = ' + fileSize + ', uploadedBytes = ' + uploadedBytes);
|
||||
(self.eventHandlers.onProgress || noop)(progress, fileSize, uploadedBytes);
|
||||
},
|
||||
onStart: function () {
|
||||
console.log('Event: upload onStart');
|
||||
(self.eventHandlers.onStart || noop)();
|
||||
},
|
||||
onCompleted: function (data) {
|
||||
console.log('Event: upload onCompleted, data = ' + data);
|
||||
file = null;
|
||||
(self.eventHandlers.onCompleted || noop)(data);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
FileUpload.prototype = {
|
||||
on: function (eventHandlers) {
|
||||
this.eventHandlers = eventHandlers;
|
||||
}
|
||||
};
|
||||
|
||||
UploadManager.prototype = {
|
||||
|
||||
initialize: function () {
|
||||
console.log('Initializing upload manager');
|
||||
var manager = this,
|
||||
dropContainer = manager.dropContainer,
|
||||
inputField = manager.inputField,
|
||||
cancelEvent = function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
if (dropContainer) {
|
||||
manager.on(dropContainer, 'dragover', cancelEvent);
|
||||
manager.on(dropContainer, 'dragenter', cancelEvent);
|
||||
manager.on(dropContainer, 'drop', function (e) {
|
||||
cancelEvent(e);
|
||||
manager.processFiles(e.dataTransfer.files);
|
||||
});
|
||||
}
|
||||
|
||||
if (inputField) {
|
||||
manager.on(inputField, 'change', function () {
|
||||
manager.processFiles(this.files);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
processFiles: function (files) {
|
||||
console.log('Processing files: ' + files.length);
|
||||
var manager = this,
|
||||
len = files.length,
|
||||
file,
|
||||
upload,
|
||||
i;
|
||||
|
||||
for (i = 0; i < len; i += 1) {
|
||||
file = files[i];
|
||||
if (file.size === 0) {
|
||||
alert('Files with files size zero cannot be uploaded or multiple file uploads are not supported by your browser');
|
||||
break;
|
||||
}
|
||||
|
||||
upload = new FileUpload(file);
|
||||
manager.uploadFile(upload);
|
||||
}
|
||||
},
|
||||
|
||||
uploadFile: function (upload) {
|
||||
var manager = this;
|
||||
|
||||
manager.onFileAdded(upload);
|
||||
|
||||
// Queue upload if maximum simultaneous uploads reached:
|
||||
if (manager.activeUploads === manager.maxSimultaneousUploads) {
|
||||
console.log('Queue upload: ' + upload.fileName);
|
||||
manager.uploadsQueue.push(upload);
|
||||
return;
|
||||
}
|
||||
|
||||
manager.ajaxUpload(upload);
|
||||
},
|
||||
|
||||
ajaxUpload: function (upload) {
|
||||
var manager = this,
|
||||
xhr,
|
||||
formData,
|
||||
fileName,
|
||||
file = upload.file,
|
||||
prop,
|
||||
data = manager.data,
|
||||
key = manager.key || 'file';
|
||||
|
||||
console.log('Beging upload: ' + upload.fileName);
|
||||
manager.activeUploads += 1;
|
||||
|
||||
xhr = new window.XMLHttpRequest();
|
||||
formData = new window.FormData();
|
||||
fileName = file.name;
|
||||
|
||||
xhr.open('POST', manager.uploadUrl);
|
||||
|
||||
// Triggered when upload starts:
|
||||
xhr.upload.onloadstart = function () {
|
||||
// File size is not reported during start!
|
||||
console.log('Upload started: ' + fileName);
|
||||
upload.events.onStart();
|
||||
};
|
||||
|
||||
// Triggered many times during upload:
|
||||
xhr.upload.onprogress = function (event) {
|
||||
if (!event.lengthComputable) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update file size because it might be bigger than reported by the fileSize:
|
||||
upload.events.onProgress(event.total, event.loaded);
|
||||
};
|
||||
|
||||
// Triggered when upload is completed:
|
||||
xhr.onload = function (event) {
|
||||
console.log('Upload completed: ' + fileName);
|
||||
|
||||
// Reduce number of active uploads:
|
||||
manager.activeUploads -= 1;
|
||||
|
||||
upload.events.onCompleted(event.target.responseText);
|
||||
|
||||
// Check if there are any uploads left in a queue:
|
||||
if (manager.uploadsQueue.length) {
|
||||
manager.ajaxUpload(manager.uploadsQueue.shift());
|
||||
}
|
||||
};
|
||||
|
||||
// Triggered when upload fails:
|
||||
xhr.onerror = function () {
|
||||
console.log('Upload failed: ', upload.fileName);
|
||||
};
|
||||
|
||||
// Append additional data if provided:
|
||||
if (data) {
|
||||
for (prop in data) {
|
||||
if (data.hasOwnProperty(prop)) {
|
||||
console.log('Adding data: ' + prop + ' = ' + data[prop]);
|
||||
formData.append(prop, data[prop]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Append file data:
|
||||
formData.append(key, file);
|
||||
|
||||
// Initiate upload:
|
||||
xhr.send(formData);
|
||||
},
|
||||
|
||||
// Event handlers:
|
||||
on: function (element, eventName, handler) {
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
if (element.addEventListener) {
|
||||
element.addEventListener(eventName, handler, false);
|
||||
} else if (element.attachEvent) {
|
||||
element.attachEvent('on' + eventName, handler);
|
||||
} else {
|
||||
element['on' + eventName] = handler;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.fileApiSupported = function () {
|
||||
if (typeof supportsFileApi !== 'boolean') {
|
||||
var input = document.createElement("input");
|
||||
input.setAttribute("type", "file");
|
||||
supportsFileApi = !!input.files;
|
||||
}
|
||||
|
||||
return supportsFileApi;
|
||||
};
|
||||
|
||||
module.initialize = function (options) {
|
||||
return new UploadManager(options);
|
||||
};
|
||||
|
||||
window.html5Upload = module;
|
25
app/assets/javascripts/app/views/generic/attachment.jst.eco
Normal file
25
app/assets/javascripts/app/views/generic/attachment.jst.eco
Normal file
|
@ -0,0 +1,25 @@
|
|||
<div class="attachments"></div>
|
||||
<div class="u-unclickable">
|
||||
<div class="attachmentPlaceholder">
|
||||
<span class="attachmentPlaceholder-inputHolder u-highlight u-clickable">
|
||||
Dateien wählen..
|
||||
<input multiple="multiple" type="file" name="file" style="position: absolute; right: 0px; top: 0px; font-family: Arial; font-size: 118px; margin: 0px; padding: 0px; cursor: pointer; opacity: 0;">
|
||||
</span>
|
||||
</div>
|
||||
<div class="attachmentUpload hide u-clickable">
|
||||
<div class="horizontal">
|
||||
<div class="u-highlight">
|
||||
<%- @T(' Uploading ') %> (<span class="js-percentage">0</span>%) ...
|
||||
</div>
|
||||
<div class="attachmentUpload-cancel align-right js-cancel u-clickable">
|
||||
<div class="delete icon"></div><%- @T('Cancel Upload') %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="attachmentUpload-progressBar" style="width: 0%"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fit dropArea">
|
||||
<div class="dropArea-inner fit centered">
|
||||
<%- @T('Drop Files here') %>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,7 @@
|
|||
<div class="attachment horizontal">
|
||||
<div class="attachment-name u-highlight"><%- @fileName %></div>
|
||||
<div class="attachment-size"><%- @fileSize %></div>
|
||||
<div class="attachment-delete js-delete align-right u-clickable" data-id="<%= @store_id %>">
|
||||
<div class="delete icon"></div><%- @T('Delete File') %>
|
||||
</div>
|
||||
</div>
|
|
@ -64,16 +64,25 @@ class TicketArticlesController < ApplicationController
|
|||
head :ok
|
||||
end
|
||||
|
||||
# POST /ticket_attachment/new
|
||||
def attachment_new
|
||||
# DELETE /ticket_attachment_upload
|
||||
def ticket_attachment_upload_delete
|
||||
Store.remove_item( params[:store_id] )
|
||||
|
||||
# return result
|
||||
render :json => {
|
||||
:success => true,
|
||||
}
|
||||
end
|
||||
|
||||
# POST /ticket_attachment_upload
|
||||
def ticket_attachment_upload_add
|
||||
|
||||
# store file
|
||||
# content_type = request.content_type
|
||||
content_type = request[:content_type]
|
||||
puts 'content_type: ' + content_type.inspect
|
||||
file = params[:File]
|
||||
content_type = file.content_type
|
||||
if !content_type || content_type == 'application/octet-stream'
|
||||
if MIME::Types.type_for(params[:qqfile]).first
|
||||
content_type = MIME::Types.type_for(params[:qqfile]).first.content_type
|
||||
if MIME::Types.type_for(file.original_filename).first
|
||||
content_type = MIME::Types.type_for(file.original_filename).first.content_type
|
||||
else
|
||||
content_type = 'application/octet-stream'
|
||||
end
|
||||
|
@ -81,17 +90,22 @@ class TicketArticlesController < ApplicationController
|
|||
headers_store = {
|
||||
'Content-Type' => content_type
|
||||
}
|
||||
Store.add(
|
||||
store = Store.add(
|
||||
:object => 'UploadCache',
|
||||
:o_id => params[:form_id],
|
||||
:data => request.body.read,
|
||||
:filename => params[:qqfile],
|
||||
:data => file.read,
|
||||
:filename => file.original_filename,
|
||||
:preferences => headers_store
|
||||
)
|
||||
|
||||
# return result
|
||||
render :json => {
|
||||
:success => true,
|
||||
:data => {
|
||||
:store_id => store.id,
|
||||
:filename => file.original_filename,
|
||||
:size => store.size,
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ returns
|
|||
# store meta data
|
||||
store = Store.create(data)
|
||||
|
||||
true
|
||||
store
|
||||
end
|
||||
|
||||
=begin
|
||||
|
@ -87,7 +87,7 @@ returns
|
|||
|
||||
=begin
|
||||
|
||||
remove an attachment to storage
|
||||
remove attachments of object from storage
|
||||
|
||||
result = Store.remove(
|
||||
:object => 'Ticket::Article',
|
||||
|
@ -109,16 +109,36 @@ returns
|
|||
stores.each do |store|
|
||||
|
||||
# check backend for references
|
||||
files = Store.where( :store_file_id => store.store_file_id )
|
||||
if files.count == 1 && files.first.id == store.id
|
||||
Store::File.find( store.store_file_id ).destroy
|
||||
end
|
||||
|
||||
store.destroy
|
||||
Store.remove_item( store.id )
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
remove one attachment from storage
|
||||
|
||||
result = Store.remove_item(store_id)
|
||||
|
||||
returns
|
||||
|
||||
result = true
|
||||
|
||||
=end
|
||||
|
||||
def self.remove_item(store_id)
|
||||
|
||||
# check backend for references
|
||||
store = Store.find(store_id)
|
||||
files = Store.where( :store_file_id => store.store_file_id )
|
||||
if files.count == 1 && files.first.id == store.id
|
||||
Store::File.find( store.store_file_id ).destroy
|
||||
end
|
||||
|
||||
store.destroy
|
||||
return true
|
||||
end
|
||||
|
||||
def content
|
||||
file = Store::File.where( :id => self.store_file_id ).first
|
||||
if !file
|
||||
|
|
|
@ -35,7 +35,8 @@ Zammad::Application.routes.draw do
|
|||
match api_path + '/ticket_articles', :to => 'ticket_articles#create', :via => :post
|
||||
match api_path + '/ticket_articles/:id', :to => 'ticket_articles#update', :via => :put
|
||||
match api_path + '/ticket_attachment/:ticket_id/:article_id/:id', :to => 'ticket_articles#attachment', :via => :get
|
||||
match api_path + '/ticket_attachment_new', :to => 'ticket_articles#attachment_new', :via => :post
|
||||
match api_path + '/ticket_attachment_upload', :to => 'ticket_articles#ticket_attachment_upload_add', :via => :post
|
||||
match api_path + '/ticket_attachment_upload', :to => 'ticket_articles#ticket_attachment_upload_delete', :via => :delete
|
||||
match api_path + '/ticket_article_plain/:id', :to => 'ticket_articles#article_plain', :via => :get
|
||||
|
||||
end
|
Loading…
Reference in a new issue