Finish full JS rewrite (in index_new.php fr now)

See #14
This commit is contained in:
2022-11-06 23:23:02 +01:00
parent 77aeebd90a
commit a141b23608
5 changed files with 397 additions and 134 deletions

View File

@ -26,22 +26,38 @@ class CsvxmlValidator {
const lines = csvRaw.trim().replace("\r\n", "\n").split("\n");
const SEPARATOR = ';';
let separator;
let delimiter;
if (csvRaw.substr(0, 1) === '"') {
separator = '";"';
delimiter = '"'
}
else {
separator = ';';
delimiter = '';
}
// Gets first line
let headers = lines.shift().split(SEPARATOR);
let headersFields = lines.shift();
let headers;
if (delimiter === "") headers = headersFields.split(separator);
else headers = headersFields.substr(1, headersFields.length - 2).split(separator);
let expectedFieldCount = headers.length;
let toValidate = [];
let lineCounter = 1;
for (let line of lines) {
if (delimiter !== '') {
line = line.substr(1, line.length - 2);
}
// Remove fully empty lines (both without content and those with fields set up,
// but without content there.
if (line.length <= headers.length) continue;
let lineContents = {};
let fields = line.split(SEPARATOR);
let fields = line.split(separator);
if (fields.length !== headers.length) {
this.errors.parsing.push("Number of columns in line " + lineCounter + " does not match number of headers");
@ -55,11 +71,13 @@ class CsvxmlValidator {
lineContents[headers[i]] = fields[i];
}
// Skip totally empty lines
if (Object.values(lineContents).join("").length === 0) continue;
toValidate.push(lineContents);
lineCounter++;
}
console.log(toValidate);
this.toValidate = toValidate;
if (toValidate.length === 0) {
@ -120,7 +138,7 @@ class CsvxmlValidator {
const headers = Object.keys(this.toValidate[0]);
for (let header of headers) {
if (this.fieldList[header].dependsOn === undefined || this.fieldList[header].dependsOn === null) continue;
if (this.fieldList[header] === undefined || this.fieldList[header].dependsOn === undefined || this.fieldList[header].dependsOn === null) continue;
let dependencies = this.fieldList[header].dependsOn;
for (let dep of dependencies) {
@ -138,12 +156,17 @@ class CsvxmlValidator {
for (let line of this.toValidate) {
for (let fieldName in line) {
if (this.fieldList[fieldName] === undefined) {
console.log("Undefined but requested field " + fieldName);
continue;
}
let allowedValues = this.fieldList[fieldName].allowedValues;
// No error if the field doesn't have a controlled list
if (allowedValues === undefined || allowedValues === null) continue;
// No error if the line's content is in the list
if (Object.values(allowedValues).length === 0 || Object.values(allowedValues).includes(line.fieldName)) {
if (Object.values(allowedValues).length === 0 || Object.values(allowedValues).includes(line[fieldName])) {
continue;
}
@ -161,7 +184,7 @@ class CsvxmlValidator {
isValid() {
for (let errorClass in this.errors) {
if (errorClass.length !== 0) return false;
if (this.errors[errorClass].length !== 0) return false;
}
return true;
@ -270,9 +293,7 @@ class CsvxmlTooltip {
newMain.setAttribute("data-title", tooltipTitle);
const tooltipDesc = document.createElement("p");
tooltipDesc.textContent = tooltipContent;
newMain.appendChild(tooltipDesc);
newMain.appendChild(tooltipContent);
document.body.appendChild(newMain);
newMain.classList.add("visible");
@ -297,15 +318,75 @@ class CsvxmlTooltip {
}
class CsvxmlDialogue {
static closeDialogue(e) {
if (e !== undefined) {
e.preventDefault();
e.stopPropagation();
}
let dialogueArea = document.getElementById("dialogueArea");
if (dialogueArea !== null && dialogueArea !== false) {
while (dialogueArea.firstChild) {
dialogueArea.removeChild(dialogueArea.firstChild);
}
dialogueArea.parentElement.removeChild(dialogueArea);
document.removeEventListener('keydown', CsvxmlDialogue.closeDialogueByEscape, false);
}
}
static closeDialogueByEscape(e) {
if (e.keyCode === 27) { // 27 = Esc
CsvxmlDialogue.closeDialogue(e);
}
}
/**
* Function for drawing a dialogue and attaching it to the body elem.
*
* @param {DOMElement} contents Contents.
*/
static drawDialogue(contents) {
let dialogueArea = document.createElement("div");
dialogueArea.id = "dialogueArea";
let dialogue = document.createElement("div");
dialogue.id = "dialogue";
dialogue.appendChild(contents);
dialogueArea.appendChild(dialogue);
document.body.appendChild(dialogueArea);
document.addEventListener('keydown', CsvxmlDialogue.closeDialogueByEscape);
return dialogue;
}
}
class CsvxmlPage {
fieldList;
tls;
domUploaderWrapper;
domMainWrapper;
selectedFields;
csvBySelectionButton;
unsetSelectionButton;
constructor(fieldList) {
this.fieldList = Object.freeze(fieldList);
this.tls = Object.freeze(JSON.parse(document.body.getAttribute("data-tls")));
let domUploaderWrapper = document.createElement("div");
domUploaderWrapper.id = "uploader";
@ -394,6 +475,70 @@ class CsvxmlPage {
}
listValidationErrors(validator) {
const dialogueContent = document.createElement("div");
const headline = document.createElement("h3");
headline.textContent = this.tls.validation_errors;
const cancelB = document.createElement("a");
cancelB.classList.add("icons");
cancelB.classList.add("iconsClose");
cancelB.classList.add("dialogueCloseX");
cancelB.id = "dialogueClose";
cancelB.textContent = "X";
cancelB.title = "Close";
cancelB.href = "#" + location.href;
cancelB.addEventListener('click', CsvxmlDialogue.closeDialogue);
headline.appendChild(cancelB);
dialogueContent.appendChild(headline);
const domErrorsSection = document.createElement("div");
for (let errorType in validator.errors) {
if (validator.errors[errorType].length === 0) continue;
const ulHl = document.createElement("h4");
ulHl.textContent = this.tls['errors_' + errorType] + " (" + validator.errors[errorType].length + ")";
ulHl.style.cursor = "pointer";
domErrorsSection.appendChild(ulHl);
const ul = document.createElement("ul");
for (let error of validator.errors[errorType]) {
const li = document.createElement("li");
li.textContent = error;
ul.appendChild(li);
}
ulHl.addEventListener('click', function() {
ul.classList.toggle("minimized");
});
domErrorsSection.appendChild(ul);
}
dialogueContent.appendChild(domErrorsSection);
const domDlSection = document.createElement("div");
const domDlA = document.createElement("span");
domDlA.textContent = this.tls.download;
domDlA.classList.add("buttonLike");
let app = this;
domDlA.addEventListener('click', function() {
app.zipUploadToXml(validator);
});
domDlSection.appendChild(domDlA);
dialogueContent.appendChild(domDlSection);
dialogue = CsvxmlDialogue.drawDialogue(dialogueContent);
}
uploadFileForValidation(file) {
const reader = new FileReader();
@ -408,15 +553,13 @@ class CsvxmlPage {
// Validate the file
let validator = new CsvxmlValidator(app.fieldList, reader.result);
if (validator.isValid() === true) {
alert("Document is valid");
alert("Document is valid. Press ok to download.");
app.zipUploadToXml(validator);
}
else {
console.log(validator.errors);
alert("Document is not valid. Errors are " + validator.errors);
app.listValidationErrors(validator);
}
app.zipUploadToXml(validator);
};
reader.onerror = function() {
alert(reader.error);
@ -432,7 +575,7 @@ class CsvxmlPage {
const form = document.createElement("form");
const label = document.createElement("label");
label.textContent = "Please select a CSV file to create XML files"; // TODO
label.textContent = app.tls.select_csv_file_for_upload;
label.setAttribute("for", "fileToUpload");
form.appendChild(label);
@ -446,10 +589,12 @@ class CsvxmlPage {
});
form.appendChild(input);
/*
const button = document.createElement("button");
button.textContent = "Upload"; // TODO
button.type = "submit";
form.appendChild(button);
*/
app.domUploaderWrapper.appendChild(form);
@ -459,6 +604,58 @@ class CsvxmlPage {
}
// Takes a callback function
doForFieldList(callback) {
let fieldLists = document.getElementsByClassName("fieldList");
for (let i = 0, max = fieldLists.length; i < max; i++) {
let fields = fieldLists[i].getElementsByTagName("li");
for (let j = 0, maxj = fields.length; j < maxj; j++) {
callback(fields[j]);
}
}
}
toggleListFieldSelectionState(field) {
let app = this;
let newValue = field.getAttribute("data-alt");
field.setAttribute("data-alt", field.textContent);
field.textContent = newValue;
field.classList.toggle("humanTLToggled");
if (field.classList.contains("humanTLToggled") === false) return;
let dependencies = field.getAttribute("data-dependencies");
if (dependencies !== undefined && dependencies !== null) {
let linkedFields = dependencies.split(";");
for (let i = 0, max = linkedFields.length; i < max; i++) {
let linkedField = document.getElementById(linkedFields[i]);
if (linkedField.classList.contains("humanTLToggled") === true) continue;
app.toggleListFieldSelectionState(linkedField);
}
}
}
checkCSVBySelectionAccessibility() {
let selected = document.getElementsByClassName("humanTLToggled");
if (selected.length === 0) {
this.csvBySelectionButton.classList.add("invisible");
this.unsetSelectionButton.classList.add("invisible");
}
else {
this.csvBySelectionButton.classList.remove("invisible");
this.unsetSelectionButton.classList.remove("invisible");
}
}
getOptionsSection() {
function genButton(id, text, link = "") {
@ -477,93 +674,28 @@ class CsvxmlPage {
const app = this;
const dlAllButton = genButton("dlAll", "Download all");
const dlAllButton = genButton("dlAll", this.tls.download_csv_all);
dlAllButton.cursor = "pointer";
dlAllButton.addEventListener('click', function() {
app.generateCsv();
});
options.appendChild(dlAllButton); // TODO
options.appendChild(dlAllButton);
const csvBySelectionButton = genButton("csvBySelection", "Csv by selection");
csvBySelectionButton.classList.add("invisible");
// TODO: Add toggle via event listener
options.appendChild(csvBySelectionButton);
this.csvBySelectionButton = genButton("csvBySelection", this.tls.download_csv_by_selection);
this.csvBySelectionButton.classList.add("invisible");
options.appendChild(this.csvBySelectionButton);
const optionSelectRequired = genButton("selectRequired", "Select required");
const optionSelectRequired = genButton("selectRequired", this.tls.select_required_fields);
options.appendChild(optionSelectRequired);
const optionSelectAll = genButton("selectAll", "Select all");
const optionSelectAll = genButton("selectAll", this.tls.select_all_fields);
options.appendChild(optionSelectAll);
const unsetSelectionButton = genButton("unsetSelection", "Unset selection");
unsetSelectionButton.classList.add("invisible");
// TODO: Add toggle via event listener
options.appendChild(unsetSelectionButton);
this.unsetSelectionButton = genButton("unsetSelection", this.tls.unset_selection);
this.unsetSelectionButton.classList.add("invisible");
options.appendChild(this.unsetSelectionButton);
function checkCSVBySelectionAccessibility() {
let selected = document.getElementsByClassName("humanTLToggled");
if (selected.length === 0) {
csvBySelectionButton.classList.add("invisible");
unsetSelectionButton.classList.add("invisible");
}
else {
csvBySelectionButton.classList.remove("invisible");
unsetSelectionButton.classList.remove("invisible");
}
}
// Takes a callback function
function doForFieldList(callback) {
let fieldLists = document.getElementsByClassName("fieldList");
for (let i = 0, max = fieldLists.length; i < max; i++) {
let fields = fieldLists[i].getElementsByTagName("li");
for (let j = 0, maxj = fields.length; j < maxj; j++) {
callback(fields[j]);
}
}
}
function toggleListFieldSelectionState(field) {
let newValue = field.getAttribute("data-alt");
field.setAttribute("data-alt", field.textContent);
field.textContent = newValue;
field.classList.toggle("humanTLToggled");
if (field.classList.contains("humanTLToggled") === false) return;
let dependencies = field.getAttribute("data-dependencies");
if (dependencies !== undefined && dependencies !== null) {
let linkedFields = dependencies.split(";");
for (let i = 0, max = linkedFields.length; i < max; i++) {
let linkedField = document.getElementById(linkedFields[i]);
if (linkedField.classList.contains("humanTLToggled") === true) continue;
toggleListFieldSelectionState(linkedField);
}
}
}
doForFieldList(function(field) {
// Each field should switch its visible content and human-readable
// translation on a click.
field.addEventListener('click', function(e) {
toggleListFieldSelectionState(field);
checkCSVBySelectionAccessibility();
});
});
csvBySelectionButton.addEventListener('click', function(e) {
this.csvBySelectionButton.addEventListener('click', function(e) {
let selected = document.getElementsByClassName("humanTLToggled");
let selectedFields = [];
@ -576,12 +708,12 @@ class CsvxmlPage {
optionSelectRequired.addEventListener('click', function(e) {
doForFieldList(function(field) {
app.doForFieldList(function(field) {
if (field.classList.contains("requiredField") === false) return;
if (field.classList.contains("humanTLToggled") === true) return;
toggleListFieldSelectionState(field);
checkCSVBySelectionAccessibility();
app.toggleListFieldSelectionState(field);
app.checkCSVBySelectionAccessibility();
});
@ -589,23 +721,23 @@ class CsvxmlPage {
optionSelectAll.addEventListener('click', function(e) {
doForFieldList(function(field) {
app.doForFieldList(function(field) {
if (field.classList.contains("humanTLToggled") === true) return;
toggleListFieldSelectionState(field);
checkCSVBySelectionAccessibility();
app.toggleListFieldSelectionState(field);
app.checkCSVBySelectionAccessibility();
});
});
unsetSelectionButton.addEventListener('click', function(e) {
this.unsetSelectionButton.addEventListener('click', function(e) {
doForFieldList(function(field) {
app.doForFieldList(function(field) {
if (field.classList.contains("humanTLToggled") === false) return;
toggleListFieldSelectionState(field);
checkCSVBySelectionAccessibility();
app.toggleListFieldSelectionState(field);
app.checkCSVBySelectionAccessibility();
});
@ -618,7 +750,7 @@ class CsvxmlPage {
renderMain() {
const domH2 = document.createElement("h2");
domH2.textContent = "Currently approved tags (column names) for md:import";
domH2.textContent = this.tls.currently_approved_tags;
this.domMainWrapper.appendChild(domH2);
this.domMainWrapper.appendChild(this.getOptionsSection());
@ -634,8 +766,6 @@ class CsvxmlPage {
const domUl = document.createElement("ul");
domUl.classList.add("fieldList");
console.log(sectionName);
const sectionFields = this.fieldList[sectionName];
for (let fieldName in sectionFields) {
const field = sectionFields[fieldName];
@ -648,7 +778,32 @@ class CsvxmlPage {
if (field.required === true) domLi.classList.add("requiredField");
domUl.appendChild(domLi);
CsvxmlTooltip.bindTooltipToElement(domLi, field.name_human_readable, field.explica);
const tooltipContent = document.createElement("div");
const explicaP = document.createElement("p");
explicaP.textContent = field.explica;
tooltipContent.appendChild(explicaP);
if (field.remarks !== undefined && field.remarks !== '') {
const remarkHl = document.createElement("h4");
remarkHl.textContent = this.tls.remarks;
tooltipContent.appendChild(remarkHl)
const remarkCont = document.createElement("p");
remarkCont = field.remarks;
tooltipContent.appendChild(remarkCont);
}
if (field.allowedValues !== undefined && Object.values(field.allowedValues).length !== 0) {
const allowedHl = document.createElement("h4");
allowedHl.textContent = this.tls.allowed_values;
tooltipContent.appendChild(allowedHl);
const allowedList = document.createElement("p");
allowedList.textContent = Object.values(field.allowedValues).join(', ');
tooltipContent.appendChild(allowedList);
}
CsvxmlTooltip.bindTooltipToElement(domLi, field.name_human_readable, tooltipContent);
}
@ -660,6 +815,20 @@ class CsvxmlPage {
document.body.appendChild(this.domMainWrapper);
let app = this;
this.doForFieldList(function(field) {
// Each field should switch its visible content and human-readable
// translation on a click.
field.addEventListener('click', function(e) {
app.toggleListFieldSelectionState(field);
app.checkCSVBySelectionAccessibility();
});
});
}
}