Skip to content
Snippets Groups Projects
Commit 03a57fd7 authored by Patrick Jentsch's avatar Patrick Jentsch
Browse files

Create a proper class for the upload form

parent d8f11a97
No related branches found
No related tags found
No related merge requests found
Showing
with 280 additions and 276 deletions
class CorpusFileList extends RessourceList {
static options = {
item: `
<tr>
<td><span class="filename"></span></td>
<td><span class="author"></span></td>
<td><span class="title"></span></td>
<td><span class="publishing_year"></span></td>
<td class="right-align">
<a class="action-button btn-floating red tooltipped waves-effect waves-light" data-action="delete" data-position="top" data-tooltip="Delete"><i class="material-icons">delete</i></a>
<a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="download" data-position="top" data-tooltip="View"><i class="material-icons">file_download</i></a>
<a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="view" data-position="top" data-tooltip="View"><i class="material-icons">send</i></a>
</td>
</tr>
`.trim(),
valueNames: [{data: ['id']}, 'author', 'filename', 'publishing_year', 'title']
};
constructor(listElement, options = {}) {
super(listElement, {...CorpusFileList.options, ...options});
this.corpusId = listElement.dataset.corpusId;
......@@ -109,19 +127,3 @@ class CorpusFileList extends RessourceList {
};
}
}
CorpusFileList.options = {
item: `
<tr>
<td><span class="filename"></span></td>
<td><span class="author"></span></td>
<td><span class="title"></span></td>
<td><span class="publishing_year"></span></td>
<td class="right-align">
<a class="action-button btn-floating red tooltipped waves-effect waves-light" data-action="delete" data-position="top" data-tooltip="Delete"><i class="material-icons">delete</i></a>
<a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="download" data-position="top" data-tooltip="View"><i class="material-icons">file_download</i></a>
<a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="view" data-position="top" data-tooltip="View"><i class="material-icons">send</i></a>
</td>
</tr>
`.trim(),
valueNames: [{data: ['id']}, 'author', 'filename', 'publishing_year', 'title']
};
class CorpusList extends RessourceList {
static options = {
item: `
<tr>
<td><a class="btn-floating disabled"><i class="material-icons service-color darken" data-service="corpus-analysis">book</i></a></td>
<td><b class="title"></b><br><i class="description"></i></td>
<td><span class="badge new status status-color status-text" data-badge-caption=""></span></td>
<td class="right-align">
<a class="action-button btn-floating red tooltipped waves-effect waves-light" data-action="delete" data-position="top" data-tooltip="Delete"><i class="material-icons">delete</i></a>
<a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="view" data-position="top" data-tooltip="View"><i class="material-icons">send</i></a>
</td>
</tr>
`.trim(),
valueNames: [{data: ['id']}, {name: 'status', attr: 'data-status'}, 'description', 'title']
};
constructor(listElement, options = {}) {
super(listElement, {...CorpusList.options, ...options});
}
......@@ -102,17 +118,3 @@ class CorpusList extends RessourceList {
};
}
}
CorpusList.options = {
item: `
<tr>
<td><a class="btn-floating disabled"><i class="material-icons service-color darken" data-service="corpus-analysis">book</i></a></td>
<td><b class="title"></b><br><i class="description"></i></td>
<td><span class="badge new status status-color status-text" data-badge-caption=""></span></td>
<td class="right-align">
<a class="action-button btn-floating red tooltipped waves-effect waves-light" data-action="delete" data-position="top" data-tooltip="Delete"><i class="material-icons">delete</i></a>
<a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="view" data-position="top" data-tooltip="View"><i class="material-icons">send</i></a>
</td>
</tr>
`.trim(),
valueNames: [{data: ['id']}, {name: 'status', attr: 'data-status'}, 'description', 'title']
};
class JobInputList extends RessourceList {
static options = {
item: `
<tr>
<td><span class="filename"></span></td>
<td class="right-align">
<a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="download" data-position="top" data-tooltip="View"><i class="material-icons">file_download</i></a>
</td>
</tr>
`.trim(),
valueNames: [{data: ['id']}, 'filename']
};
constructor(listElement, options = {}) {
super(listElement, {...JobInputList.options, ...options});
this.jobId = listElement.dataset.jobId;
......@@ -39,14 +52,3 @@ class JobInputList extends RessourceList {
};
}
}
JobInputList.options = {
item: `
<tr>
<td><span class="filename"></span></td>
<td class="right-align">
<a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="download" data-position="top" data-tooltip="View"><i class="material-icons">file_download</i></a>
</td>
</tr>
`.trim(),
valueNames: [{data: ['id']}, 'filename']
};
class JobList extends RessourceList {
static options = {
item: `
<tr>
<td><a class="btn-floating disabled"><i class="nopaque-icons service service-color darken service-icon"></i></a></td>
<td><b class="title"></b><br><i class="description"></i></td>
<td><span class="badge new status status-color status-text" data-badge-caption=""></span></td>
<td class="right-align">
<a class="action-button btn-floating red tooltipped waves-effect waves-light" data-action="delete" data-position="top" data-tooltip="Delete"><i class="material-icons">delete</i></a>
<a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="view" data-position="top" data-tooltip="View"><i class="material-icons">send</i></a>
</td>
</tr>
`.trim(),
valueNames: [{data: ['id']}, {name: 'service', attr: 'data-service'}, {name: 'status', attr: 'data-status'}, 'description', 'title']
};
constructor(listElement, options = {}) {
super(listElement, {...JobList.options, ...options});
}
......@@ -105,17 +121,3 @@ class JobList extends RessourceList {
};
}
}
JobList.options = {
item: `
<tr>
<td><a class="btn-floating disabled"><i class="nopaque-icons service service-color darken service-icon"></i></a></td>
<td><b class="title"></b><br><i class="description"></i></td>
<td><span class="badge new status status-color status-text" data-badge-caption=""></span></td>
<td class="right-align">
<a class="action-button btn-floating red tooltipped waves-effect waves-light" data-action="delete" data-position="top" data-tooltip="Delete"><i class="material-icons">delete</i></a>
<a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="view" data-position="top" data-tooltip="View"><i class="material-icons">send</i></a>
</td>
</tr>
`.trim(),
valueNames: [{data: ['id']}, {name: 'service', attr: 'data-service'}, {name: 'status', attr: 'data-status'}, 'description', 'title']
};
class JobResultList extends RessourceList {
static options = {
item: `
<tr>
<td><span class="description"></span></td>
<td><span class="filename"></span></td>
<td class="right-align">
<a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="download" data-position="top" data-tooltip="View"><i class="material-icons">file_download</i></a>
</td>
</tr>
`.trim(),
valueNames: [{data: ['id']}, 'description', 'filename']
};
constructor(listElement, options = {}) {
super(listElement, {...JobResultList.options, ...options});
this.jobId = listElement.dataset.jobId;
......@@ -74,15 +88,3 @@ class JobResultList extends RessourceList {
};
}
}
JobResultList.options = {
item: `
<tr>
<td><span class="description"></span></td>
<td><span class="filename"></span></td>
<td class="right-align">
<a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="download" data-position="top" data-tooltip="View"><i class="material-icons">file_download</i></a>
</td>
</tr>
`.trim(),
valueNames: [{data: ['id']}, 'description', 'filename']
};
class QueryResultList extends RessourceList {
static options = {
item: `
<tr>
<td><b class="title"></b><br><i class="description"></i><br></td>
<td><span class="corpus_title"></span><br><span class="query"></span></td>
<td class="right-align">
<a class="action-button btn-floating red tooltipped waves-effect waves-light" data-action="delete" data-position="top" data-tooltip="Delete"><i class="material-icons">delete</i></a>
<a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="view" data-position="top" data-tooltip="View"><i class="material-icons">send</i></a>
</td>
</tr>
`.trim(),
valueNames: [{data: ['id']}, 'corpus_title', 'description', 'query', 'title']
};
constructor(listElement, options = {}) {
super(listElement, {...QueryResultList.options, ...options});
}
......@@ -105,16 +120,3 @@ class QueryResultList extends RessourceList {
};
}
}
QueryResultList.options = {
item: `
<tr>
<td><b class="title"></b><br><i class="description"></i><br></td>
<td><span class="corpus_title"></span><br><span class="query"></span></td>
<td class="right-align">
<a class="action-button btn-floating red tooltipped waves-effect waves-light" data-action="delete" data-position="top" data-tooltip="Delete"><i class="material-icons">delete</i></a>
<a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="view" data-position="top" data-tooltip="View"><i class="material-icons">send</i></a>
</td>
</tr>
`.trim(),
valueNames: [{data: ['id']}, 'corpus_title', 'description', 'query', 'title']
};
......@@ -3,6 +3,41 @@ class RessourceList {
* This class is not meant to be used directly, instead it should be used as
* a base class for concrete ressource list implementations.
*/
static autoInit() {
const nopaqueRessourceListElements = document.querySelectorAll('.nopaque-ressource-list[data-ressource-type]:not(.no-autoinit)');
let nopaqueRessourceListElement;
for (nopaqueRessourceListElement of nopaqueRessourceListElements) {
switch (nopaqueRessourceListElement.dataset.ressourceType) {
case 'Corpus':
new CorpusList(nopaqueRessourceListElement);
break;
case 'CorpusFile':
new CorpusFileList(nopaqueRessourceListElement);
break;
case 'Job':
new JobList(nopaqueRessourceListElement);
break;
case 'JobInput':
new JobInputList(nopaqueRessourceListElement);
break;
case 'JobResult':
new JobResultList(nopaqueRessourceListElement);
break;
case 'QueryResult':
new QueryResultList(nopaqueRessourceListElement);
break;
case 'User':
new UserList(nopaqueRessourceListElement);
break;
default:
break;
}
}
}
static options = {page: 5, pagination: [{innerWindow: 4, outerWindow: 1}]};
constructor(listElement, options = {}) {
let i;
......@@ -88,4 +123,3 @@ class RessourceList {
this.listjs.get('id', id)[0].values({[valueName]: newValue});
}
}
RessourceList.options = {page: 5, pagination: [{innerWindow: 4, outerWindow: 1}]};
class UserList extends RessourceList {
static options = {
item: `
<tr>
<td><span class="id_"></span></td>
<td><span class="username"></span></td>
<td><span class="email"></span></td>
<td><span class="last_seen"></span></td>
<td><span class="role"></span></td>
<td class="right-align">
<a class="action-button btn-floating red tooltipped waves-effect waves-light" data-action="delete" data-position="top" data-tooltip="Delete"><i class="material-icons">delete</i></a>
<a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="edit" data-position="top" data-tooltip="Edit"><i class="material-icons">edit</i></a>
<a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="view" data-position="top" data-tooltip="View"><i class="material-icons">send</i></a>
</td>
</tr>
`.trim(),
valueNames: [{data: ['id']}, 'id_', 'username', 'email', 'last_seen', 'role']
};
constructor(listElement, options = {}) {
super(listElement, {...UserList.options, ...options});
}
......@@ -70,20 +90,3 @@ class UserList extends RessourceList {
};
}
}
UserList.options = {
item: `
<tr>
<td><span class="id_"></span></td>
<td><span class="username"></span></td>
<td><span class="email"></span></td>
<td><span class="last_seen"></span></td>
<td><span class="role"></span></td>
<td class="right-align">
<a class="action-button btn-floating red tooltipped waves-effect waves-light" data-action="delete" data-position="top" data-tooltip="Delete"><i class="material-icons">delete</i></a>
<a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="edit" data-position="top" data-tooltip="Edit"><i class="material-icons">edit</i></a>
<a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="view" data-position="top" data-tooltip="View"><i class="material-icons">send</i></a>
</td>
</tr>
`.trim(),
valueNames: [{data: ['id']}, 'id_', 'username', 'email', 'last_seen', 'role']
};
class UploadForm {
static autoInit() {
const nopaqueSubmitForms = document.querySelectorAll('.nopaque-upload-form');
let nopaqueSubmitForm;
for (nopaqueSubmitForm of nopaqueSubmitForms) {
new UploadForm(nopaqueSubmitForm);
}
}
constructor(formElement) {
this.formElement = formElement;
this.request = new XMLHttpRequest();
this.formElement.addEventListener('submit', (event) => {
event.preventDefault();
this.submit();
});
}
submit() {
const selectElements = this.formElement.querySelectorAll('select');
let abortElement;
let helperTextElement;
let helperTextElements;
let inputFieldElement;
let modal;
let modalElement;
let progressElement;
let selectElement;
let tmp;
// Check if select elements are filled out properly
for (selectElement of selectElements) {
if (selectElement.value === '') {
inputFieldElement = selectElement.closest('.input-field');
inputFieldElement.querySelector('.select-dropdown').classList.add('invalid');
helperTextElements = inputFieldElement.querySelectorAll('.helper-text');
for (helperTextElement of helperTextElements) {
helperTextElement.remove();
}
inputFieldElement.insertAdjacentHTML(
'beforeend',
'<span class="helper-text error-color-text">Please select an option.</span>'
);
return;
}
}
// Setup modal
tmp = document.createElement('div');
tmp.innerHTML = `
<div class="modal">
<div class="modal-content">
<h4><i class="material-icons left">file_upload</i>Uploading files...</h4>
<div class="progress">
<div class="determinate" style="width: 0%"></div>
</div>
</div>
<div class="modal-footer">
<a href="#!" class="btn red waves-effect waves-light abort">Cancel</a>
</div>
</div>
`.trim();
modalElement = document.querySelector('#modals').appendChild(tmp.firstChild);
modal = M.Modal.init(
modalElement,
{
dismissible: false,
onCloseEnd: () => {
modal.destroy();
modalElement.remove();
}
}
);
modal.open();
// Setup abort handling
abortElement = modalElement.querySelector('.abort');
abortElement.addEventListener('click', event => {this.request.abort();});
this.request.addEventListener('abort', event => {
this.request.abort();
modal.close();
});
// Setup load handling (after the request completed)
this.request.addEventListener('load', event => {
const response = JSON.parse(this.request.responseText);
let inputError;
let inputErrors;
let inputFieldElement;
let inputName;
if (this.request.status === 201) {
window.location.href = response.redirect_url;
}
if (this.request.status === 400) {
for ([inputName, inputErrors] of Object.entries(response)) {
inputFieldElement = this.formElement.querySelector(`input[name="${inputName}"], select[name="${inputName}"]`).closest('.input-field');
for (inputError of inputErrors) {
inputFieldElement.insertAdjacentHTML(
'beforeend',
`<span class="helper-text red-text">${inputError}</span>`
);
}
}
}
if (this.request.status === 500) {
location.reload();
}
modal.close();
});
// Setup progress handling
progressElement = modalElement.querySelector('.progress > .determinate');
this.request.upload.addEventListener('progress', event => {
const progress = Math.floor(100 * event.loaded / event.total);
progressElement.style.width = `${progress}%`;
});
this.request.open('POST', window.location.href);
this.request.send(new FormData(this.formElement));
}
}
/*
* The nopaque object is used as a namespace for nopaque specific functions and
* variables.
*/
var nopaque = {};
nopaque.Forms = {};
nopaque.Forms.autoInit = () => {
const nopaqueSubmitForms = document.querySelectorAll('.nopaque-submit-form');
let nopaqueSubmitForm;
for (nopaqueSubmitForm of nopaqueSubmitForms) {
nopaqueSubmitForm.addEventListener('submit', (event) => {
event.preventDefault();
const request = new XMLHttpRequest();
const selectElements = nopaqueSubmitForm.querySelectorAll('select');
let abortElement;
let helperTextElement;
let helperTextElements;
let inputFieldElement;
let modal;
let modalElement;
let progressElement;
let selectElement;
let tmp;
// Check if select elements are filled out properly
for (selectElement of selectElements) {
if (selectElement.value === '') {
inputFieldElement = selectElement.closest('.input-field');
inputFieldElement.querySelector('.select-dropdown').classList.add('invalid');
helperTextElements = inputFieldElement.querySelectorAll('.helper-text');
for (helperTextElement of helperTextElements) {
helperTextElement.remove();
}
inputFieldElement.insertAdjacentHTML(
'beforeend',
'<span class="helper-text error-color-text">Please select an option.</span>'
);
return;
}
}
// Setup modal
tmp = document.createElement('div');
tmp.innerHTML = `
<div class="modal">
<div class="modal-content">
<h4><i class="material-icons left">file_upload</i>Uploading files...</h4>
<div class="progress">
<div class="determinate" style="width: 0%"></div>
</div>
</div>
<div class="modal-footer">
<a href="#!" class="btn red waves-effect waves-light abort">Cancel</a>
</div>
</div>
`.trim();
modalElement = document.querySelector('#modals').appendChild(tmp.firstChild);
modal = M.Modal.init(
modalElement,
{
dismissible: false,
onCloseEnd: () => {
modal.destroy();
modalElement.remove();
}
}
);
modal.open();
// Setup abort handling
abortElement = modalElement.querySelector('.abort');
abortElement.addEventListener('click', event => {request.abort();});
request.addEventListener('abort', event => {
request.abort();
modal.close();
});
// Setup load handling (after the request completed)
request.addEventListener('load', event => {
const response = JSON.parse(request.responseText);
let inputError;
let inputErrors;
let inputFieldElement;
let inputName;
if (request.status === 201) {
window.location.href = response.redirect_url;
}
if (request.status === 400) {
for ([inputName, inputErrors] of Object.entries(response)) {
inputFieldElement = nopaqueSubmitForm.querySelector(`input[name="${inputName}"], select[name="${inputName}"]`).closest('.input-field');
for (inputError of inputErrors) {
inputFieldElement.insertAdjacentHTML(
'beforeend',
`<span class="helper-text red-text">${inputError}</span>`
);
}
}
}
if (request.status === 500) {
location.reload();
}
modal.close();
});
// Setup progress handling
progressElement = modalElement.querySelector('.progress > .determinate');
request.upload.addEventListener('progress', event => {
const progress = Math.floor(100 * event.loaded / event.total);
progressElement.style.width = `${progress}%`;
});
request.open('POST', window.location.href);
request.send(new FormData(nopaqueSubmitForm));
});
}
}
nopaque.RessourceList = {};
nopaque.RessourceList.autoInit = () => {
const nopaqueRessourceListElements = document.querySelectorAll('.nopaque-ressource-list[data-ressource-type]:not(.no-autoinit)');
let nopaqueRessourceListElement;
for (nopaqueRessourceListElement of nopaqueRessourceListElements) {
switch (nopaqueRessourceListElement.dataset.ressourceType) {
case 'Corpus':
new CorpusList(nopaqueRessourceListElement);
break;
case 'CorpusFile':
new CorpusFileList(nopaqueRessourceListElement);
break;
case 'Job':
new JobList(nopaqueRessourceListElement);
break;
case 'JobInput':
new JobInputList(nopaqueRessourceListElement);
break;
case 'JobResult':
new JobResultList(nopaqueRessourceListElement);
break;
case 'QueryResult':
new QueryResultList(nopaqueRessourceListElement);
break;
case 'User':
new UserList(nopaqueRessourceListElement);
break;
default:
break;
}
}
}
......@@ -9,7 +9,6 @@
<script src="{{ url_for('static', filename='js/socket.io.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/nopaque/App.js') }}"></script>
<script src="{{ url_for('static', filename='js/nopaque/JobStatusNotifier.js') }}"></script>
<script src="{{ url_for('static', filename='js/nopaque/main.js') }}"></script>
{% assets filters='rjsmin', output="js/nopaque/RessourceDisplays.min.bundle.js",
"js/nopaque/RessourceDisplays/RessourceDisplay.js",
"js/nopaque/RessourceDisplays/CorpusDisplay.js",
......@@ -27,6 +26,7 @@
"js/nopaque/RessourceLists/UserList.js" %}
<script src="{{ ASSET_URL }}"></script>
{% endassets %}
<script src="{{ url_for('static', filename='js/nopaque/UploadForm.js') }}"></script>
<script>
// Disable all option elements with no value
for (let optionElement of document.querySelectorAll('option[value=""]')) {optionElement.disabled = true;}
......@@ -40,7 +40,7 @@
app.addEventListener('users.patch', patch => jobStatusNotifier.usersPatchHandler(patch));
app.getUserById(currentUserId).then(user => {}, error => {throw JSON.stringify(error)});
{% endif %}
nopaque.Forms.autoInit();
nopaque.RessourceList.autoInit();
UploadForm.autoInit();
RessourceList.autoInit();
for (let flashedMessage of {{ get_flashed_messages(with_categories=True)|tojson }}) {app.flash(flashedMessage[1], flashedMessage[0]);}
</script>
......@@ -17,7 +17,7 @@
</div>
<div class="col s12 m8">
<form class="nopaque-submit-form" data-progress-modal="progress-modal">
<form class="nopaque-upload-form" data-progress-modal="progress-modal">
<div class="card">
<div class="card-content">
{{ form.hidden_tag() }}
......
......@@ -17,7 +17,7 @@
</div>
<div class="col s12 m8">
<form class="nopaque-submit-form" data-progress-modal="progress-modal">
<form class="nopaque-upload-form" data-progress-modal="progress-modal">
<div class="card">
<div class="card-content">
{{ form.hidden_tag() }}
......
......@@ -39,7 +39,7 @@
<div class="col s12">
<h2>Submit a job</h2>
<div class="card">
<form class="nopaque-submit-form" data-progress-modal="progress-modal">
<form class="nopaque-upload-form" data-progress-modal="progress-modal">
<div class="card-content">
{{ form.hidden_tag() }}
<div class="row">
......@@ -66,18 +66,3 @@
</div>
</div>
{% endblock page_content %}
{% block modals %}
{{ super() }}
<div id="progress-modal" class="modal">
<div class="modal-content">
<h4><i class="material-icons prefix">file_upload</i> Uploading files...</h4>
<div class="progress">
<div class="determinate" style="width: 0%"></div>
</div>
</div>
<div class="modal-footer">
<a href="#!" class="modal-close waves-effect waves-light btn red abort-request">Cancel</a>
</div>
</div>
{% endblock modals %}
......@@ -57,7 +57,7 @@
<div class="col s12">
<h2>Submit a job</h2>
<div class="card">
<form class="nopaque-submit-form" data-progress-modal="progress-modal">
<form class="nopaque-upload-form" data-progress-modal="progress-modal">
<div class="card-content">
{{ form.hidden_tag() }}
<div class="row">
......
......@@ -39,7 +39,7 @@
<div class="col s12">
<h2>Submit a job</h2>
<div class="card">
<form class="nopaque-submit-form" data-progress-modal="progress-modal">
<form class="nopaque-upload-form" data-progress-modal="progress-modal">
<div class="card-content">
{{ form.hidden_tag() }}
<div class="row">
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment