Add AJAX file delete support and upload notifications, make UI nicer

This commit is contained in:
2018-04-26 18:03:04 +10:00
parent 983a32aba8
commit f4c21006de
4 changed files with 235 additions and 42 deletions

View File

@ -1,40 +1,193 @@
$(function () {
// un-set "name" attributes to avoid submitting to server
$(".fileupload").removeAttr('name');
// set up all file inputs for jQuery-fileUpload
$(".fileupload").fileupload({
dataType: 'json',
paramName: 'file',
done: function (e, data) {
// process server response
if (data.result.is_valid) {
var $field = $('#id_' + $(this).attr('data-field')),
$ul = $(this).siblings('ul'),
$li = $("<li>"),
$remove = $('<a title="remove"></a>'),
ids = $field.val().split(",").filter(function (v) {
return v != '';
});
class MultipleFilesWidget {
constructor(div) {
this.root = div
this.fieldName = this.root.getAttribute('data-field')
this.fileList = []
this.element = {
list: this.root.querySelector('.filewidget--list'),
uploadButton: this.root.querySelector('.filewidget--input'),
field: document.querySelector(`[name="${this.fieldName}"]`)
}
ids.push(data.result.id);
if (!$ul.length) {
$ul = $("<ul>").insertAfter(this);
const self = this
// Set up jquery-fileupload
$(this.element.uploadButton).fileupload({
dataType: 'json',
paramName: 'file',
done: function (e, data) {
// process server response
if (!data.result.is_valid) {
throw new Error('Server sent us invalid data!')
}
$li.text(data.result.name);
self.fileFinished.bind(self)(data.result)
},
add: function (e, data) {
$.each(data.files, (index, file) => {
self.addFile.bind(self)(null, file.name, self.setFileInProgress.bind(self))
});
// FIXME AJAXify
//$remove.attr('href', '/files/' + data.result.id + '/delete/');
//$remove.append($('<i class="glyphicon glyphicon-remove"/>'));
//$li.append($remove);
data.process().done(function () {
data.submit()
})
}
})
$ul.append($li);
// Set up listening for restore
this.element.field.addEventListener('change', evt => {
let idList = evt.srcElement.value.split(",")
for (let id of idList) {
this.addFile(id, id, this.setFileDone.bind(this))
}
this.viewFileList()
})
}
$field.val(ids.toString());
addFile(id, name, stateFunc) {
console.log('addfile here!')
document.getElementById('case-study-form').dispatchEvent(new Event('dirty'))
let li = document.createElement('li')
li.className = 'filewidget--file'
li.innerHTML =
`<i class='filewidget--file--icon'></i> <span class='filewidget--file--name'></span>
<span class='filewidget--file--actions'></span>`
let file = {
root: li,
element: {
icon: li.querySelector('.filewidget--file--icon'),
name: li.querySelector('.filewidget--file--name'),
actions: li.querySelector('.filewidget--file--actions'),
},
name: name,
id: id || null,
}
stateFunc(file)
this.fileList.push(file)
this.viewFileList()
}
removeFile(file) {
// Remove file from display
this.element.list.removeChild(file.root)
// Remove file from fileList
this.fileList = this.fileList.filter(cmpFile => cmpFile === file)
}
fileFinished(serverResponse) {
// Using data, find the file
let file = this.fileList.filter(file => file.name === serverResponse.name)[0]
// Set the ID
file.id = serverResponse.id
// Set the file state now it's finished
this.setFileDone(file)
this.viewFileList()
// Do this last tho
this.updateFormField()
}
//
// Update the field that keeps track of file IDs
//
updateFormField() {
console.log("updateFormField", this)
let oldVal = this.element.field.value
this.element.field.value = this.fileList.filter(f => f.id != null)
.map(f => f.id)
.toString()
// Mark form as dirty
if (this.element.field.value !== oldVal) {
document.getElementById('case-study-form').dispatchEvent(new Event('dirty'))
}
}
//
// Manage the state of individual files in the widget
//
setFileState(file, state) {
file.element.icon.classList = `filewidget--file--icon ${state.iconClassList}`
file.element.name.innerText = state.fileName
file.element.actions.innerHTML = state.actions || ''
}
setFileInProgress(file) {
this.setFileState(file, {
iconClassList: 'fa fa-spinner fa-spin',
fileName: `${file.name} (uploading...)`
})
}
setFileDeletingInProgress(file) {
this.setFileState(file, {
iconClassList: 'fa fa-spinner fa-spin',
fileName: `Deleting...`
})
}
setFileDeletingFailed(file) {
this.setFileState(file, {
iconClassList: 'fa fa-exclamation-triangle',
fileName: `${file.name} (delete failed!)`
})
}
setFileDone(file) {
console.log("setFileDone", file)
this.setFileState(file, {
iconClassList: 'fa fa-file-o',
fileName: `${file.name}`,
actions: '<a title="Remove"><i class="fa fa-times"></i></a>'
})
file.element.actions.querySelector('a').addEventListener('click', () => {
this.setFileDeletingInProgress(file)
return fetch(`/files/delete/${file.id}/`, {
method: 'POST',
credentials: "same-origin",
headers: {
"X-CSRFToken": jQuery("[name=csrfmiddlewaretoken]").val(),
}
}).then(response => {
if (response.ok) {
this.removeFile(file)
} else {
this.setFileDeletingFailed(file)
}
})
})
}
//
// Redraw the file list
//
viewFileList() {
console.log("viewFileList", this)
for (let file of this.fileList) {
// Check if it's appended to the list
if (!this.element.list.contains(file.root)) {
this.element.list.appendChild(file.root)
}
}
});
});
}
}
$(function() {
window.official_project_documents = new MultipleFilesWidget(
document.querySelector('[data-field=official_project_documents_files]')
)
})