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}"]`) } 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) { self.fileFinished.bind(self)(data.result) } else { self.fileFailed.bind(self)(data) } }, add: function (e, data) { $.each(data.files, (index, file) => { self.addFile.bind(self)(null, file.name, self.setFileInProgress.bind(self)) }) data.process().done(function () { data.submit() }) }, progress: function(e, data) { console.log('progress event') console.log(e, data) } }) // 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() }) } rebuildListFromField() { let idList = this.element.field.value.split(",") for (let id of idList) { this.addFile(id, `Saved upload (id ${id})`, this.setFileDone.bind(this)) } this.viewFileList() } addFile(id, name, stateFunc) { let li = document.createElement('li') li.className = 'filewidget--file' li.innerHTML = ` ` 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() } fileFailed(serverResponse) { let errors = Object.values(serverResponse.result.errors).join(' ') console.error('File upload failed: ' + errors) console.error(serverResponse) // Using data, find the file let file = this.fileList.filter(file => file.name === serverResponse.files[0].name)[0] // Set the file state now it's finished this.setFileFailed(file, errors) this.viewFileList() } // // Update the field that keeps track of file IDs // updateFormField() { 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 || '' } setFileFailed(file, errors) { this.setFileState(file, { iconClassList: 'fa fa-exclamation-triangle', fileName: `FAILED: ${errors} ('${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: '' }) 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() { window.images = new MultipleFilesWidget( document.querySelector('[data-field=images_files]') ) window.official_project_documents = new MultipleFilesWidget( document.querySelector('[data-field=official_project_documents_files]') ) window.other_documents = new MultipleFilesWidget( document.querySelector('[data-field=other_documents_files]') ) window.shapefiles = new MultipleFilesWidget( document.querySelector('[data-field=shapefiles_files]') ) })