diff --git a/web/app/static/js/modules/corpus_analysis/client/Client.js b/web/app/static/js/modules/corpus_analysis/client/Client.js index 9dff66ba..496749bb 100644 --- a/web/app/static/js/modules/corpus_analysis/client/Client.js +++ b/web/app/static/js/modules/corpus_analysis/client/Client.js @@ -133,7 +133,12 @@ class Client { last_cpos: tmp_last_cpos,}); } - // + /** + * Gets results data either for, 'full-results' or 'sub-results' + * Gets results for every provided dataIndex (one match) without full + * context. Because no context is needed the results data is gathered locally + * from results.data and not from the server. + **/ getResultsDataWithoutContext(resultsType, dataIndexes, results, resultsList) { this.notifyView('results-data-recieving', {fullContext: false}); let objectKey = ''; diff --git a/web/app/static/js/modules/corpus_analysis/client/callbacks.js b/web/app/static/js/modules/corpus_analysis/client/callbacks.js index f2793d2d..445e4df3 100644 --- a/web/app/static/js/modules/corpus_analysis/client/callbacks.js +++ b/web/app/static/js/modules/corpus_analysis/client/callbacks.js @@ -70,6 +70,12 @@ function saveQueryData() { } } +/** + * This callback gets the results data for the export. Either requesting it + * whith full context from the server or gets it locally without full context + * from the already present results.data. Result data is identified with the + * dataIndexes. On index is one match. + */ function getResultsData() { let [resultsType, dataIndexes, resultsList, client, results, rest] = arguments; client.isBusy = true; @@ -83,6 +89,10 @@ function getResultsData() { } } +/** + * Handles incoming results whcih have been requestes via getResultsData(). and + * saves the data accorindgly into the results object. + */ function saveResultsData() { let [payload, type, client, results, rest] = arguments; let objectKey = ''; diff --git a/web/app/static/js/modules/corpus_analysis/client/listeners.js b/web/app/static/js/modules/corpus_analysis/client/listeners.js index 6fa0a285..6549462e 100644 --- a/web/app/static/js/modules/corpus_analysis/client/listeners.js +++ b/web/app/static/js/modules/corpus_analysis/client/listeners.js @@ -186,7 +186,6 @@ function recieveViewNotification(type, client) { default: console.error('Recieved unkown notification case identifier from View'); // do something to not crash the analysis session? - // maybe unnecessary? } }); } diff --git a/web/app/static/js/modules/corpus_analysis/model/Results.js b/web/app/static/js/modules/corpus_analysis/model/Results.js index 0d3b3c24..41dc319d 100644 --- a/web/app/static/js/modules/corpus_analysis/model/Results.js +++ b/web/app/static/js/modules/corpus_analysis/model/Results.js @@ -4,6 +4,7 @@ * classes combined in the Results class define the Model. */ +// Results class bundleing the different data objects. class Results { constructor() { this.data = new Data(); @@ -14,6 +15,7 @@ class Results { console.info('Initialized the Results object.'); } + // Reset all the data objects in the results class and thus emptying them. init() { this.data.init(); this.metaData.init(); @@ -24,19 +26,43 @@ class Results { } +/** + * Class that defines the actual data objects holding the results data + * requested by the client. Data can be the results of a query, full results + * data for the export, sub results data for the export or inspect results data. + * All kinds are structured the same way. + */ class Data { - // Sets empty object structure. Also usefull to delete old results. - // matchCount default is 0 + /** + * Sets empty object structure. Also usefull to delete old results. + * MatchCount default is 0. + */ init(matchCount=0, type="results") { - 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 + // List of all c with lc and rc CPOS. + this.matches = []; + /** + * CPOS lookup object. CPOS are the key and value are infos about the CPOS + * like lemma, ner, pos, text ID etc. CPOS from the matches correspond to + * exactly one object in the cpos_lookup. + */ + this.cpos_lookup = {}; + /** + * Same like above but for text IDs. One CPOS object always has a text ID + * referencing on text object in the text_lookup. Text ID is the key. Values + * are author, publishing year etc. + */ + this.text_lookup = {}; this.match_count = matchCount; this.corpus_type = 'results'; this.cpos_ranges = null; this.query = ''; } + /** + * Function to add json data/object data to this data instance. + * If no key is specified the entire data will be assigned to this data + * instance. + */ addData(jsonData, key=null) { if (key !== null) { Object.assign(this[key], jsonData); @@ -45,7 +71,7 @@ class Data { } } - // get query as string from form Element + // Get query as a string from the form Element. getQueryStr(queryFormElement) { // gets query let queryFormData; @@ -55,7 +81,7 @@ class Data { this['query'] = queryStr; } - // function creates a unique and safe filename for the download + // Function creates a unique and safe filename for the download. createDownloadFilename(suffix) { let today = new Date(); let currentDate = `${today.getUTCFullYear()}` + @@ -80,7 +106,7 @@ class Data { downloadElement.download = filename; } - // function to download the results as JSON + // Function to download the results as JSON. downloadJSONRessource(resultFilename, downloadData, downloadElement) { /** * Stringify JSON object for json download. @@ -93,6 +119,10 @@ class Data { } +/** + * Similar to the data class but just intended for meta data about the current + * corpus the client is working with. + */ class MetaData { // Sets empty object structure when no input is given. // if json object like input is given class fields are created from this @@ -101,4 +131,9 @@ class MetaData { } } -export {Results, Data, MetaData}; \ No newline at end of file +// Export the classes +export { + Results, + Data, + MetaData +}; \ No newline at end of file diff --git a/web/app/static/js/modules/corpus_analysis/view/ResultsView.js b/web/app/static/js/modules/corpus_analysis/view/ResultsView.js index 92cfde1d..5f6b8830 100644 --- a/web/app/static/js/modules/corpus_analysis/view/ResultsView.js +++ b/web/app/static/js/modules/corpus_analysis/view/ResultsView.js @@ -1,5 +1,5 @@ /** - * This class implements a NotificationListener that is listening for the + * This class implements a ViewEventListener that is listening for the * specified */ class ViewEventListener { @@ -43,16 +43,23 @@ class ResultsList extends List { */ this.eventTokens = {}; /** - * all token elements which have added - * classes like chip and hoverable for expert view. Collected - * here to delete later on - */ + * All token elements which have added classes like chip and hoverable for + * expert view. Collected here to delete later on. + */ this.currentExpertTokenElements = {}; - // TODO: Rename both variables to something more descreptive and clear - // holds True/false for check buttons used to add matches tu sub-results. If checked, it is True. If unchecked, it is false. Buttons for this have the class add. Those little round check buttons. + /** + * Holds True/false for check buttons used to add matches to sub-results. + * If checked, it is True. If unchecked, it is false. Buttons for this + * have the class add. The ittle round check buttons to add matches to sub + * results. + */ this.addToSubResultsStatus = {}; - this.addToSubResultsIdsToShow = new Set(); // If check button is pressed its corresponding data_index is saved in this set. The set is shown to the user. - // notification listeners listening for client notifications (or other in the future?) + /** + * If a check button is pressed its corresponding data_index is saved in + * this set. The set is shown to the user. + */ + this.addToSubResultsIdsToShow = new Set(); + // ViewEventListeners listening for client notifications. this.notificationListeners = {}; this.knownHTMLElements = new Set(); } @@ -80,8 +87,11 @@ class ResultsList extends List { * class field in the ResultsList object with the query selector * string as the key. The selector will be converted to a valid JavaScript * Field name i. e. #html-id-string -> this.htmlIdString - * The value will be the identifed element or elements fetched with the querySelector - * method. + * The value will be the identifed element or elements fetched with the + * querySelector respectively querySelectorAll method. + * If the query selector is passed as an Array of length 2, with the second + * element defining modal options, teh identified element will be initialized + * as a modal with the given options. */ getHTMLElements(arrayOfSelectors) { for (let selector of arrayOfSelectors) { @@ -116,7 +126,7 @@ class ResultsList extends List { cleanKey[0] = cleanKey[0].toLowerCase(); cleanKey = cleanKey.join(''); this[cleanKey] = element ? element: elements; - // Initialize current element as modal if true. + // Initialize current element as modal if modalInit true. if (modalInit) { this[cleanKey] = M.Modal.init(this[cleanKey], options); } @@ -127,20 +137,20 @@ class ResultsList extends List { } /** - * Register notificationListeners to the ResultsList. Which will listen for + * Register ViewEventListeners to the ResultsList. Which will listen for * the specified event. */ - setNotificationListeners(notificationListeners) { + setViewEventListeners(notificationListeners) { for (let notificationListener of notificationListeners) { this.notificationListeners[notificationListener.type] = notificationListener; } } /** - * Loads the notificationListeners so that hey will be listening to their + * Loads the ViewEventListeners so that hey will be listening to their * assigned custom events. */ - loadNotificationListeners() { + loadViewEventListeners() { for (let [type, listener] of Object.entries(this.notificationListeners)) { listener.listenerFunction(type, this); } @@ -165,8 +175,10 @@ class ResultsList extends List { let c; let rc; if (cpos_ranges) { - // python range like function from MDN - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from#Sequence_generator_(range) + /** + * 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(cpos_values.lc[0], cpos_values.lc[1], 1) c = range(cpos_values.c[0], cpos_values.c[1], 1) @@ -179,30 +191,7 @@ class ResultsList extends List { return {lc: lc, c: c, rc: rc}; } - // handels interactionElements during a pagination navigation - // loops over interactionElements and executes callback functions accordingly - pageChangeEventInteractionHandler(interactionElements) { - // get elements to check thier status - for (let interaction of interactionElements.interactions) { - if (interaction.checkStatus) { - if (interaction.element.checked) { - let f_on = interaction.bindThisToCallback("on"); - let args_on = interaction.callbacks.on.args; - f_on(...args_on); - } else { - let f_off = interaction.bindThisToCallback("off"); - let args_off = interaction.callbacks.off.args; - f_off(...args_off); - } - } else { - let f = interaction.bindThisToCallback("noCheck"); - let args = interaction.callbacks.noCheck.args; - f(...args); - } - } - } - - // get display options from display options form element + // Get display options from display options form element. static getDisplayOptions(htmlId) { // gets display options parameters let displayOptionsFormElement = document.getElementById(htmlId); @@ -215,48 +204,56 @@ class ResultsList extends List { }; return displayOptionsData } - - // Used in addToSubResults and inspect to toggle the design of the check - // buttons according to its checked unchecked status. + /** + * Used in addToSubResults and inspect to toggle the design of the check + * buttons according to its checked unchecked status. + */ helperActivateAddBtn(btn) { btn.classList.remove("corpus-analysis-color.lighten"); btn.classList.add("green"); btn.textContent = "check"; } - // Used in addToSubResults and inspect to toggle the design of the check - // buttons according to its checked unchecked status. + /** + * Used in addToSubResults and inspect to toggle the design of the check + * buttons according to its checked unchecked status. + */ helperDeactivateAddBtn(btn) { btn.classList.remove("green"); btn.classList.add("corpus-analysis-color.lighten"); btn.textContent = "add"; } - - // Either adds or removes a match to the sub-results. For this it checks - // onclick if the current button has been checked or not. For this the - // function checks if its status in addToSubResultsStatus is either flase or - // true. Adds match to sub-results if status is false if status is true it - // removes it. + /** + * Either adds or removes a match to the sub-results. For this it checks + * onclick if the current button has been checked or not. For this the + * function checks if its status in addToSubResultsStatus is either flase or + * true. Adds match to sub-results if status is false if status is true it + * removes it. + */ addToSubResults(dataIndex, client, tableCall=true) { if (!this.addToSubResultsStatus[dataIndex] || this.addToSubResultsStatus === undefined) { // add button is activated because status is either false or undefined this.helperActivateAddBtn(event.target); - this.addToSubResultsStatus[dataIndex] = true; // sets status to true - this.addToSubResultsIdsToShow.add(dataIndex + 1); // + 1 because user does not see zero indexd data indexes - this.subResultsMatchIds.textContent = [...this.addToSubResultsIdsToShow].sort(function(a, b){return a-b}).join(", "); // automaticalle sorts ids into the textarea in ascending order - M.textareaAutoResize(this.subResultsMatchIds); // after an insert textarea has to be resized manually + this.addToSubResultsStatus[dataIndex] = true; + // Add 1 because indexes are zero based. User sees 1 based numbering. + this.addToSubResultsIdsToShow.add(dataIndex + 1); + // Allways sort the shown indexes for the user if new match is added. + this.subResultsMatchIds.textContent = [...this.addToSubResultsIdsToShow].sort(function(a, b){return a-b}).join(", "); + M.textareaAutoResize(this.subResultsMatchIds); this.nrMarkedMatches.textContent = [...this.addToSubResultsIdsToShow].length; } else if (this.addToSubResultsStatus[dataIndex]) { // add button is deactivated because status is true this.helperDeactivateAddBtn(event.target); - this.addToSubResultsStatus[dataIndex] = false; // sets status to false - this.addToSubResultsIdsToShow.delete(dataIndex + 1); // + 1 because user does not see zero indexd data indexes - this.subResultsMatchIds.textContent = [...this.addToSubResultsIdsToShow].sort(function(a, b){return a-b}).join(", "); // automaticalle sorts ids into the textarea in ascending order + this.addToSubResultsStatus[dataIndex] = false; + // Add 1 because indexes are zero based. User sees 1 based numbering. + this.addToSubResultsIdsToShow.delete(dataIndex + 1); + // Allways sort the shown indexes for the user if new match is added. + this.subResultsMatchIds.textContent = [...this.addToSubResultsIdsToShow].sort(function(a, b){return a-b}).join(", "); this.nrMarkedMatches.textContent = [...this.addToSubResultsIdsToShow].length; - M.textareaAutoResize(this.subResultsMatchIds); // after an insert textarea has to be resized manually + M.textareaAutoResize(this.subResultsMatchIds); } - // Toggles the create button according to the number of ids in addToSubResultsIdsToShow + // Toggles create button according to the number of ids in addToSubResultsIdsToShow if ([...this.addToSubResultsIdsToShow].length > 0 && !client.isBusy) { this.subResultsCreate.classList.toggle('disabled', false); } else if ([...this.addToSubResultsIdsToShow].length === 0) { @@ -270,7 +267,7 @@ class ResultsList extends List { this.subResultsExport.classList.add("hide"); this.subResultsCreate.classList.remove("hide"); /** - * Also activate/deactivate buttons in the table/jsList results accordingly + * Also activate/deactivate buttons in the table/resultsList accordingly * if button in inspect was activated/deactivated. * This part only runs if tableCall is set to false when this function is * called. @@ -287,8 +284,7 @@ class ResultsList extends List { } } - // ###### Functions to inspect one match, to show more details ###### - // Activate inspect buttons depending on the Client status + // Toggle inspect buttons depending on the Client status toggleInspectButtons(client) { if (!client.isBusy) { this.activateInspectButtons(); @@ -297,7 +293,7 @@ class ResultsList extends List { } } - // Activate inspect buttons. Should be private if this is supported. + // Helper function. Should be private if feature is available. activateInspectButtons() { let inspectBtnElements; inspectBtnElements = document.querySelectorAll('.inspect'); @@ -306,7 +302,7 @@ class ResultsList extends List { } } - // Deactivate inspect buttons. Should be private if this is supported. + // Helper function. Should be private if feature is available. deactivateInspectButtons() { let inspectBtnElements; inspectBtnElements = document.querySelectorAll('.inspect'); @@ -314,10 +310,13 @@ class ResultsList extends List { inspectBtn.classList.toggle('disabled', true); } } - - // ### functions to inspect imported Matches - // gets result cpos infos for dataIndexes (use list of length 1 for one match) to send back to - // the server + /** + * Function to inspect one match in more detail (Showing more context). + * If in dynamic mode the view notifies the client to requests the new + * context for the one match identified by the given dataIndex. + * If not in dynamic mode the the needed context will be gathered from the + * already present results in results.data. + */ inspect(client, results, dataIndex, type) { // initialize context modal this.getHTMLElements([ @@ -326,31 +325,37 @@ class ResultsList extends List { '#create-inspect-menu', '#create-from-inspect', ]); - // get result infos from server and show them in context modal + // Clear fields from old data on every new inspect() call. this.contextId = dataIndex[0]; - this.contextResults.innerHTML = ""; // clear it from old inspects + this.contextResults.innerHTML = ""; if (client.dynamicMode) { + // Notify Client to get results from server. this.notifyClient('get-results', {resultsType: 'inspect-results', dataIndexes: [dataIndex], resultsList: this}); } else { + // Gather results data from already present data. results.inspectResultsData.matches = [results.data.matches[dataIndex[0]]]; results.inspectResultsData.cpos_ranges = results.data.cpos_ranges; this.showMatchContext(results, client) } - // match nr for user to display derived from data_index + // Match nr for user to display derived from data_index. let contextMatchNrElement = document.getElementById("context-match-nr"); contextMatchNrElement.textContent = this.contextId + 1; this.contextModal.open(); - // add a button to add this match to sub results with onclick event + // Add the add button to add this match to sub results with onclick event. let classes = `btn-floating btn waves-effect` + ` waves-light corpus-analysis-color.lighten right` let addToSubResultsIdsBtn = document.createElement("a"); addToSubResultsIdsBtn.setAttribute("class", classes + ` add`); addToSubResultsIdsBtn.innerHTML = 'add'; - addToSubResultsIdsBtn.onclick= () => {this.addToSubResults(dataIndex[0], client, false)}; - // checks if the match has or has not been added to sub results yet - // sets the color and status of the button accordingly + addToSubResultsIdsBtn.onclick= () => { + this.addToSubResults(dataIndex[0], client, false) + }; + /** + * Checks if the match has or has not been added to sub results yet. + * Sets the color and status of the button accordingly. + */ if (this.addToSubResultsStatus[dataIndex[0]]) { this.helperActivateAddBtn(addToSubResultsIdsBtn.firstElementChild); } else if (!this.addToSubResultsStatus[dataIndex[0]]) { @@ -358,25 +363,27 @@ class ResultsList extends List { } this.createInspectMenu.innerHTML = ''; this.createInspectMenu.appendChild(addToSubResultsIdsBtn); - // Hide create menu if not in dynamic mode + // Hide create menu if not in dynamic mode. if (!client.dynamicMode) { this.createFromInspect.classList.add('hide'); } } - - // create Element from HTML String helper function + /** + * Create Element from HTML String. Helper function should be private. + * https://stackoverflow.com/questions/494143/creating-a-new-dom-element-from-an-html-string-using-built-in-dom-methods-or-pro/35385518#35385518 + */ HTMLTStrToElement(htmlStr) { - // https://stackoverflow.com/questions/494143/creating-a-new-dom-element-from-an-html-string-using-built-in-dom-methods-or-pro/35385518#35385518 let template = document.createElement("template"); htmlStr = htmlStr.trim(); template.innerHTML = htmlStr; return template.content.firstChild; } - - // Used as a callback to handle incoming match context results when inspect - // has been used. + /** + * Used either as a callback if the client has been notified to get new + * results with new full context. Or just directly invoced as a function + * with the according input data. + */ showMatchContext(results, client) { - this.getHTMLElements([ '#context-results', '#inspect-display-options-form-expert_mode_inspect', @@ -388,7 +395,7 @@ class ResultsList extends List { let uniqueContextS = new Set(); let {lc, c, rc} = this.helperCreateCpos(results.inspectResultsData.cpos_ranges, results.inspectResultsData.matches[0]); - // create sentence strings as tokens + // Create sentence strings as tokens. let tokenHTMLArray = []; let htmlTokenStr = ``; let tokenHTMlElement; @@ -457,9 +464,7 @@ class ResultsList extends List { } this.contextResults.appendChild(sentenceElement); } - - - // add inspect display options events + // Add expert mode switch event for the modal to toggle expert mode. this.inspectDisplayOptionsFormExpertModeInspect.onchange = (event) => { if (event.target.checked) { this.expertModeOn("context-results", results); @@ -467,7 +472,7 @@ class ResultsList extends List { this.expertModeOff("context-results") } }; - + // Add switch event to toggle Sentence highlighting. this.inspectDisplayOptionsFormHighlightSentences.onchange = (event) => { if (event.target.checked) { this.higlightContextSentences(); @@ -475,56 +480,52 @@ class ResultsList extends List { this.unhighlightContextSentences(); } }; - + // Add range event to change nr of context sentences. this.contextSentences.onchange = (event) => { - // console.log(event.target.value); this.changeSentenceContext(event.target.value); } - - // checks on new modal opening if switches are checked - // if switches are checked functions are executed + /** + * Checks on new modal opening if switches are checked + * if switches are checked functions are executed. + */ if (this.inspectDisplayOptionsFormExpertModeInspect.checked) { this.expertModeOn("context-results", results); } - if (this.inspectDisplayOptionsFormHighlightSentences.checked) { this.higlightContextSentences(); } - - // checks the value of the number of sentences to show on modal opening - // sets context sentences accordingly + /** + * Checks the value of the number of sentences to show on modal opening + * sets context sentences accordingly + */ this.changeSentenceContext(this.contextSentences.value); } - // splits context text into sentences based on spacy sentence split + // Splits context text into sentences based on spacy sentence split higlightContextSentences() { - let sentences; - sentences = document.getElementById("context-results").getElementsByClassName("sentence"); - for (let s of sentences) { - s.insertAdjacentHTML("beforeend", `

