From 763183435dd2adde8c946a8a94a48aadc7de1949 Mon Sep 17 00:00:00 2001 From: Stephan Porada Date: Tue, 25 Aug 2020 15:56:04 +0200 Subject: [PATCH] Add result rendering for the user etc. --- .../modules/corpus_analysis/client/Client.js | 9 +- .../corpus_analysis/client/callbacks.js | 15 ++- .../corpus_analysis/client/listeners.js | 2 +- .../corpus_analysis/view/ResultsView.js | 66 +++++++------ .../modules/corpus_analysis/view/callbacks.js | 98 +++++++++++++++++-- .../modules/corpus_analysis/view/listeners.js | 12 ++- .../corpus_analysis/view/scrollToTop.js | 14 +-- .../templates/corpora/analyse_corpus.html.j2 | 32 +++++- web/app/templates/interactions/create.html.j2 | 2 +- web/app/templates/interactions/infos.html.j2 | 2 + 10 files changed, 183 insertions(+), 69 deletions(-) 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 b90df28e..09566c29 100644 --- a/web/app/static/js/modules/corpus_analysis/client/Client.js +++ b/web/app/static/js/modules/corpus_analysis/client/Client.js @@ -64,12 +64,9 @@ class Client { * This functions sends events to the View to trigger specific functions that * are handleing the representation of data stored in the model. */ - notifyView(caseIdentifier, msg=null) { - const event = new CustomEvent('notify', { detail: { - 'caseIdentifier': caseIdentifier, - 'msg': msg - } - }); + notifyView(caseIdentifier, detailObject={}) { + detailObject.caseIdentifier = caseIdentifier; + const event = new CustomEvent('notify', { detail: detailObject }); console.info('Dispatching Notification:', caseIdentifier); document.dispatchEvent(event); } 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 ed0f182a..afda9fe7 100644 --- a/web/app/static/js/modules/corpus_analysis/client/callbacks.js +++ b/web/app/static/js/modules/corpus_analysis/client/callbacks.js @@ -5,7 +5,6 @@ function saveMetaData() { let [payload, client, results, rest] = arguments; results.metaData.init(payload) - client.recivedMetaData = true; console.info('Metada saved:', results); } @@ -18,21 +17,29 @@ function saveMetaData() { function prepareQueryData() { // deletes old data from query issued before this new query let [payload, client, results, rest] = arguments; + // always initialize the results to delete data from the query issued before results.init(); + results.data.match_count = payload.match_count; client.requestQueryProgress = 0; - client.notifyView('query-data-prepareing'); + client.notifyView('query-data-prepareing', { results: results }); } function saveQueryData(args) { let [payload, client, results, rest] = arguments; + // get data matches length before new chun kdata is being inserted + let dataLength = results.data.matches.length; // incorporating new chunk data into full results results.data.matches.push(...payload.chunk.matches); results.data.addData(payload.chunk.cpos_lookup, 'cpos_lookup'); results.data.addData(payload.chunk.text_lookup, 'text_lookup'); results.data.cpos_ranges = payload.chunk.cpos_ranges; - results.data.match_count = results.data.matches.length; + let queryFormElement = document.querySelector('#query-form'); + results.data.getQueryStr(queryFormElement); client.requestQueryProgress = payload.progress; - client.notifyView('query-data-recieving') + client.notifyView('query-data-recieving', + { results: results, + client: client, + dataLength: dataLength }); console.info('Query data chunk saved', results.data); if (client.requestQueryProgress === 100) { client.notifyView('query-data-recieved'); 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 ea4fc41f..681bf6c6 100644 --- a/web/app/static/js/modules/corpus_analysis/client/listeners.js +++ b/web/app/static/js/modules/corpus_analysis/client/listeners.js @@ -28,7 +28,7 @@ function recieveConnected(type, client) { let errorText = `Error ${response.code} - ${response.msg}`; console.group('Connection failed!') console.error(`corpus_analysis_init: ${errorText}`); - client.notifyView('conntecting-failed', errorText); + client.notifyView('connecting-failed', { msg: errorText }); console.groupEnd(); } }); 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 845aa114..146913ed 100644 --- a/web/app/static/js/modules/corpus_analysis/view/ResultsView.js +++ b/web/app/static/js/modules/corpus_analysis/view/ResultsView.js @@ -21,7 +21,7 @@ class ResultsList extends List { * the options below are used. */ static options = { - page: 10, + page: 30, pagination: [{ name: "paginationTop", paginationClass: "paginationTop", @@ -37,7 +37,6 @@ class ResultsList extends List { }; constructor(idOrElement, options) { super(idOrElement, options); - this.options = options; /** * All span tokens which are holding events if expert * mode is on. Collected here to delete later on. @@ -68,16 +67,22 @@ class ResultsList extends List { // TODO: multipleResults=false, atattchSomeCallback=false ? getHTMLElements(arrayOfSelectors) { for (let selector of arrayOfSelectors) { - let element = document.querySelector(selector); + let element; + let elements; + if (selector.startsWith('#')) { + element = document.querySelector(selector); + } else { + elements = document.querySelectorAll(selector); + } let cleanKey = []; - selector = selector.replace('_', '-'); + selector = selector.replace(/_/g, '-'); selector.match(/\w+/g).forEach((word) => { let tmp = word[0].toUpperCase() + word.slice(1); cleanKey.push(tmp); }); cleanKey[0] = cleanKey[0].toLowerCase(); cleanKey = cleanKey.join(''); - this[cleanKey] = element; + this[cleanKey] = element ? element: elements; } } @@ -554,59 +559,58 @@ class ResultsList extends List { // Event function that changes the shown hits per page. // Just alters the resultsList.page property - changeHitsPerPage(event) { + changeHitsPerPage() { try { - // console.log(this); - this.page = event.target.value; - this.update(); - this.activateInspect(); - this.pageChangeEventInteractionHandler(interactionElements); - if (expertModeSwitchElement.checked) { - this.expertModeOn("query-display"); // page holds new result rows, so add new tooltips + if (event.type === "change") { + nopaque.flash("Updated matches per page.", "corpus") } - nopaque.flash("Updated matches per page.", "corpus") } catch (e) { - // console.log(e); - // console.log("resultsList has no results right now."); + + } finally { + this.page = this.displayOptionsFormResultsPerPage.value; + this.update(); } + // TODO: reimplement the followinmg stuff + // this.activateInspect(); + // this.pageChangeEventInteractionHandler(interactionElements); + // if (expertModeSwitchElement.checked) { + // this.expertModeOn("query-display"); // page holds new result rows, so add new tooltips + // } } // Event function triggered on context select change // also if pagination is clicked - changeContext(event) { - let array; - let lc; - let newContextValue; - let rc; + changeContext() { try { if (event.type === "change") { nopaque.flash("Updated context per match!", "corpus"); } } catch (e) { + } finally { - newContextValue = document.getElementById("display-options-form-result_context").value; - lc = document.getElementsByClassName("left-context"); - rc = document.getElementsByClassName("right-context"); + let newContextValue = this.displayOptionsFormResultContext.value; + let lc = document.querySelectorAll(".left-context"); + let rc = document.querySelectorAll(".right-context"); for (let element of lc) { - array = Array.from(element.childNodes); - for (let element of array.reverse().slice(newContextValue)) { + let arrayLc = Array.from(element.childNodes); + for (let element of arrayLc.reverse().slice(newContextValue)) { element.classList.add("hide"); } - for (let element of array.slice(0, newContextValue)) { + for (let element of arrayLc.slice(0, newContextValue)) { element.classList.remove("hide"); } } for (let element of rc) { - array = Array.from(element.childNodes); - for (let element of array.slice(newContextValue)) { + let arrayRc = Array.from(element.childNodes); + for (let element of arrayRc.slice(newContextValue)) { element.classList.add("hide"); } - for (let element of array.slice(0, newContextValue)) { + for (let element of arrayRc.slice(0, newContextValue)) { element.classList.remove("hide"); } } + } } - } // ###### Expert view event functions ###### // function to create a tooltip for the current hovered token 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 daf55db0..46b67fb7 100644 --- a/web/app/static/js/modules/corpus_analysis/view/callbacks.js +++ b/web/app/static/js/modules/corpus_analysis/view/callbacks.js @@ -2,34 +2,112 @@ * This file contains all the callbacks triggered by the notificationListener. */ -function connectingCallback(resultsList, msg=null) { +function connectingCallback(resultsList, detail) { resultsList.getHTMLElements(['#analysis-init-modal']); resultsList.analysisInitModal = M.Modal.init(resultsList.analysisInitModal, {dismissible: false}); resultsList.analysisInitModal.open(); } -function connectedCallback(resultsList, msg=null) { +function connectedCallback(resultsList, detail) { resultsList.analysisInitModal.close(); } -function connectingFaildeCallback(resultsList, msg=null) { +function connectingFaildeCallback(resultsList, detail) { resultsList.getHTMLElements([ '#analysis-init-progress', '#analysis-init-error' ]); resultsList.analysisInitProgress.classList.toggle('hide'); resultsList.analysisInitError.classList.toggle('hide'); - resultsList.analysisInitError.textContent = msg; + resultsList.analysisInitError.textContent = detail.msg; } -function queryDataPreparingCallback(resultsList, msg=null) { - resultsList.getHTMLElements(['#interactions-menu']); +function queryDataPreparingCallback(resultsList, detail) { + // remove all items from resultsList, like from the query issued before + resultsList.clear() + // get needed HTML Elements + let results = detail.results; + resultsList.getHTMLElements( + ['#interactions-menu', + '#recieved-match-count', + '#total-match-count', + '#text-lookup-count', + '#text-lookup-titles', + '#query-results-user-feedback', + '#query-progress-bar', + '#query-results-create', + '#add-to-sub-results' + ]); + // set some initial values for the user feedback + resultsList.recievedMatchCount.textContent = 0; + resultsList.totalMatchCount.textContent = results.data.match_count; + resultsList.textLookupTitles.textContent = ''; + resultsList.textLookupCount.textContent = 0; + // show or enable some things for the user resultsList.interactionsMenu.classList.toggle('hide', false) + resultsList.addToSubResults.setAttribute("disabled", ""); + resultsList.queryResultsUserFeedback.classList.toggle('hide', false); + resultsList.queryProgressBar.classList.toggle('hide', false); + // hide ore disable some things for the user + resultsList.queryResultsCreate.classList.toggle('disabled', true); +} + +function queryDataRecievingCallback(resultsList, detail) { + // load the data into the resultsList and show them to the user + let results = detail.results; + let client = detail.client; + let start = detail.dataLength; + let resultItems = []; + for (let [index, match] of Object.entries(results.data.matches).slice(start)) { + resultItems.push({ ...match, ...{ 'index': parseInt(index) } }); + } + if (client.dynamicMode) { + resultsList.add(resultItems, (items) => { + for (let item of items) { + item.elm = resultsList.createResultRowElement(item, results.data); + } + }); + // update user feedback about query status + resultsList.recievedMatchCount.textContent = results.data.matches.length; + resultsList.queryProgressBar.firstElementChild.style.width = `${client.requestQueryProgress}%`; + resultsList.textLookupCount.textContent = `${Object.keys(results.data.text_lookup).length}`; + let titles = new Array(); + for (let [key, value] of Object.entries(results.data.text_lookup)) { + titles.push(`${value.title} (${value.publishing_year})`); + } + resultsList.textLookupTitles.textContent = `${titles.join(', ')}`; + // updating table on finished item creation callback via createResultRowElement + resultsList.update(); + resultsList.changeHitsPerPage(); + resultsList.changeContext(); + } else if (!client.dynamicMode) { + results.jsList.add(resultItems, (items) => { + for (let item of items) { + item.elm = results.jsList.createResultRowElement(item, payload.chunk, + true); + } + }); + } +} + +function queryDataRecievedCallback(resultsList, detail) { + // hide or disable some things for the user + resultsList.queryResultsUserFeedback.classList.toggle('hide'); + resultsList.queryProgressBar.classList.toggle('hide'); + // show or enable some things for the user + resultsList.queryResultsCreate.classList.toggle('disabled'); + resultsList.addToSubResults.removeAttribute("disabled"); + + } // export the callbacks -export { connectingCallback, - connectedCallback, - connectingFaildeCallback, - queryDataPreparingCallback, }; \ No newline at end of file +export { + connectingCallback, + connectedCallback, + connectingFaildeCallback, + queryDataPreparingCallback, + queryDataRecievingCallback, + queryDataRecievedCallback, +}; \ No newline at end of file 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 34201cdd..b549a2f3 100644 --- a/web/app/static/js/modules/corpus_analysis/view/listeners.js +++ b/web/app/static/js/modules/corpus_analysis/view/listeners.js @@ -12,6 +12,8 @@ import { connectedCallback, connectingFaildeCallback, queryDataPreparingCallback, + queryDataRecievingCallback, + queryDataRecievedCallback, } from './callbacks.js'; function recieveNotification(eventType, resultsList) { @@ -20,30 +22,32 @@ function recieveNotification(eventType, resultsList) { switch (caseIdentifier) { case 'connecting': console.info('Recieved notification:', caseIdentifier); - connectingCallback(resultsList); + connectingCallback(resultsList, event.detail); // execute callback break; case 'connected': console.info('Recieved notification:', caseIdentifier); - connectedCallback(resultsList); + connectedCallback(resultsList, event.detail); break; case 'connecting-failed': console.info('Recieved notification:', caseIdentifier); // execute callback - connectingFaildeCallback(resultsList, event.detail.msg); + connectingFaildeCallback(resultsList, event.detail); break; case 'query-data-prepareing': console.info('Recieved notification:', caseIdentifier); // execute callback - queryDataPreparingCallback(resultsList); + queryDataPreparingCallback(resultsList, event.detail); break; case 'query-data-recieving': console.info('Recieved notification:', caseIdentifier); // execute callback + queryDataRecievingCallback(resultsList, event.detail); break; case 'query-data-recieved': console.info('Recieved notification:', caseIdentifier); // execute callback + queryDataRecievedCallback(resultsList, event.detail); break; default: console.error('Recieved unkown notification case identifier'); 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 d5aed247..302cdf4f 100644 --- a/web/app/static/js/modules/corpus_analysis/view/scrollToTop.js +++ b/web/app/static/js/modules/corpus_analysis/view/scrollToTop.js @@ -2,18 +2,18 @@ * Function to show a scroll to top button if the user has scrolled down * 250 pixels from the headline element. */ -function scrollToTop() { - let headline = document.querySelector(".headline"); - let scrollToTop = document.querySelector("#menu-scroll-to-top-div"); - window.addEventListener("scroll", (event) => { +function scrollToTop(scrollToElementSelector, triggerElementSelector) { + let headline = document.querySelector(scrollToElementSelector); + let scrollToTop = document.querySelector(triggerElementSelector); + window.addEventListener('scroll', (event) => { if (pageYOffset > 250) { - scrollToTop.classList.toggle("hide", false); + scrollToTop.classList.toggle('hide', false); } else { - scrollToTop.classList.toggle("hide", true); + scrollToTop.classList.toggle('hide', true); } }); scrollToTop.onclick = () => { - headline.scrollIntoView({behavior: "smooth", block: "end", inline: "nearest"}); + headline.scrollIntoView({behavior: 'smooth', block: 'end', inline: 'nearest'}); }; } diff --git a/web/app/templates/corpora/analyse_corpus.html.j2 b/web/app/templates/corpora/analyse_corpus.html.j2 index e7f5d4ac..3b932839 100644 --- a/web/app/templates/corpora/analyse_corpus.html.j2 +++ b/web/app/templates/corpora/analyse_corpus.html.j2 @@ -96,6 +96,9 @@ import { import { recieveNotification, } from '../../static/js/modules/corpus_analysis/view/listeners.js'; +import { + scrollToTop, +} from '../../static/js/modules/corpus_analysis/view/scrollToTop.js' /** * Second Phase: @@ -114,7 +117,7 @@ document.addEventListener("DOMContentLoaded", () => { * object as the View handeling the represnetation of the data. */ let results = new Results(); - let resultsView = new ResultsList('result-list', ResultsList.options); + let resultsList = new ResultsList('result-list', ResultsList.options); /** * Register listeners listening to socket.io events and their callbacks * Afterwards load them. @@ -145,18 +148,18 @@ document.addEventListener("DOMContentLoaded", () => { listenForMetaData]); client.loadSocketEventListeners(); /** - * Register resultsView listeners listening to nitification events. + * Register resultsList listeners listening to nitification events. */ const listenForNotification = new NotificationListener('notify', recieveNotification); - resultsView.setNotificationListeners([listenForNotification]); - resultsView.loadNotificationListeners(); + resultsList.setNotificationListeners([listenForNotification]); + resultsList.loadNotificationListeners(); // Connect client to server client.notifyView('connecting'); client.connect(); // Send a query and recieve its answer data - let queryFormElement = document.getElementById('query-form'); + let queryFormElement = document.querySelector('#query-form'); queryFormElement.addEventListener('submit', (event) => { try { /** @@ -178,6 +181,25 @@ document.addEventListener("DOMContentLoaded", () => { results.data.getQueryStr(queryFormElement); client.query(results.data.query); }); + /** + * Display events + * 1. live update of hits per page if hits per page value is changed + */ + resultsList.getHTMLElements([ + '#display-options-form-results_per_page', + '#display-options-form-result_context' + ]); + resultsList.displayOptionsFormResultsPerPage.onchange = () => { + resultsList.changeHitsPerPage(); + }; + resultsList.displayOptionsFormResultContext.onchange = () => { + resultsList.changeContext(); + }; + + + + // enable scroll to Top + scrollToTop('.headline', '#menu-scroll-to-top-div'); }); {% endblock %} diff --git a/web/app/templates/interactions/create.html.j2 b/web/app/templates/interactions/create.html.j2 index 5cf72d4f..7c76be7b 100644 --- a/web/app/templates/interactions/create.html.j2 +++ b/web/app/templates/interactions/create.html.j2 @@ -7,7 +7,7 @@ results.-->
- Sub-Results creation: + Sub-Results: