Anna Sidwell
5cc0b7cdc1
* Take the opportunity to reformat the source to not have so many hugely long lines. * Put a few things through gettext that weren't going through it before * Move 'Hold down Ctrl to select multiple files at once.' text to widget, out of help text for each individual control.
285 lines
8.3 KiB
JavaScript
285 lines
8.3 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() {
|
|
// Display OS-specific stuff
|
|
function os_class() {
|
|
if (navigator.platform.indexOf("Win") != -1) return "os-windows";
|
|
if (navigator.platform.indexOf("Mac") != -1) return "os-mac";
|
|
return "os-other";
|
|
}
|
|
|
|
document.querySelector('body').classList.add(os_class())
|
|
|
|
// Get the CSRF token for form submitting
|
|
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,
|
|
)
|
|
})
|