mirror of
https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
synced 2024-12-24 10:34:17 +00:00
intermediate update on displays and forms 1/2
This commit is contained in:
parent
9fe38fab52
commit
c7dab5e502
@ -1,25 +1,23 @@
|
||||
class ResourceList {
|
||||
/* A wrapper class for the list.js list.
|
||||
* This class is not meant to be used directly, instead it should be used as
|
||||
* a base class for concrete resource list implementations.
|
||||
*/
|
||||
var ResourceLists = {};
|
||||
|
||||
static autoInit() {
|
||||
CorpusList.autoInit();
|
||||
CorpusFileList.autoInit();
|
||||
JobList.autoInit();
|
||||
JobInputList.autoInit();
|
||||
JobResultList.autoInit();
|
||||
SpaCyNLPPipelineModelList.autoInit();
|
||||
TesseractOCRPipelineModelList.autoInit();
|
||||
UserList.autoInit();
|
||||
AdminUserList.autoInit();
|
||||
CorpusFollowerList.autoInit();
|
||||
CorpusTextInfoList.autoInit();
|
||||
CorpusTokenList.autoInit();
|
||||
ResourceLists.autoInit = () => {
|
||||
for (let propertyName in ResourceLists) {
|
||||
let property = ResourceLists[propertyName];
|
||||
// Call autoInit of all properties that are subclasses of `ResourceLists.BaseList`.
|
||||
// This does not include `ResourceLists.BaseList` itself.
|
||||
if (property.prototype instanceof ResourceLists.BaseList) {
|
||||
// Check if the static `htmlClass` property is defined.
|
||||
if (property.htmlClass === undefined) {return;}
|
||||
// Gather all HTML elements that have the `this.htmlClass` class
|
||||
// and do not have the `no-autoinit` class.
|
||||
let listElements = document.querySelectorAll(`.${property.htmlClass}:not(.no-autoinit)`);
|
||||
// Create an instance of this class for each display element.
|
||||
for (let listElement of listElements) {new property(listElement);}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static defaultOptions = {
|
||||
ResourceLists.defaultOptions = {
|
||||
page: 5,
|
||||
pagination: {
|
||||
innerWindow: 2,
|
||||
@ -27,6 +25,14 @@ class ResourceList {
|
||||
}
|
||||
};
|
||||
|
||||
ResourceLists.BaseList = class BaseList {
|
||||
/* A wrapper class for the list.js list.
|
||||
* This class is not meant to be used directly, instead it should be used as
|
||||
* a base class for concrete resource list implementations.
|
||||
*/
|
||||
|
||||
static htmlClass;
|
||||
|
||||
constructor(listContainerElement, options = {}) {
|
||||
if ('items' in options) {
|
||||
throw '"items" is not supported as an option, define it as a getter in the list class';
|
||||
@ -36,7 +42,7 @@ class ResourceList {
|
||||
}
|
||||
let _options = Utils.mergeObjectsDeep(
|
||||
{item: this.item, valueNames: this.valueNames},
|
||||
ResourceList.defaultOptions,
|
||||
ResourceLists.defaultOptions,
|
||||
options
|
||||
);
|
||||
this.listContainerElement = listContainerElement;
|
||||
|
138
app/static/js/forms/base-form.js
Normal file
138
app/static/js/forms/base-form.js
Normal file
@ -0,0 +1,138 @@
|
||||
Forms.BaseForm = class BaseForm {
|
||||
static htmlClass;
|
||||
|
||||
constructor(formElement) {
|
||||
this.formElement = formElement;
|
||||
this.eventListeners = {
|
||||
'requestLoad': []
|
||||
};
|
||||
this.afterRequestListeners = [];
|
||||
|
||||
for (let selectElement of this.formElement.querySelectorAll('select')) {
|
||||
selectElement.removeAttribute('required');
|
||||
}
|
||||
|
||||
this.formElement.addEventListener('submit', (event) => {
|
||||
event.preventDefault();
|
||||
this.submit(event);
|
||||
});
|
||||
}
|
||||
|
||||
addEventListener(eventType, listener) {
|
||||
if (eventType in this.eventListeners) {
|
||||
this.eventListeners[eventType].push(listener);
|
||||
} else {
|
||||
throw `Unknown event type ${eventType}`;
|
||||
}
|
||||
}
|
||||
|
||||
submit(event) {
|
||||
let request = new XMLHttpRequest();
|
||||
let modalElement = Utils.HTMLToElement(
|
||||
`
|
||||
<div class="modal">
|
||||
<div class="modal-content">
|
||||
<h4><i class="material-icons left">file_upload</i>Submitting...</h4>
|
||||
<div class="progress">
|
||||
<div class="determinate" style="width: 0%"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a class="action-button btn red waves-effect waves-light modal-close" data-action="cancel">Cancel</a>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
);
|
||||
document.querySelector('#modals').appendChild(modalElement);
|
||||
let modal = M.Modal.init(
|
||||
modalElement,
|
||||
{
|
||||
dismissible: false,
|
||||
onCloseEnd: () => {
|
||||
modal.destroy();
|
||||
modalElement.remove();
|
||||
}
|
||||
}
|
||||
);
|
||||
modal.open();
|
||||
|
||||
// Remove all previous helper text elements that indicate errors
|
||||
let errorHelperTextElements = this.formElement
|
||||
.querySelectorAll('.helper-text[data-helper-text-type="error"]');
|
||||
for (let errorHelperTextElement of errorHelperTextElements) {
|
||||
errorHelperTextElement.remove();
|
||||
}
|
||||
|
||||
// Check if select elements are filled out properly
|
||||
for (let selectElement of this.formElement.querySelectorAll('select')) {
|
||||
if (selectElement.value === '') {
|
||||
let inputFieldElement = selectElement.closest('.input-field');
|
||||
let errorHelperTextElement = Utils.HTMLToElement(
|
||||
'<span class="helper-text error-color-text" data-helper-text-type="error">Please select an option.</span>'
|
||||
);
|
||||
inputFieldElement.appendChild(errorHelperTextElement);
|
||||
inputFieldElement.querySelector('.select-dropdown').classList.add('invalid');
|
||||
modal.close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Setup abort handling
|
||||
let cancelElement = modalElement.querySelector('.action-button[data-action="cancel"]');
|
||||
cancelElement.addEventListener('click', (event) => {request.abort();});
|
||||
|
||||
// Setup load handling (after the request completed)
|
||||
request.addEventListener('load', (event) => {
|
||||
for (let listener of this.eventListeners['requestLoad']) {
|
||||
listener(event);
|
||||
}
|
||||
if (request.status === 400) {
|
||||
let responseJson = JSON.parse(request.responseText);
|
||||
for (let [inputName, inputErrors] of Object.entries(responseJson.errors)) {
|
||||
let inputFieldElement = this.formElement
|
||||
.querySelector(`input[name$="${inputName}"], select[name$="${inputName}"]`)
|
||||
.closest('.input-field');
|
||||
for (let inputError of inputErrors) {
|
||||
let errorHelperTextElement = Utils.HTMLToElement(
|
||||
`<span class="helper-text error-color-text" data-helper-type="error">${inputError}</span>`
|
||||
);
|
||||
inputFieldElement.appendChild(errorHelperTextElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (request.status === 500) {
|
||||
app.flash('Internal Server Error', 'error');
|
||||
}
|
||||
modal.close();
|
||||
});
|
||||
|
||||
// Setup progress handling
|
||||
let progressBarElement = modalElement.querySelector('.progress > .determinate');
|
||||
request.upload.addEventListener('progress', (event) => {
|
||||
let progress = Math.floor(100 * event.loaded / event.total);
|
||||
progressBarElement.style.width = `${progress}%`;
|
||||
});
|
||||
|
||||
request.open(this.formElement.method, this.formElement.action);
|
||||
request.setRequestHeader('Accept', 'application/json');
|
||||
let formData = new FormData(this.formElement);
|
||||
switch (this.formElement.enctype) {
|
||||
case 'application/x-www-form-urlencoded': {
|
||||
let urlSearchParams = new URLSearchParams(formData);
|
||||
request.send(urlSearchParams);
|
||||
break;
|
||||
}
|
||||
case 'multipart/form-data': {
|
||||
request.send(formData);
|
||||
break;
|
||||
}
|
||||
case 'text/plain': {
|
||||
throw 'enctype "text/plain" is not supported';
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
@ -16,142 +16,3 @@ Forms.autoInit = () => {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Forms.BaseForm = class BaseForm {
|
||||
static htmlClass;
|
||||
|
||||
constructor(formElement) {
|
||||
this.formElement = formElement;
|
||||
this.eventListeners = {
|
||||
'requestLoad': []
|
||||
};
|
||||
this.afterRequestListeners = [];
|
||||
|
||||
for (let selectElement of this.formElement.querySelectorAll('select')) {
|
||||
selectElement.removeAttribute('required');
|
||||
}
|
||||
|
||||
this.formElement.addEventListener('submit', (event) => {
|
||||
event.preventDefault();
|
||||
this.submit(event);
|
||||
});
|
||||
}
|
||||
|
||||
addEventListener(eventType, listener) {
|
||||
if (eventType in this.eventListeners) {
|
||||
this.eventListeners[eventType].push(listener);
|
||||
} else {
|
||||
throw `Unknown event type ${eventType}`;
|
||||
}
|
||||
}
|
||||
|
||||
submit(event) {
|
||||
let request = new XMLHttpRequest();
|
||||
let modalElement = Utils.HTMLToElement(
|
||||
`
|
||||
<div class="modal">
|
||||
<div class="modal-content">
|
||||
<h4><i class="material-icons left">file_upload</i>Submitting...</h4>
|
||||
<div class="progress">
|
||||
<div class="determinate" style="width: 0%"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a class="action-button btn red waves-effect waves-light modal-close" data-action="cancel">Cancel</a>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
);
|
||||
document.querySelector('#modals').appendChild(modalElement);
|
||||
let modal = M.Modal.init(
|
||||
modalElement,
|
||||
{
|
||||
dismissible: false,
|
||||
onCloseEnd: () => {
|
||||
modal.destroy();
|
||||
modalElement.remove();
|
||||
}
|
||||
}
|
||||
);
|
||||
modal.open();
|
||||
|
||||
// Remove all previous helper text elements that indicate errors
|
||||
let errorHelperTextElements = this.formElement
|
||||
.querySelectorAll('.helper-text[data-helper-text-type="error"]');
|
||||
for (let errorHelperTextElement of errorHelperTextElements) {
|
||||
errorHelperTextElement.remove();
|
||||
}
|
||||
|
||||
// Check if select elements are filled out properly
|
||||
for (let selectElement of this.formElement.querySelectorAll('select')) {
|
||||
if (selectElement.value === '') {
|
||||
let inputFieldElement = selectElement.closest('.input-field');
|
||||
let errorHelperTextElement = Utils.HTMLToElement(
|
||||
'<span class="helper-text error-color-text" data-helper-text-type="error">Please select an option.</span>'
|
||||
);
|
||||
inputFieldElement.appendChild(errorHelperTextElement);
|
||||
inputFieldElement.querySelector('.select-dropdown').classList.add('invalid');
|
||||
modal.close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Setup abort handling
|
||||
let cancelElement = modalElement.querySelector('.action-button[data-action="cancel"]');
|
||||
cancelElement.addEventListener('click', (event) => {request.abort();});
|
||||
|
||||
// Setup load handling (after the request completed)
|
||||
request.addEventListener('load', (event) => {
|
||||
for (let listener of this.eventListeners['requestLoad']) {
|
||||
listener(event);
|
||||
}
|
||||
if (request.status === 400) {
|
||||
let responseJson = JSON.parse(request.responseText);
|
||||
for (let [inputName, inputErrors] of Object.entries(responseJson.errors)) {
|
||||
let inputFieldElement = this.formElement
|
||||
.querySelector(`input[name$="${inputName}"], select[name$="${inputName}"]`)
|
||||
.closest('.input-field');
|
||||
for (let inputError of inputErrors) {
|
||||
let errorHelperTextElement = Utils.HTMLToElement(
|
||||
`<span class="helper-text error-color-text" data-helper-type="error">${inputError}</span>`
|
||||
);
|
||||
inputFieldElement.appendChild(errorHelperTextElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (request.status === 500) {
|
||||
app.flash('Internal Server Error', 'error');
|
||||
}
|
||||
modal.close();
|
||||
});
|
||||
|
||||
// Setup progress handling
|
||||
let progressBarElement = modalElement.querySelector('.progress > .determinate');
|
||||
request.upload.addEventListener('progress', (event) => {
|
||||
let progress = Math.floor(100 * event.loaded / event.total);
|
||||
progressBarElement.style.width = `${progress}%`;
|
||||
});
|
||||
|
||||
request.open(this.formElement.method, this.formElement.action);
|
||||
request.setRequestHeader('Accept', 'application/json');
|
||||
let formData = new FormData(this.formElement);
|
||||
switch (this.formElement.enctype) {
|
||||
case 'application/x-www-form-urlencoded': {
|
||||
let urlSearchParams = new URLSearchParams(formData);
|
||||
request.send(urlSearchParams);
|
||||
break;
|
||||
}
|
||||
case 'multipart/form-data': {
|
||||
request.send(formData);
|
||||
break;
|
||||
}
|
||||
case 'text/plain': {
|
||||
throw 'enctype "text/plain" is not supported';
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -16,50 +16,3 @@ ResourceDisplays.autoInit = () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ResourceDisplays.BaseDisplay = class BaseDisplay {
|
||||
static htmlClass;
|
||||
|
||||
constructor(displayElement) {
|
||||
this.displayElement = displayElement;
|
||||
this.userId = this.displayElement.dataset.userId;
|
||||
this.isInitialized = false;
|
||||
if (this.userId) {
|
||||
app.subscribeUser(this.userId)
|
||||
.then((response) => {
|
||||
app.socket.on('PATCH', (patch) => {
|
||||
if (this.isInitialized) {this.onPatch(patch);}
|
||||
});
|
||||
});
|
||||
app.getUser(this.userId)
|
||||
.then((user) => {
|
||||
this.init(user);
|
||||
this.isInitialized = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
init(user) {throw 'Not implemented';}
|
||||
|
||||
onPatch(patch) {throw 'Not implemented';}
|
||||
|
||||
setElement(element, value) {
|
||||
switch (element.tagName) {
|
||||
case 'INPUT': {
|
||||
element.value = value;
|
||||
M.updateTextFields();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
element.innerText = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setElements(elements, value) {
|
||||
for (let element of elements) {
|
||||
this.setElement(element, value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
46
app/static/js/resource-displays/resource-display.js
Normal file
46
app/static/js/resource-displays/resource-display.js
Normal file
@ -0,0 +1,46 @@
|
||||
ResourceDisplays.BaseDisplay = class BaseDisplay {
|
||||
static htmlClass;
|
||||
|
||||
constructor(displayElement) {
|
||||
this.displayElement = displayElement;
|
||||
this.userId = this.displayElement.dataset.userId;
|
||||
this.isInitialized = false;
|
||||
if (this.userId) {
|
||||
app.subscribeUser(this.userId)
|
||||
.then((response) => {
|
||||
app.socket.on('PATCH', (patch) => {
|
||||
if (this.isInitialized) {this.onPatch(patch);}
|
||||
});
|
||||
});
|
||||
app.getUser(this.userId)
|
||||
.then((user) => {
|
||||
this.init(user);
|
||||
this.isInitialized = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
init(user) {throw 'Not implemented';}
|
||||
|
||||
onPatch(patch) {throw 'Not implemented';}
|
||||
|
||||
setElement(element, value) {
|
||||
switch (element.tagName) {
|
||||
case 'INPUT': {
|
||||
element.value = value;
|
||||
M.updateTextFields();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
element.innerText = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setElements(elements, value) {
|
||||
for (let element of elements) {
|
||||
this.setElement(element, value);
|
||||
}
|
||||
}
|
||||
};
|
@ -36,6 +36,7 @@
|
||||
filters='rjsmin',
|
||||
output='gen/Forms.%(version)s.js',
|
||||
'js/forms/index.js',
|
||||
'js/forms/form.js'
|
||||
'js/forms/create-contribution-form.js',
|
||||
'js/forms/create-corpus-file-form.js',
|
||||
'js/forms/create-job-form.js'
|
||||
@ -47,6 +48,7 @@
|
||||
filters='rjsmin',
|
||||
output='gen/resource-displays.%(version)s.js',
|
||||
'js/resource-displays/index.js',
|
||||
'js/resource-displays/base-display.js',
|
||||
'js/resource-displays/corpus-display.js',
|
||||
'js/resource-displays/job-display.js'
|
||||
%}
|
||||
|
Loading…
Reference in New Issue
Block a user