mirror of
https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
synced 2024-11-14 16:55:42 +00:00
intermediate update on displays and forms 1/2
This commit is contained in:
parent
9fe38fab52
commit
c7dab5e502
@ -1,31 +1,37 @@
|
|||||||
class ResourceList {
|
var ResourceLists = {};
|
||||||
|
|
||||||
|
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);}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ResourceLists.defaultOptions = {
|
||||||
|
page: 5,
|
||||||
|
pagination: {
|
||||||
|
innerWindow: 2,
|
||||||
|
outerWindow: 2
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ResourceLists.BaseList = class BaseList {
|
||||||
/* A wrapper class for the list.js list.
|
/* A wrapper class for the list.js list.
|
||||||
* This class is not meant to be used directly, instead it should be used as
|
* This class is not meant to be used directly, instead it should be used as
|
||||||
* a base class for concrete resource list implementations.
|
* a base class for concrete resource list implementations.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static autoInit() {
|
static htmlClass;
|
||||||
CorpusList.autoInit();
|
|
||||||
CorpusFileList.autoInit();
|
|
||||||
JobList.autoInit();
|
|
||||||
JobInputList.autoInit();
|
|
||||||
JobResultList.autoInit();
|
|
||||||
SpaCyNLPPipelineModelList.autoInit();
|
|
||||||
TesseractOCRPipelineModelList.autoInit();
|
|
||||||
UserList.autoInit();
|
|
||||||
AdminUserList.autoInit();
|
|
||||||
CorpusFollowerList.autoInit();
|
|
||||||
CorpusTextInfoList.autoInit();
|
|
||||||
CorpusTokenList.autoInit();
|
|
||||||
}
|
|
||||||
|
|
||||||
static defaultOptions = {
|
|
||||||
page: 5,
|
|
||||||
pagination: {
|
|
||||||
innerWindow: 2,
|
|
||||||
outerWindow: 2
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(listContainerElement, options = {}) {
|
constructor(listContainerElement, options = {}) {
|
||||||
if ('items' in options) {
|
if ('items' in options) {
|
||||||
@ -36,7 +42,7 @@ class ResourceList {
|
|||||||
}
|
}
|
||||||
let _options = Utils.mergeObjectsDeep(
|
let _options = Utils.mergeObjectsDeep(
|
||||||
{item: this.item, valueNames: this.valueNames},
|
{item: this.item, valueNames: this.valueNames},
|
||||||
ResourceList.defaultOptions,
|
ResourceLists.defaultOptions,
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
this.listContainerElement = listContainerElement;
|
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',
|
filters='rjsmin',
|
||||||
output='gen/Forms.%(version)s.js',
|
output='gen/Forms.%(version)s.js',
|
||||||
'js/forms/index.js',
|
'js/forms/index.js',
|
||||||
|
'js/forms/form.js'
|
||||||
'js/forms/create-contribution-form.js',
|
'js/forms/create-contribution-form.js',
|
||||||
'js/forms/create-corpus-file-form.js',
|
'js/forms/create-corpus-file-form.js',
|
||||||
'js/forms/create-job-form.js'
|
'js/forms/create-job-form.js'
|
||||||
@ -47,6 +48,7 @@
|
|||||||
filters='rjsmin',
|
filters='rjsmin',
|
||||||
output='gen/resource-displays.%(version)s.js',
|
output='gen/resource-displays.%(version)s.js',
|
||||||
'js/resource-displays/index.js',
|
'js/resource-displays/index.js',
|
||||||
|
'js/resource-displays/base-display.js',
|
||||||
'js/resource-displays/corpus-display.js',
|
'js/resource-displays/corpus-display.js',
|
||||||
'js/resource-displays/job-display.js'
|
'js/resource-displays/job-display.js'
|
||||||
%}
|
%}
|
||||||
|
Loading…
Reference in New Issue
Block a user