From 90ef828e613fd6018653cab55179e14dd639dd10 Mon Sep 17 00:00:00 2001 From: Stephan Porada Date: Tue, 7 Apr 2020 13:13:48 +0200 Subject: [PATCH] Make analyse OOP style --- app/static/js/nopaque.Results.js | 73 ++++- app/static/js/nopaque.analyse_corpus.js | 279 ------------------- app/static/js/nopaque.callbacks.js | 15 +- app/static/js/nopaque.lists.js | 224 ++++++++++++++- app/templates/corpora/analyse_corpus.html.j2 | 36 ++- 5 files changed, 318 insertions(+), 309 deletions(-) delete mode 100644 app/static/js/nopaque.analyse_corpus.js diff --git a/app/static/js/nopaque.Results.js b/app/static/js/nopaque.Results.js index 064fafcf..3f4c7473 100644 --- a/app/static/js/nopaque.Results.js +++ b/app/static/js/nopaque.Results.js @@ -1,8 +1,77 @@ class Results { - constructor(results, resultsList) { - this.resultsJSON = results; + constructor(resultsJSON, resultsList) { + this.resultsJSON = resultsJSON; this.resultsList = resultsList; } + clear_all() { + this.resultsList.clear(); + this.resultsList.update(); + this.resultsJSON.init(); + } +} + +class ResultsJSON { + // Sets empty object structure. Also usefull to delete old results. + init(matchCount = 0) { + this["matches"] = []; // list of all c with lc and rc + this["cpos_lookup"] = {}; // object contains all this key value pair + this["text_lookup"] = {}; // same as above for all text ids + this["match_count"] = matchCount; + } + + // get query as string from form Element + getQueryStr(queryFormElement) { + // gets query + let queryFormData; + let queryStr; + queryFormData = new FormData(queryFormElement); + queryStr = queryFormData.get("query-form-query"); + this["query"] = queryStr; + } + + // function creates a unique and safe filename for the download + createDownloadFilename() { + let today; + let currentDate; + let currentTime; + let safeFilename; + let resultFilename; + // get and create metadata + today = new Date(); + currentDate = today.getUTCFullYear() + '-' + (today.getUTCMonth() +1) + '-' + today.getUTCDate(); + currentTime = today.getUTCHours() + ":" + today.getUTCMinutes() + ":" + today.getUTCSeconds(); + safeFilename = this.query.replace(/[^a-z0-9_-]/gi, "_"); + resultFilename = "UTC-" + currentDate + "_" + currentTime + "_" + safeFilename; + return resultFilename + } + + // Function to download data as a Blob created from a string, should be multi purpose + download(downloadElem, dataStr, filename, type, filenameSlug) { + let file; + console.log("Start Download!"); + filename += filenameSlug; + file = new Blob([dataStr], {type: type}); + if (window.navigator.msSaveOrOpenBlob) {// IE10+ + window.navigator.msSaveOrOpenBlob(file, filename); + } + else { // Others + var url = URL.createObjectURL(file); + downloadElem.href = url; + downloadElem.download = filename; + } + } + + // function to download the results as JSON + downloadJSONRessource(resultFilename) { + let dataStr; + let downloadElement; + // stringify JSON object for json download + dataStr = JSON.stringify(results.resultsJSON, undefined, "\t"); // use tabs to save some space + // get downloadResultsElement + downloadElement = document.getElementById("download-results-json"); + // start actual download + this.download(downloadElement, dataStr, resultFilename, "text/json", ".json") + } } \ No newline at end of file diff --git a/app/static/js/nopaque.analyse_corpus.js b/app/static/js/nopaque.analyse_corpus.js deleted file mode 100644 index 71ecaf06..00000000 --- a/app/static/js/nopaque.analyse_corpus.js +++ /dev/null @@ -1,279 +0,0 @@ -// ###### Helper functions ###### - -// get query as string from form Element -function getQueryStr(queryFormElement) { - // gets query - let queryFormData; - let queryStr; - queryFormData = new FormData(queryFormElement); - queryStr = queryFormData.get("query-form-query"); - return queryStr -} - -// get display options from display options form element -function getDisplayOptions(displayOptionsFormElement) { - // gets display options parameters - let displayOptionsFormData - let displayOptionsData; - displayOptionsFormData = new FormData(displayOptionsFormElement); - displayOptionsData = {"resultsPerPage": displayOptionsFormData.get("display-options-form-results_per_page"), - "resultsContex": displayOptionsFormData.get("display-options-form-result_context"), - "expertMode": displayOptionsFormData.get("display-options-form-expert_mode")}; - console.log(displayOptionsData); - return displayOptionsData -} - -// ###### Download results functions ###### -// TODO: Maybe write these as class functions? For this maybe create a result class - -// function creates a unique and safe filename for the download -function createDownloadFilename() { - let today; - let currentDate; - let currentTime; - let safeFilename; - let resultFilename; - // get and create metadata - console.log("Create Metadata!"); - today = new Date(); - currentDate = today.getUTCFullYear() + '-' + (today.getUTCMonth() +1) + '-' + today.getUTCDate(); - currentTime = today.getUTCHours() + ":" + today.getUTCMinutes() + ":" + today.getUTCSeconds(); - safeFilename = results.resultsJSON["query"].replace(/[^a-z0-9_-]/gi, "_"); - resultFilename = "UTC-" + currentDate + "_" + currentTime + "_" + safeFilename; - return resultFilename -} - -// function to download the results as JSON -function downloadJSONRessource(resultFilename) { - let dataStr; - let downloadElement; - // stringify JSON object for json download - dataStr = JSON.stringify(results.resultsJSON, undefined, "\t"); // use tabs to save some space - // get downloadResultsElement - downloadElement = document.getElementById("download-results-json"); - // start actual download - download(downloadElement, dataStr, resultFilename, "text/json", ".json") -} - -// Function to download data as a Blob created from a string, should be multi purpose -function download(downloadElem, dataStr, filename, type, filenameSlug) { - let file; - console.log("Start Download!"); - filename += filenameSlug; - file = new Blob([dataStr], {type: type}); - if (window.navigator.msSaveOrOpenBlob) // IE10+ - window.navigator.msSaveOrOpenBlob(file, filename); - else { // Others - var url = URL.createObjectURL(file); - downloadElem.href = url; - downloadElem.download = filename; - } -} - -// ###### Functions to inspect one match, to show more details ###### - -// activate inspect buttons if queryFinished is true -function activateInspect() { - console.log("activation progress", progress); - if (progress === 100) { - let inspectBtnElements; - inspectBtnElements = document.getElementsByClassName("inspect"); - for (let inspectBtn of inspectBtnElements) { - inspectBtn.classList.remove("disabled"); - } - } else { - return - } -} - -//gets result cpos infos for one dataIndex to send back to the server -function inspect(dataIndex) { - // This function should be in the AnalysisClient class as a method. - console.log("Inspect!"); - console.log(results.resultsJSON.matches[dataIndex].c); - contextResultsElement = document.getElementById("context-results"); - contextResultsElement.innerHTML = ""; // clear it from old inspects - contextModal.open(); - nopaque.socket.emit("corpus_analysis_inspect_match", - {payload: { - first_cpos: results.resultsJSON.matches[dataIndex].c[0], - last_cpos: results.resultsJSON.matches[dataIndex].c[1] - } - }); -} - -function showMatchContext(response) { - let contextData = response.payload - let contextResultsElement; - let contextModalLoading; - let contextModalReady; - let expertModeSwitchElement - let partElement - let token; - let tokenElement; - let tokenElements; - console.log("###### match_context ######"); - console.log("Incoming data:", contextData); - expertModeSwitchElement = document.getElementById("display-options-form-expert_mode"); - contextResultsElement = document.getElementById("context-results"); - - if (contextData.cpos_ranges == true) { - // python range like function from MDN https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from#Sequence_generator_(range) - const range = (start, stop, step) => Array.from({ length: (stop - start) / step + 1}, (_, i) => start + (i * step)); - lc = range(contextData.match.lc[0], contextData.match.lc[1], 1) - c = range(contextData.match.c[0], contextData.match.c[1], 1) - rc = range(contextData.match.rc[0], contextData.match.rc[1], 1) - } else { - lc = contextData.match.lc; - c = contextData.match.c; - rc = contextData.match.rc; - } - - partElement = document.createElement("p"); - for (let cpos of lc) { - token = contextData["cpos_lookup"][cpos]; - partElement.insertAdjacentHTML("beforeend", `${token["word"]} `); - contextResultsElement.append(partElement); - } - for (let cpos of c) { - token = contextData["cpos_lookup"][cpos]; - partElement.insertAdjacentHTML("beforeend", `${token["word"]} `); - contextResultsElement.append(partElement); - } - for (let cpos of rc) { - token = contextData["cpos_lookup"][cpos]; - partElement.insertAdjacentHTML("beforeend", `${token["word"]} `); - contextResultsElement.append(partElement); - } - if (expertModeSwitchElement.checked) { - tokenElements = partElement.getElementsByClassName("token"); - expertModeOn(tokenElements, contextData); - } -} - -// ###### Display options changing live how the matches are being displayed ###### - -// Event function that changes the shown hits per page. -// Just alters the resultsList.page property -function changeHitsPerPage(event) { - try { - resultsList.page = event.target.value; - resultsList.update(); - nopaque.flash("Updated matches per page.") - } catch (e) { - console.log("resultsList has no results right now. Live update of items per page is useless for now."); - } -} - -// Event function triggered on context select change and also if pagination is clicked -function changeContext(event) { - let newContextValue; - let lc; - let rc; - let array; - try { - if (event.type === "change") { - nopaque.flash("Updated context per match!"); - } - } catch (e) { - // console.log(e); - // console.log("This error is expected."); - } finally { - newContextValue = document.getElementById("display-options-form-result_context").value; - console.log("Context value is:", newContextValue); - lc = document.getElementsByClassName("left-context"); - rc = document.getElementsByClassName("right-context"); - for (let element of lc) { - array = Array.from(element.childNodes); - for (let element of array.slice(newContextValue)) { - element.classList.add("hide"); - } - for (let element of array.slice(0, newContextValue)) { - element.classList.remove("hide"); - } - } - for (let element of rc) { - array = Array.from(element.childNodes); - for (let element of array.slice(newContextValue)) { - element.classList.add("hide"); - } - for (let element of array.slice(0, newContextValue)) { - element.classList.remove("hide"); - } - } - } -} - -// ###### Expert view event functions ###### - -// Event function to check if pagination is used and then look if -// expertModeSwitchElement is checked -// if checked than expertModeOn is executed -// if unchecked expertModeOff is executed -function eventHandlerCheck(event) { - console.log("pagination used!"); - console.log(expertModeSwitchElement.checked); - if (expertModeSwitchElement.checked) { - expertModeOn(event.currentTarget.tokenElements, resultsJSON); - } else if (!expertModeSwitchElement.checked) { - event.preventDefault(); - console.log("prevented! Destroy"); - expertModeOff(event.currentTarget.tokenElements); - } -} - -// function to apply extra information and animation to every token -function expertModeOn(tokenElements, results) { - let token; - - console.log("expertModeOn!"); - for (let tokenElement of tokenElements) { - tokenElement.classList.add("chip"); - tokenElement.classList.add("hoverable"); - tokenElement.classList.add("expert-view"); - token = results["cpos_lookup"][tokenElement.dataset.cpos]; - tokenElement.addEventListener("mouseover", function(event) { - console.log("Mouseover!"); - console.log(event.target); - token = results["cpos_lookup"][event.target.dataset.cpos]; - addToolTipToTokenElement(event.target, token); - }); - } -} - -// fuction that creates Tooltip for one token and extracts the corresponding -// infos from the result JSON -function addToolTipToTokenElement(tokenElement, token) { - M.Tooltip.init(tokenElement, - {"html": ` - - - - - - - - -
Token informationSource information
- Word: ${token["word"]}
- Lemma: ${token["lemma"]}
- POS: ${token["pos"]}
- Simple POS: ${token["simple_pos"]}
- NER: ${token["ner"]} -
- Title: ${resultsJSON["text_lookup"][token["text"]]["title"]}
- Author: ${resultsJSON["text_lookup"][token["text"]]["author"]}
- Publishing year: ${resultsJSON["text_lookup"][token["text"]]["publishing_year"]} -
`}); -} - -// function to remove extra informations and animations from tokens -function expertModeOff(tokenElements) { - console.log("expertModeOff!"); - for (let tokenElement of tokenElements) { - tokenElement.classList.remove("chip"); - tokenElement.classList.remove("hoverable"); - tokenElement.classList.remove("expert-view"); - tokenElement.outerHTML = tokenElement.outerHTML; // this is actually a workaround, but it works pretty fast - } -} diff --git a/app/static/js/nopaque.callbacks.js b/app/static/js/nopaque.callbacks.js index 1d683d33..031d73b8 100644 --- a/app/static/js/nopaque.callbacks.js +++ b/app/static/js/nopaque.callbacks.js @@ -11,11 +11,10 @@ function querySetup(payload) { textLookupCountElement.innerText = "0"; matchCountElement.innerText = payload.match_count; // always re initializes results to delete old results from it - results.resultsJSON["matches"] = []; // list of all c with lc and rc - results.resultsJSON["cpos_lookup"] = {}; // object contains all cpos as key value pair - results.resultsJSON["text_lookup"] = {}; // same as above for all text ids - results.resultsJSON["match_count"] = payload.match_count; - results.resultsJSON["query"] = getQueryStr(queryFormElement); + // this has to be done here again because teh last chunk from old results was still being recieved + results.clear_all() + // Get query string again + results.resultsJSON.getQueryStr(queryFormElement); } function queryRenderResults(payload) { @@ -39,7 +38,7 @@ function queryRenderResults(payload) { item.elm = resultsList.createResultRowElement(item, payload.chunk); } resultsList.update(); - changeContext(); // sets lr context on first result load + results.resultsList.changeContext(); // sets lr context on first result load }); // incorporating new chunk results into full results results.resultsJSON.matches.push(...payload.chunk.matches); @@ -55,11 +54,11 @@ function queryRenderResults(payload) { queryResultsProgressElement.classList.add("hide"); queryResultsUserFeedbackElement.classList.add("hide"); queryResultsExportElement.classList.remove("disabled"); - activateInspect(); + ResultsList.activateInspect(); } // inital expert mode check and activation if (expertModeSwitchElement.checked) { let initialTokenElements = document.getElementsByClassName("token"); - expertModeOn(initialTokenElements, resultsJSON); + results.resultsList.expertModeOn(initialTokenElements, resultsJSON); } } \ No newline at end of file diff --git a/app/static/js/nopaque.lists.js b/app/static/js/nopaque.lists.js index 2bdc391b..49772278 100644 --- a/app/static/js/nopaque.lists.js +++ b/app/static/js/nopaque.lists.js @@ -120,6 +120,228 @@ RessourceList.options = { class ResultsList extends List { + // get display options from display options form element + static getDisplayOptions(displayOptionsFormElement) { + // gets display options parameters + let displayOptionsFormData + let displayOptionsData; + displayOptionsFormData = new FormData(displayOptionsFormElement); + displayOptionsData = {"resultsPerPage": displayOptionsFormData.get("display-options-form-results_per_page"), + "resultsContex": displayOptionsFormData.get("display-options-form-result_context"), + "expertMode": displayOptionsFormData.get("display-options-form-expert_mode")}; + return displayOptionsData + } + + // ###### Functions to inspect one match, to show more details ###### + // activate inspect buttons if queryFinished is true + static activateInspect() { + if (progress === 100) { + let inspectBtnElements; + inspectBtnElements = document.getElementsByClassName("inspect"); + for (let inspectBtn of inspectBtnElements) { + inspectBtn.classList.remove("disabled"); + } + } else { + return + } + } + + //gets result cpos infos for one dataIndex to send back to the server + inspect(dataIndex) { + // This function should be in the AnalysisClient class as a method. + console.log("Inspect!"); + console.log(results.resultsJSON.matches[dataIndex].c); + let contextResultsElement = document.getElementById("context-results"); + contextResultsElement.innerHTML = ""; // clear it from old inspects + contextModal.open(); + nopaque.socket.emit("corpus_analysis_inspect_match", + {payload: { + first_cpos: results.resultsJSON.matches[dataIndex].c[0], + last_cpos: results.resultsJSON.matches[dataIndex].c[1] + } + }); + } + + showMatchContext(response) { + let contextData = response.payload + let contextResultsElement; + let contextModalLoading; + let contextModalReady; + let expertModeSwitchElement + let partElement + let token; + let tokenElement; + let tokenElements; + let lc; + let c; + let rc; + console.log("###### match_context ######"); + console.log("Incoming data:", contextData); + expertModeSwitchElement = document.getElementById("display-options-form-expert_mode"); + contextResultsElement = document.getElementById("context-results"); + + if (contextData.cpos_ranges == true) { + // python range like function from MDN https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from#Sequence_generator_(range) + const range = (start, stop, step) => Array.from({ length: (stop - start) / step + 1}, (_, i) => start + (i * step)); + lc = range(contextData.match.lc[0], contextData.match.lc[1], 1) + c = range(contextData.match.c[0], contextData.match.c[1], 1) + rc = range(contextData.match.rc[0], contextData.match.rc[1], 1) + } else { + lc = contextData.match.lc; + c = contextData.match.c; + rc = contextData.match.rc; + } + + partElement = document.createElement("p"); + for (let cpos of lc) { + token = contextData["cpos_lookup"][cpos]; + partElement.insertAdjacentHTML("beforeend", `${token["word"]} `); + contextResultsElement.append(partElement); + } + for (let cpos of c) { + token = contextData["cpos_lookup"][cpos]; + partElement.insertAdjacentHTML("beforeend", `${token["word"]} `); + contextResultsElement.append(partElement); + } + for (let cpos of rc) { + token = contextData["cpos_lookup"][cpos]; + partElement.insertAdjacentHTML("beforeend", `${token["word"]} `); + contextResultsElement.append(partElement); + } + if (expertModeSwitchElement.checked) { + tokenElements = partElement.getElementsByClassName("token"); + console.log(this); + this.expertModeOn(tokenElements, contextData); + } + } + + // ###### Display options changing live how the matches are being displayed ###### + + // Event function that changes the shown hits per page. + // Just alters the resultsList.page property + changeHitsPerPage(event) { + try { + resultsList.page = event.target.value; + resultsList.update(); + nopaque.flash("Updated matches per page.") + } catch (e) { + console.log("resultsList has no results right now. Live update of items per page is useless for now."); + } + } + + // Event function triggered on context select change and also if pagination is clicked + changeContext(event) { + let newContextValue; + let lc; + let rc; + let array; + try { + if (event.type === "change") { + nopaque.flash("Updated context per match!"); + } + } catch (e) { + // console.log(e); + // console.log("This error is expected."); + } finally { + newContextValue = document.getElementById("display-options-form-result_context").value; + console.log("Context value is:", newContextValue); + lc = document.getElementsByClassName("left-context"); + rc = document.getElementsByClassName("right-context"); + for (let element of lc) { + array = Array.from(element.childNodes); + for (let element of array.slice(newContextValue)) { + element.classList.add("hide"); + } + for (let element of array.slice(0, newContextValue)) { + element.classList.remove("hide"); + } + } + for (let element of rc) { + array = Array.from(element.childNodes); + for (let element of array.slice(newContextValue)) { + element.classList.add("hide"); + } + for (let element of array.slice(0, newContextValue)) { + element.classList.remove("hide"); + } + } + } + } + + // ###### Expert view event functions ###### + + // Event function to check if pagination is used and then look if + // expertModeSwitchElement is checked + // if checked than expertModeOn is executed + // if unchecked expertModeOff is executed + eventHandlerCheck(event) { + console.log("pagination used!"); + console.log(expertModeSwitchElement.checked); + if (expertModeSwitchElement.checked) { + this.expertModeOn(event.currentTarget.tokenElements, resultsJSON); + } else if (!expertModeSwitchElement.checked) { + event.preventDefault(); + console.log("prevented! Destroy"); + this.expertModeOff(event.currentTarget.tokenElements); + } + } + + // function to apply extra information and animation to every token + expertModeOn(tokenElements, results) { + let token; + + console.log("expertModeOn!"); + for (let tokenElement of tokenElements) { + tokenElement.classList.add("chip"); + tokenElement.classList.add("hoverable"); + tokenElement.classList.add("expert-view"); + token = results["cpos_lookup"][tokenElement.dataset.cpos]; + tokenElement.addEventListener("mouseover", (event) => { + console.log("Mouseover!"); + console.log(event.target); + token = results["cpos_lookup"][event.target.dataset.cpos]; + this.addToolTipToTokenElement(event.target, token); + }); + } + } + + // fuction that creates Tooltip for one token and extracts the corresponding + // infos from the result JSON + addToolTipToTokenElement(tokenElement, token) { + M.Tooltip.init(tokenElement, + {"html": ` + + + + + + + + +
Token informationSource information
+ Word: ${token["word"]}
+ Lemma: ${token["lemma"]}
+ POS: ${token["pos"]}
+ Simple POS: ${token["simple_pos"]}
+ NER: ${token["ner"]} +
+ Title: ${resultsJSON["text_lookup"][token["text"]]["title"]}
+ Author: ${resultsJSON["text_lookup"][token["text"]]["author"]}
+ Publishing year: ${resultsJSON["text_lookup"][token["text"]]["publishing_year"]} +
`}); + } + + // function to remove extra informations and animations from tokens + expertModeOff(tokenElements) { + console.log("expertModeOff!"); + for (let tokenElement of tokenElements) { + tokenElement.classList.remove("chip"); + tokenElement.classList.remove("hoverable"); + tokenElement.classList.remove("expert-view"); + tokenElement.outerHTML = tokenElement.outerHTML; // this is actually a workaround, but it works pretty fast + } + } + createResultRowElement(item, chunk) { let values, cpos, token, matchRowElement, lcCellElement, hitCellElement, rcCellElement, textTitlesCellElement, matchNrElement, lc, c, rc; // gather values from item @@ -164,7 +386,7 @@ class ResultsList extends List { var inspectBtn = document.createElement("a"); inspectBtn.setAttribute("class", "btn-floating btn-flat waves-effect waves-light grey right inspect disabled"); inspectBtn.innerHTML = 'search'; - inspectBtn.onclick = function() {inspect(values["index"])}; + inspectBtn.onclick = () => {this.inspect(values["index"])}; } // add text titles at front as first td of one row hitCellElement.appendChild(inspectBtn); diff --git a/app/templates/corpora/analyse_corpus.html.j2 b/app/templates/corpora/analyse_corpus.html.j2 index ed9a9d80..10ec3bc5 100644 --- a/app/templates/corpora/analyse_corpus.html.j2 +++ b/app/templates/corpora/analyse_corpus.html.j2 @@ -216,8 +216,6 @@ - {% endblock %}