`) - } + let sentences = document.getElementById("context-results").getElementsByClassName("sentence"); + for (let s of sentences) { + s.insertAdjacentHTML("beforeend", `

`) + } } - + // Reverse operation of above function. unhighlightContextSentences() { - let sentences; + let sentences = document.getElementById("context-results").getElementsByClassName("sentence"); let br; - sentences = document.getElementById("context-results").getElementsByClassName("sentence"); - for (let s of sentences) { - br = s.lastChild; - br.remove(); - } + for (let s of sentences) { + br = s.lastChild; + br.remove(); + } } - // changes how many context sentences in inspect view are shown + // Changes how many context sentences in inspect view are shown. changeSentenceContext(sValue, maxSValue=10) { - let array; - let sentences; - let toHideArray; - let toShowArray; sValue = maxSValue - sValue; // console.log(sValue); - sentences = document.getElementById("context-results").getElementsByClassName("sentence"); - array = Array.from(sentences); + let sentences = document.getElementById("context-results").getElementsByClassName("sentence"); + let array = Array.from(sentences); + let toHideArray; + let toShowArray; if (sValue != 0) { toHideArray = array.slice(0, sValue).concat(array.slice(-(sValue))); toShowArray = array.slice(sValue, 9).concat(array.slice(9, -(sValue))) @@ -540,10 +541,12 @@ class ResultsList extends List { } } - // ###### Display options changing live how the matches are being displayed ###### + // ###### Display options functions changing how results are being displayed ###### - // Event function that changes the shown hits per page. - // Just alters the resultsList.page property + /** + * Event function that changes the shown hits per page. + * Just alters the resultsList.page property. + */ changeHitsPerPage(client, results) { this.page = this.displayOptionsFormResultsPerPage.value; this.update(); @@ -553,9 +556,10 @@ class ResultsList extends List { this.expertModeOn('query-display', results); } } - - // Event function triggered on context select change - // also if pagination is clicked + /** + * Event function triggered on context select change also if pagination is + * clicked. + */ changeContext() { let newContextValue = this.displayOptionsFormResultContext.value; let lc = document.querySelectorAll(".left-context"); @@ -581,48 +585,15 @@ class ResultsList extends List { } // ###### Expert view event functions ###### - // function to create a tooltip for the current hovered token + + // Function to create a tooltip for the current hovered token. tooltipEventCreate(event, results) { - // console.log("Create Tooltip on mouseover."); - let token; - token = results.data.cpos_lookup[event.target.dataset.cpos]; + let token = results.data.cpos_lookup[event.target.dataset.cpos]; if (!token) { token = results.inspectResultsData.cpos_lookup[event.target.dataset.cpos]; } - this.addToolTipToTokenElement(event.target, token, results); - } - - // Function to destroy the current Tooltip for the current hovered tooltip - // on mouse leave - tooltipEventDestroy(event) { - // console.log("Tooltip destroy on leave."); - this.currentTooltipElement.destroy(); - } - - // turn the expert mode on for all tokens in the DOM element identified by its htmlID - expertModeOn(htmlId, results) { - if (!Array.isArray(this.currentExpertTokenElements[htmlId])) { - this.currentExpertTokenElements[htmlId] = []; - } - let container = document.getElementById(htmlId); - let tokens = container.querySelectorAll('span.token'); - this.currentExpertTokenElements[htmlId].push(...tokens); - this.eventTokens[htmlId] = []; - for (let tokenElement of this.currentExpertTokenElements[htmlId]) { - tokenElement.classList.add("chip", "hoverable", "expert-view"); - const eventCreate = (event, arg) => this.tooltipEventCreate(event, arg); - tokenElement.onmouseover = (event) => eventCreate(event, results); - tokenElement.onmouseout = (event) => this.tooltipEventDestroy(event); - this.eventTokens[htmlId].push(tokenElement); - } - } - - // fuction that creates Tooltip for one token and extracts the corresponding - // infos from the result JSON - addToolTipToTokenElement(tokenElement, token, results) { - this.currentTooltipElement; - this.currentTooltipElement = M.Tooltip.init(tokenElement, - {"html": ` + this.currentTooltipElement = M.Tooltip.init(event.target, { + 'html': `
@@ -643,13 +614,44 @@ class ResultsList extends List { Publishing year: ${results.data.text_lookup[token.text].publishing_year} -
Token information Source information
`} - ); + ` + } + ); + } + /** + * Function to destroy the current Tooltip for the current hovered tooltip + * on mouse leave + */ + tooltipEventDestroy(event) { + this.currentTooltipElement.destroy(); } - // function to remove extra informations and animations from tokens + /** + * Turn the expert mode on for all tokens in the DOM element identified by + * its htmlID. + */ + expertModeOn(htmlId, results) { + if (!Array.isArray(this.currentExpertTokenElements[htmlId])) { + this.currentExpertTokenElements[htmlId] = []; + } + let container = document.getElementById(htmlId); + let tokens = container.querySelectorAll('span.token'); + this.currentExpertTokenElements[htmlId].push(...tokens); + this.eventTokens[htmlId] = []; + for (let tokenElement of this.currentExpertTokenElements[htmlId]) { + tokenElement.classList.add('chip', 'hoverable', 'expert-view'); + const eventCreate = (event, arg) => this.tooltipEventCreate(event, arg); + tokenElement.onmouseover = (event) => eventCreate(event, results); + tokenElement.onmouseout = (event) => this.tooltipEventDestroy(event); + this.eventTokens[htmlId].push(tokenElement); + } + } + + /** + * Turn the expert mode off for all tokens in the DOM element identified by + * its htmlID. + */ expertModeOff(htmlId) { - // console.log("Expert mode is off."); if (!Array.isArray(this.currentExpertTokenElements[htmlId])) { this.currentExpertTokenElements[htmlId] = []; } @@ -662,78 +664,62 @@ class ResultsList extends List { this.currentExpertTokenElements[htmlId] = []; for (let eventToken of this.eventTokens[htmlId]) { - eventToken.onmouseover = ""; - eventToken.onmouseout = ""; + eventToken.onmouseover = ''; + eventToken.onmouseout = ''; } this.eventTokens[htmlId] = []; } createResultRowElement(item, chunk, client, imported=false) { - let aCellElement; - let addToSubResultsBtn; - let cCellElement; - let cpos; - let fakeResponse; // used if imported results are being created; - let inspectBtn - let lcCellElement; - let matchNrElement; - let matchRowElement; - let rcCellElement; - let textTitles; - let textTitlesCellElement; - let token; - let values; - // gather values from item - values = item.values(); + // Gather values from item. + let values = item.values(); let {lc, c, rc} = this.helperCreateCpos(chunk.cpos_ranges, values) - // get infos for full match row - matchRowElement = document.createElement("tr"); + // Get infos for full match row. + let matchRowElement = document.createElement("tr"); matchRowElement.setAttribute("data-index", values.index) - lcCellElement = document.createElement("td"); + let lcCellElement = document.createElement("td"); lcCellElement.classList.add("left-context"); matchRowElement.appendChild(lcCellElement); - for (cpos of lc) { - token = chunk.cpos_lookup[cpos]; + for (let cpos of lc) { + let token = chunk.cpos_lookup[cpos]; lcCellElement.insertAdjacentHTML("beforeend", `${token.word} `); } - // get infos for hit of match and set actions - textTitles = new Set(); - aCellElement = document.createElement("td"); + // Get infos for hit of match and set actions. + let textTitles = new Set(); + let aCellElement = document.createElement("td"); aCellElement.classList.add("actions"); - cCellElement = document.createElement("td"); + let cCellElement = document.createElement("td"); cCellElement.classList.add("match-hit"); - textTitlesCellElement = document.createElement("td"); + let textTitlesCellElement = document.createElement("td"); textTitlesCellElement.classList.add("titles"); - matchNrElement = document.createElement("td"); + let matchNrElement = document.createElement("td"); matchNrElement.classList.add("match-nr"); matchRowElement.appendChild(cCellElement); matchRowElement.appendChild(aCellElement); - for (cpos of c) { - token = chunk.cpos_lookup[cpos]; + for (let cpos of c) { + let token = chunk.cpos_lookup[cpos]; cCellElement.insertAdjacentHTML("beforeend", `${token.word} `); - // get text titles of every hit cpos token + // Get text titles of every hit cpos token. textTitles.add(chunk.text_lookup[token.text].title); } - // add some interaction buttons - // # some btn css rules and classes + // Add some interaction buttons. let css = `margin-right: 5px; margin-bottom: 5px;` let classes = `btn-floating btn waves-effect` + ` waves-light corpus-analysis-color.lighten` - // # add button to trigger more context to every match td - inspectBtn = document.createElement("a"); + // Add inspect button to trigger inspect view with more context. + let inspectBtn = document.createElement("a"); inspectBtn.setAttribute("style", css); inspectBtn.setAttribute("class", classes + ` disabled inspect` ); inspectBtn.innerHTML = 'search'; - // # add btn to add matches to sub-results. hidden per default - addToSubResultsBtn = document.createElement("a"); + // Add btn to be able add matches to sub-results. + let addToSubResultsBtn = document.createElement("a"); addToSubResultsBtn.setAttribute("style", css); - addToSubResultsBtn.setAttribute("class", classes + ` add` - ); + addToSubResultsBtn.setAttribute("class", classes + ` add`); addToSubResultsBtn.innerHTML = 'add'; if (client.dynamicMode || client.fullContext) { aCellElement.appendChild(inspectBtn); @@ -741,25 +727,27 @@ class ResultsList extends List { if (client.dynamicMode) { aCellElement.appendChild(addToSubResultsBtn); } - // add text titles at front as first td of one row + // Add text titles at front as first td of one row. textTitlesCellElement.textContent = [...textTitles].join(", "); matchRowElement.insertAdjacentHTML("afterbegin", textTitlesCellElement.outerHTML); matchNrElement.textContent = values.index + 1; matchRowElement.insertAdjacentHTML("afterbegin", matchNrElement.outerHTML); - // get infos for right context of match - rcCellElement = document.createElement("td"); + // Get infos for right context of match + let rcCellElement = document.createElement("td"); rcCellElement.classList.add("right-context"); matchRowElement.appendChild(rcCellElement); - for (cpos of rc) { - token = chunk.cpos_lookup[cpos]; + for (let cpos of rc) { + let token = chunk.cpos_lookup[cpos]; rcCellElement.insertAdjacentHTML("beforeend", `${token.word} `); } return matchRowElement } - - // creates the HTML table code for the metadata view in the corpus analysis interface + /** + * Creates the HTML table code for the metadata view in the corpus analysis + * interface + */ createMetaDataForModal(metaDataObject) { let html = `
@@ -771,7 +759,7 @@ class ResultsList extends List { ` for (let [outerKey, outerValue] of Object.entries(metaDataObject)) { - // Use more descriptive names + // Use more descriptive names. if (outerKey === 'corpus_all_texts') { let tmpName = 'All texts in this corpus'; html += ` @@ -823,8 +811,10 @@ class ResultsList extends List {
` return html } - - // Creates the text details for the texts shown in the corpus analysis metadata modal. + /** + * Creates the text details for the texts shown in the corpus analysis + * metadata modal table. + */ createTextDetails(metaData) { let metadataKey = event.target.dataset.metadataKey; let textKey = event.target.dataset.textKey; @@ -833,12 +823,19 @@ class ResultsList extends List { bibliographicData.textContent = ''; for (let [key, value] of Object.entries(textData)) { bibliographicData.insertAdjacentHTML("afterbegin", - ` -
  • ${key}: ${value}
  • - `); + ` +
  • + ${key}: + ${value} +
  • + ` + ); } } }; -// export classses -export { ViewEventListener, ResultsList }; \ No newline at end of file +// Export classes. +export { + ViewEventListener, + ResultsList +}; \ No newline at end of file diff --git a/web/app/static/js/modules/corpus_analysis/view/callbacks.js b/web/app/static/js/modules/corpus_analysis/view/callbacks.js index 09d0d829..2ef4d1a6 100644 --- a/web/app/static/js/modules/corpus_analysis/view/callbacks.js +++ b/web/app/static/js/modules/corpus_analysis/view/callbacks.js @@ -4,6 +4,7 @@ * showing/enabling of common elements when data is being transmitted or not. */ +// Callback to disable some elements for the user when the client is busy. function disableElementsGeneralCallback(resultsList, detail) { if (detail.client.isBusy) { resultsList.fullResultsCreate.classList.toggle('disabled', true); @@ -12,6 +13,7 @@ function disableElementsGeneralCallback(resultsList, detail) { } } +// Callback to enable some elements for the user when the client is not busy. function enableElementsGeneralCallback(resultsList, detail) { if (!detail.client.isBusy) { resultsList.fullResultsCreate.classList.toggle('disabled', false); @@ -22,6 +24,10 @@ function enableElementsGeneralCallback(resultsList, detail) { } } + /** + * Callback opening the loading modal when the client is connecting to the + * CQP server. + */ function connectingCallback(resultsList, detail) { resultsList.getHTMLElements(['#analysis-init-modal']); resultsList.analysisInitModal = M.Modal.init(resultsList.analysisInitModal, @@ -29,10 +35,12 @@ function connectingCallback(resultsList, detail) { resultsList.analysisInitModal.open(); } +// Callback that closes teh loading modal from above. function connectedCallback(resultsList, detail) { resultsList.analysisInitModal.close(); } +// Callback that shows the user some feedback if the client raised an error. function clientFailedCallback(resultsList, detail) { resultsList.getHTMLElements([ '#analysis-init-progress', @@ -49,6 +57,7 @@ function clientFailedCallback(resultsList, detail) { } } +// Callback doing some preperation work if a query has been issued by the user. function queryDataPreparingCallback(resultsList, detail) { // remove all items from resultsList, like from the query issued before resultsList.clear() @@ -83,6 +92,10 @@ function queryDataPreparingCallback(resultsList, detail) { resultsList.resetFields(); } +/** + * Callback handling the incoming results of an issued query. It renders + * the incoming matches using the resultsList for the user. + */ function queryDataRecievingCallback(resultsList, detail) { // load the data into the resultsList and show them to the user let results = detail.results; @@ -142,6 +155,7 @@ function queryDataRecievingCallback(resultsList, detail) { } } +// Callback that is executed when all results from an issued query have been recieved function queryDataRecievedCallback(resultsList, detail) { // hide or disable some things for the user resultsList.queryResultsUserFeedback.classList.toggle('hide'); @@ -150,12 +164,16 @@ function queryDataRecievedCallback(resultsList, detail) { resultsList.queryProgressBar.firstElementChild.style.width = '0%'; } +/** + * Callback that is handling incoming results data. Results data is needed for + * the export and download of the data. + */ function resultsDataRecievingCallback(resultsList, detail) { resultsList.getHTMLElements([ '#full-results-progress-bar', '#sub-results-progress-bar', ]); - // Disable the full context switch when results are being recieved + // Disable the full context switch when results are being recieved. resultsList.exportFullInspectContext.setAttribute('disabled', ''); if (detail.type === 'full-results' && detail.progress) { resultsList.fullResultsProgressBar.firstElementChild.style.width = `${detail.progress}%`; @@ -165,7 +183,10 @@ function resultsDataRecievingCallback(resultsList, detail) { resultsList.subResultsProgressBar.classList.toggle('hide', false); } } - +/** + * Callback is executed when all results data has been recieved. + * Reactivates the resutls create buttons etc. + */ function resultsDataRecievedCallback(resultsList, detail) { // create strings for create buttons depending on type const handleType = (keyPrefix, text) => { diff --git a/web/app/static/js/modules/corpus_analysis/view/eventListeners.js b/web/app/static/js/modules/corpus_analysis/view/eventListeners.js index 3741955c..9215bae3 100644 --- a/web/app/static/js/modules/corpus_analysis/view/eventListeners.js +++ b/web/app/static/js/modules/corpus_analysis/view/eventListeners.js @@ -1,17 +1,17 @@ +/** +* This module contains vanilla javascript Event listeners which are listening +* for button clicks etc. the user is doing to interact with the page. +*/ + // Import the script that implements a spinner animation for buttons. import { loadingSpinnerHTML, } from './spinner.js'; -/** - * This module contains vanilla javascript Event listeners which are listening - * for button clicks etc. the user is doing to interact with the page. - */ /** * The following listener handles what functions are called when the user * does use the page navigation to navigate to a new page. */ - function pageNavigation(resultsList, results, client) { for (let element of resultsList.pagination) { element.addEventListener("click", (event) => { diff --git a/web/app/static/js/modules/corpus_analysis/view/listeners.js b/web/app/static/js/modules/corpus_analysis/view/listeners.js index 456f6bd1..1eb58c56 100644 --- a/web/app/static/js/modules/corpus_analysis/view/listeners.js +++ b/web/app/static/js/modules/corpus_analysis/view/listeners.js @@ -2,7 +2,7 @@ * This file contains the listener function that will be assigned to the * corpus_analysis ResultsView. The listener is listening for the notification * event which is being dispatched by the corpus_analysis Client. The - * notification Event triggers the listener whiche will call different + * notification Event triggers the listener which will call different * callback functions depending on the detail information of the notification * event. */ @@ -79,7 +79,6 @@ function recieveClientNotification(eventType, resultsList) { default: console.error('Recieved unkown notification case identifier from Client'); // do something to not crash the analysis session? - // maybe unnecessary } }); } diff --git a/web/app/static/js/modules/corpus_analysis/view/scrollToTop.js b/web/app/static/js/modules/corpus_analysis/view/scrollToTop.js index 302cdf4f..689ea13a 100644 --- a/web/app/static/js/modules/corpus_analysis/view/scrollToTop.js +++ b/web/app/static/js/modules/corpus_analysis/view/scrollToTop.js @@ -1,21 +1,27 @@ /** * Function to show a scroll to top button if the user has scrolled down - * 250 pixels from the headline element. + * 250 pixels from the with scrollToElementSelector specified Element. */ function scrollToTop(scrollToElementSelector, triggerElementSelector) { - let headline = document.querySelector(scrollToElementSelector); - let scrollToTop = document.querySelector(triggerElementSelector); + let scrollToThis = document.querySelector(scrollToElementSelector); + let scrolltoTopTrigger = document.querySelector(triggerElementSelector); window.addEventListener('scroll', (event) => { if (pageYOffset > 250) { - scrollToTop.classList.toggle('hide', false); + scrolltoTopTrigger.classList.toggle('hide', false); } else { - scrollToTop.classList.toggle('hide', true); + scrolltoTopTrigger.classList.toggle('hide', true); } }); - scrollToTop.onclick = () => { - headline.scrollIntoView({behavior: 'smooth', block: 'end', inline: 'nearest'}); + scrolltoTopTrigger.onclick = () => { + scrollToThis.scrollIntoView({ + behavior: 'smooth', + block: 'end', + inline: 'nearest' + }); }; } -// export function -export { scrollToTop }; \ No newline at end of file +// Export function. +export { + scrollToTop +}; \ No newline at end of file diff --git a/web/app/static/js/modules/corpus_analysis/view/spinner.js b/web/app/static/js/modules/corpus_analysis/view/spinner.js index 5c4b7422..5df37cfd 100644 --- a/web/app/static/js/modules/corpus_analysis/view/spinner.js +++ b/web/app/static/js/modules/corpus_analysis/view/spinner.js @@ -1,17 +1,19 @@ // loading spinner animation HTML const loadingSpinnerHTML = ` -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - `; +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +`; -//export -export { loadingSpinnerHTML }; +// Export const. +export { + loadingSpinnerHTML +}; diff --git a/web/app/templates/corpora/analyse_corpus.html.j2 b/web/app/templates/corpora/analyse_corpus.html.j2 index 08dd411a..64a8acb6 100644 --- a/web/app/templates/corpora/analyse_corpus.html.j2 +++ b/web/app/templates/corpora/analyse_corpus.html.j2 @@ -143,7 +143,7 @@ document.addEventListener("DOMContentLoaded", () => { let corpusId = {{ corpus_id }} const client = new Client({'corpusId': corpusId, 'socket': nopaque.socket, - 'logging': false, + 'logging': true, 'dynamicMode': true}); /** * Initializing the results object as a model holding all the data of a @@ -205,8 +205,8 @@ document.addEventListener("DOMContentLoaded", () => { */ const listenForClientNotification = new ViewEventListener('notify-view', recieveClientNotification); - resultsList.setNotificationListeners([listenForClientNotification]); - resultsList.loadNotificationListeners(); + resultsList.setViewEventListeners([listenForClientNotification]); + resultsList.loadViewEventListeners(); // Connect client to server. client.notifyView('connecting'); client.connect(); diff --git a/web/app/templates/query_results/inspect.html.j2 b/web/app/templates/query_results/inspect.html.j2 index 0f1caf21..f8e7e9b0 100644 --- a/web/app/templates/query_results/inspect.html.j2 +++ b/web/app/templates/query_results/inspect.html.j2 @@ -160,8 +160,8 @@ document.addEventListener("DOMContentLoaded", () => { */ const listenForClientNotification = new ViewEventListener('notify-view', recieveClientNotification); - resultsList.setNotificationListeners([listenForClientNotification]); - resultsList.loadNotificationListeners(); + resultsList.setViewEventListeners([listenForClientNotification]); + resultsList.loadViewEventListeners(); // Get all needed HTMLElements for the following event listeners. resultsList.getHTMLElements([ '.add-btn',