This involved turning the list of file IDs stored in the hidden text field into JSON.
275 lines
7.9 KiB
JavaScript
275 lines
7.9 KiB
JavaScript
class MultipleFilesWidget {
|
|
constructor(div, csrf_token) {
|
|
this.root = div
|
|
this.fieldName = this.root.getAttribute('data-field')
|
|
this.fileList = []
|
|
this.element = {
|
|
list: this.root.querySelector('.filewidget--list'),
|
|
fileInput: this.root.querySelector('.filewidget--input'),
|
|
field: document.querySelector(`[name="${this.fieldName}"]`)
|
|
}
|
|
this.csrf_token = csrf_token
|
|
this.status = {
|
|
IN_PROGRESS: 1,
|
|
DONE: 2,
|
|
}
|
|
this.endpoint = this.element.fileInput.getAttribute('data-url')
|
|
|
|
// Set up event listener for file upload button
|
|
this.element.fileInput.addEventListener('change', evt => {
|
|
const fileList = this.element.fileInput.files
|
|
for (let file of fileList) {
|
|
this.startUpload(file)
|
|
}
|
|
})
|
|
|
|
// If there is something in the field, we need to restore our state
|
|
if (this.element.field.value) {
|
|
this.rebuildListFromField()
|
|
}
|
|
|
|
// Set up listening for restore
|
|
this.element.field.addEventListener('change', evt => {
|
|
this.rebuildListFromField()
|
|
})
|
|
}
|
|
|
|
// file is a JS File object
|
|
startUpload(file) {
|
|
const fileName = file.name
|
|
|
|
let data = new FormData()
|
|
data.append('file', file)
|
|
|
|
fetch(this.endpoint, {
|
|
method: 'POST',
|
|
body: data,
|
|
headers: {
|
|
"X-CSRFToken": this.csrf_token,
|
|
},
|
|
})
|
|
.then(response => response.json())
|
|
.then(response => {
|
|
if (response.is_valid) {
|
|
response.originalName = fileName
|
|
this.fileFinished(response)
|
|
} else {
|
|
this.fileFailed(fileName, response.errors.toString())
|
|
}
|
|
})
|
|
.catch(err => {
|
|
this.fileFailed(fileName, err.toString())
|
|
})
|
|
|
|
this.addFile(null, file.name, this.status.IN_PROGRESS)
|
|
}
|
|
|
|
rebuildListFromField() {
|
|
const fieldContents = this.element.field.value
|
|
|
|
// Nothing to do
|
|
if (fieldContents === '') {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
let list = JSON.parse(fieldContents)
|
|
for (let file of list) {
|
|
this.addFile(file.id, file.name, this.status.DONE)
|
|
}
|
|
} catch (e) {
|
|
// Not JSON, let's parse as CSV
|
|
for (let id of fieldContents.split(",")) {
|
|
this.addFile(id, `Saved upload (id ${id})`, this.status.DONE)
|
|
}
|
|
}
|
|
|
|
this.viewFileList()
|
|
}
|
|
|
|
addFile(id, name, state) {
|
|
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,
|
|
}
|
|
|
|
switch (state) {
|
|
case this.status.DONE:
|
|
this.setFileDone(file)
|
|
break
|
|
|
|
case this.status.IN_PROGRESS:
|
|
this.setFileInProgress(file)
|
|
break
|
|
}
|
|
|
|
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(response) {
|
|
let file = this.fileList.filter(file => file.name === response.originalName)[0]
|
|
|
|
// Set the ID
|
|
file.id = response.id
|
|
file.name = response.name
|
|
|
|
// Set the file state now it's finished
|
|
this.setFileDone(file)
|
|
this.viewFileList()
|
|
|
|
// Do this last tho
|
|
this.updateFormField()
|
|
}
|
|
|
|
fileFailed(fileName, errors) {
|
|
console.error('File upload failed!')
|
|
console.error(errors)
|
|
|
|
// Using data, find the file
|
|
let file = this.fileList.filter(file => file.name === fileName)[0]
|
|
|
|
// Set the file state now it's finished
|
|
this.setFileFailed(file)
|
|
this.viewFileList()
|
|
}
|
|
|
|
//
|
|
// Update the field that keeps track of file IDs
|
|
//
|
|
|
|
updateFormField() {
|
|
let oldVal = this.element.field.value
|
|
let fileList = this.fileList.filter(f => f.id != null)
|
|
.map(f => ({
|
|
id: f.id,
|
|
name: f.name
|
|
}))
|
|
|
|
this.element.field.value = JSON.stringify(fileList)
|
|
|
|
// 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 || ''
|
|
}
|
|
|
|
setFileFailed(file) {
|
|
this.setFileState(file, {
|
|
iconClassList: 'fa fa-exclamation-triangle',
|
|
fileName: `FAILED: '${file.name}'`
|
|
})
|
|
}
|
|
|
|
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) {
|
|
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() {
|
|
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() {
|
|
let csrf_token = document.querySelector("[name=csrfmiddlewaretoken]").value
|
|
|
|
window.images = new MultipleFilesWidget(
|
|
document.querySelector('[data-field=images_files]'),
|
|
csrf_token,
|
|
)
|
|
window.official_project_documents = new MultipleFilesWidget(
|
|
document.querySelector('[data-field=official_project_documents_files]'),
|
|
csrf_token,
|
|
)
|
|
window.other_documents = new MultipleFilesWidget(
|
|
document.querySelector('[data-field=other_documents_files]'),
|
|
csrf_token,
|
|
)
|
|
window.shapefiles = new MultipleFilesWidget(
|
|
document.querySelector('[data-field=shapefiles_files]'),
|
|
csrf_token,
|
|
)
|
|
})
|