Implement local save and load buttons #28
2 changed files with 301 additions and 6 deletions
4
.prettierrc.yaml
Normal file
4
.prettierrc.yaml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
trailingComma: "es5"
|
||||
tabWidth: 4
|
||||
semi: true
|
||||
singleQuote: false
|
||||
303
entry.html
303
entry.html
|
|
@ -2266,6 +2266,18 @@
|
|||
<div class="row pt-5">
|
||||
<button class="btn btn-primary btn-lg btn-block" type="submit" id="submit-button" formaction="https://sfb1451.inm7.de/store-data" formmethod="post" disabled>Daten speichern</button>
|
||||
</div>
|
||||
|
||||
<div class="row pt-5">
|
||||
<!-- Eine lokale Kopie der Formulardaten speichern -->
|
||||
<button class="btn btn-primary btn-lg btn-block" type="button" id="local-save-button">Daten lokal speichern</button>
|
||||
</div>
|
||||
|
||||
<div class="row pt-5">
|
||||
<!-- Eine lokale Kopie der Formulardaten laden -->
|
||||
<input type="file" id="local-load-input" accept=".json" style="display:none">
|
||||
<button class="btn btn-primary btn-lg btn-block" type="button" id="local-load-button">Lokale Daten laden</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
|
@ -2681,7 +2693,7 @@
|
|||
return "";
|
||||
}
|
||||
|
||||
function getContentString() {
|
||||
function getContentArray() {
|
||||
let nameArray = [
|
||||
["form-data-version", getStringContent],
|
||||
["data-entry-domain", getStringContent],
|
||||
|
|
@ -2777,18 +2789,28 @@
|
|||
["additional-blood-sampling-url", getStringContent],
|
||||
["additional-remarks", getStringContent]
|
||||
];
|
||||
let valueArray = nameArray.map(spec => {
|
||||
let contentArray = nameArray.map(spec => {
|
||||
if (spec.length === 2) {
|
||||
return spec[0] + ":" + spec[1].apply(null, [spec[0]]);
|
||||
return [spec[0], spec[1].apply(null, [spec[0]])];
|
||||
}
|
||||
return spec[0] + ":" + spec[1].apply(null, [spec[2]]);
|
||||
return [spec[0], spec[1].apply(null, [spec[2]])];
|
||||
});
|
||||
return contentArray;
|
||||
}
|
||||
|
||||
let contentString = valueArray.join(";");
|
||||
console.log("contentString: " + contentString);
|
||||
function getContentString() {
|
||||
let keyValueArray = getContentArray();
|
||||
let contentString = Array.from(keyValueArray, kvpair => kvpair.join(":")).join(";");
|
||||
return contentString;
|
||||
}
|
||||
|
||||
function getContentObj() {
|
||||
let keyValueArray = getContentArray();
|
||||
let contentObj = Object.fromEntries(keyValueArray);
|
||||
contentObj['subject-group'] = getStringContent('subject-group'); // not present in getContentString
|
||||
return contentObj;
|
||||
}
|
||||
|
||||
async function digestMessage(message) {
|
||||
const msgUint8 = new TextEncoder().encode(message); // encode as (utf-8) Uint8Array
|
||||
const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8); // hash the message
|
||||
|
|
@ -3070,9 +3092,278 @@
|
|||
|
||||
updatedLines();
|
||||
|
||||
|
I'm going for "decline to save" - it makes little sense to save records not attributed to any subject. I also don't know an easy way to ask for value. I found showSaveFilePicker in JS, but it's new and currently supported only by Chromium-based browsers. I'm going for "decline to save" - it makes little sense to save records not attributed to any subject.
I also don't know an easy way to ask for value. I found [showSaveFilePicker](https://developer.mozilla.org/en-US/docs/Web/API/Window/showSaveFilePicker) in JS, but it's new and currently supported only by Chromium-based browsers.
I thought I knew my editor, but apparently not... I'll clean it up (probably I'll run prettier on the script blocks). I thought I knew my editor, but apparently not... I'll clean it up (probably I'll run prettier on the script blocks).
|
||||
</script>
|
||||
|
||||
|
||||
<script>
|
||||
// Local save
|
||||
const lsb = document.getElementById("local-save-button");
|
||||
|
||||
lsb.onclick = function () {
|
||||
let savedObject = getContentObj();
|
||||
|
||||
// refuse to save without subject pseudonym
|
||||
if (savedObject["subject-pseudonym"] === "") {
|
||||
window.alert(
|
||||
"Vor dem Speichern bitte Probanden-Pseudonym eingeben"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// save using pseudonym as filename
|
||||
let fname = savedObject["subject-pseudonym"] + ".json";
|
||||
var bb = new Blob([JSON.stringify(savedObject)], {
|
||||
type: "text/json;charset=utf-8",
|
||||
});
|
||||
var a = document.createElement("a");
|
||||
a.setAttribute("download", fname);
|
||||
a.setAttribute("href", URL.createObjectURL(bb));
|
||||
a.click();
|
||||
a.remove();
|
||||
return false;
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
<script>
|
||||
// Local load
|
||||
|
||||
function resetTheForm(fieldsToWrite) {
|
||||
// call reset() to clear the form
|
||||
document.getElementById("entry-form").reset();
|
||||
|
||||
// while reset affects the sliders used to enable/disable fields,
|
||||
// it does not change the field's disabled status
|
||||
// looks like we need to do this explicitly
|
||||
let shouldBeDisabled = [
|
||||
"patient-main-disease",
|
||||
"patient-stronger-impacted-hand-left",
|
||||
"patient-stronger-impacted-hand-right",
|
||||
"patient-stronger-impacted-hand-none",
|
||||
"go-nogo-recognized-error-time",
|
||||
"kas-sum",
|
||||
"kopss-sum",
|
||||
"acl-k-sum",
|
||||
"demtect-sum",
|
||||
"additional-mrt-url",
|
||||
"additional-mrt-resting-state",
|
||||
"additional-mrt-resting-state-valid",
|
||||
"additional-mrt-tapping-task",
|
||||
"additional-mrt-tapping-task-valid",
|
||||
"additional-mrt-anatomical-representation",
|
||||
"additional-mrt-dti",
|
||||
"additional-mrt-dti-valid",
|
||||
"additional-eeg-url",
|
||||
"additional-blood-sampling-url",
|
||||
"submit-button",
|
||||
];
|
||||
|
||||
// disable all fields which should be disabled
|
||||
shouldBeDisabled.forEach((fieldId) => {
|
||||
let elem = document.getElementById(fieldId);
|
||||
if (!elem.disabled) {
|
||||
elem.disabled = true;
|
||||
}
|
||||
});
|
||||
|
||||
// enable all fields which should be enabled
|
||||
fieldsToWrite.forEach((fieldId) => {
|
||||
let elem = document.getElementById(fieldId);
|
||||
// note: this won't catch radio buttons, but presently they cannot get disabled
|
||||
if (
|
||||
elem !== null &&
|
||||
elem.disabled &&
|
||||
!shouldBeDisabled.includes(fieldId)
|
||||
) {
|
||||
elem.disabled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function insertSubjectGroup(groupName) {
|
||||
document.getElementById("subject-group").value = groupName;
|
||||
patientUpdateElement(); // required to display or hide patient-specific fields
|
||||
}
|
||||
|
||||
function insertGoNogo(loadedData) {
|
||||
function updateAndDispatch(obj, key, evt, parseFun) {
|
||||
// helper to update a field and trigger an event if obj[key] is nonempty
|
||||
let elem = document.getElementById(key);
|
||||
if (obj[key] !== "") {
|
||||
if (parseFun === undefined) {
|
||||
elem.value = obj[key];
|
||||
} else {
|
||||
elem.value = parseFun(obj[key]);
|
||||
}
|
||||
elem.dispatchEvent(evt);
|
||||
}
|
||||
}
|
||||
|
||||
let syntheticChangeEvent = new UIEvent("change");
|
||||
|
||||
// update block count, reaction times, and error counts
|
||||
updateAndDispatch(
|
||||
loadedData,
|
||||
"go-nogo-block-count",
|
||||
syntheticChangeEvent
|
||||
);
|
||||
updateAndDispatch(
|
||||
loadedData,
|
||||
"go-nogo-correct-answer-time",
|
||||
syntheticChangeEvent,
|
||||
parseFloat
|
||||
);
|
||||
updateAndDispatch(
|
||||
loadedData,
|
||||
"go-nogo-total-errors",
|
||||
syntheticChangeEvent,
|
||||
parseInt
|
||||
);
|
||||
updateAndDispatch(
|
||||
loadedData,
|
||||
"go-nogo-wrong-errors",
|
||||
syntheticChangeEvent,
|
||||
parseInt
|
||||
);
|
||||
updateAndDispatch(
|
||||
loadedData,
|
||||
"go-nogo-recognized-errors",
|
||||
syntheticChangeEvent,
|
||||
parseInt
|
||||
);
|
||||
updateAndDispatch(
|
||||
loadedData,
|
||||
"go-nogo-recognized-error-time",
|
||||
syntheticChangeEvent,
|
||||
parseFloat
|
||||
);
|
||||
// note *-time fields have no onChange event, but that shouldn't be a problem
|
||||
|
||||
// update checkbox
|
||||
if (loadedData["go-nogo-incorrectly-executed"] === "True") {
|
||||
document.getElementById(
|
||||
"go-nogo-incorrectly-executed"
|
||||
).checked = true;
|
||||
} else if (loadedData["go-nogo-incorrectly-executed"] === "False") {
|
||||
document.getElementById(
|
||||
"go-nogo-incorrectly-executed"
|
||||
).checked = false;
|
||||
}
|
||||
}
|
||||
|
||||
function loadData() {
|
||||
let obj = JSON.parse(this.result); // when used as event handler, this = element on which is placed
|
||||
|
||||
// set the state to a clean sheet
|
||||
resetTheForm(Object.keys(obj));
|
||||
|
||||
// some fields need to trigger their events and need more than just value update
|
||||
let handledSeparately = [/subject-group/, /go-nogo-*/];
|
||||
insertSubjectGroup(obj["subject-group"]);
|
||||
insertGoNogo(obj);
|
||||
|
||||
// iterate through all the loaded values and put remaining values into the form
|
||||
for (let [key, value] of Object.entries(obj)) {
|
||||
if (handledSeparately.some((x) => key.match(x))) continue;
|
||||
let element = document.getElementById(key); // will be null if key not present
|
||||
|
||||
if (element !== null) {
|
||||
let elType = element.getAttribute("type");
|
||||
if (
|
||||
(elType === "text") |
|
||||
(elType === "date") |
|
||||
(elType === "url")
|
||||
) {
|
||||
element.value = value;
|
||||
} else if (elType === "number") {
|
||||
if (value === "") {
|
||||
// disabled or NaN - don't change (relies on the form being reset by this function)
|
||||
} else {
|
||||
element.value = parseFloat(value);
|
||||
}
|
||||
} else if (elType === "checkbox") {
|
||||
if (value === "True") {
|
||||
element.checked = true;
|
||||
} else if (value === "False") {
|
||||
element.checked = false;
|
||||
}
|
||||
// note: (value === "") <-> checkbox was disabled; do nothing
|
||||
} else if (element.type === "textarea") {
|
||||
// for textarea, getAttribute() doesn't work
|
||||
element.value = value;
|
||||
} else if (element.getAttribute("class") == "form-select") {
|
||||
element.value = value;
|
||||
} else {
|
||||
// catch those that didn't match - shouldn't be any
|
||||
console.log(
|
||||
"unmatched",
|
||||
element.getAttribute("class"),
|
||||
elType,
|
||||
element
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// element with given id not found, probably radiobox
|
||||
// assuming the coding is done with name & value proprerties
|
||||
document.getElementsByName(key).forEach((elem) => {
|
||||
// assuming type === radio, might check
|
||||
if (elem.getAttribute("value") === value) {
|
||||
elem.checked = true;
|
||||
} else {
|
||||
elem.checked = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Weitere Diagnostik starts disabled, enable if needed
|
||||
if (
|
||||
obj["additional-mrt-url"] !== "" ||
|
||||
obj["additional-eeg-url"] !== "" ||
|
||||
obj["additional-blood-sampling-url"] != ""
|
||||
) {
|
||||
document.getElementById("enable-ad").click();
|
||||
}
|
||||
|
||||
// update sum fields
|
||||
kasSum();
|
||||
kopssSum();
|
||||
aclKSum();
|
||||
demtectSum();
|
||||
}
|
||||
|
||||
function readSavedJSON() {
|
||||
const [dataFile] = this.files; // let dataFile = this.files[0];
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.addEventListener("load", loadData, false);
|
||||
if (dataFile) {
|
||||
reader.readAsText(dataFile);
|
||||
}
|
||||
}
|
||||
|
||||
const fileSelect = document.getElementById("local-load-button");
|
||||
const fileElem = document.getElementById("local-load-input");
|
||||
|
||||
fileSelect.addEventListener(
|
||||
"click",
|
||||
function (e) {
|
||||
if (fileElem) {
|
||||
fileElem.click();
|
||||
}
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
fileElem.addEventListener("change", readSavedJSON, false);
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<!-- Local Variables: -->
|
||||
<!-- sgml-basic-offset: 4 -->
|
||||
<!-- web-mode-script-padding: 4 -->
|
||||
<!-- indent-tabs-mode: nil -->
|
||||
<!-- End: -->
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue
If
subject-pseudonymis not set,fnamewill be.json(there is also a comment in the PR discussion). Maybe decline to save withoutsubject-pseudonymor ask for a value?Maybe remove element
aagain?Generally it would be nice if you could unify tab and space, more precisly, replace tabs with 4 spaces