From 6b05651f05b7395244079213f7cd26308a4fbb20 Mon Sep 17 00:00:00 2001 From: Stephan Porada Date: Thu, 10 Sep 2020 15:33:04 +0200 Subject: [PATCH] Add Results viewer functionality with ne javascript and fix some errors. --- .../corpus_analysis/client/callbacks.js | 53 +++++--- .../modules/corpus_analysis/model/Results.js | 2 +- .../corpus_analysis/view/ResultsView.js | 50 ++++++-- .../modules/corpus_analysis/view/callbacks.js | 27 +++- .../templates/corpora/analyse_corpus.html.j2 | 25 ++-- .../templates/modals/context_modal.html.j2 | 2 +- .../templates/query_results/inspect.html.j2 | 121 +++++++++++++++++- 7 files changed, 231 insertions(+), 49 deletions(-) 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 bf42fecd..4db0c200 100644 --- a/web/app/static/js/modules/corpus_analysis/client/callbacks.js +++ b/web/app/static/js/modules/corpus_analysis/client/callbacks.js @@ -30,24 +30,43 @@ function prepareQueryData() { */ function saveQueryData() { 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; - let queryFormElement = document.querySelector('#query-form'); - results.data.getQueryStr(queryFormElement); - client.requestQueryProgress = payload.progress; - client.notifyView('query-data-recieving', - { results: results, - client: client, - dataLength: dataLength }); - console.info('Query data chunk saved', results.data); - if (client.requestQueryProgress === 100) { - client.isBusy = false; - client.notifyView('query-data-recieved'); + if (client.dynamicMode) { + // get data matches length before new chunk data is being inserted + // 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; + let queryFormElement = document.querySelector('#query-form'); + results.data.getQueryStr(queryFormElement); + client.requestQueryProgress = payload.progress; + client.notifyView('query-data-recieving', + { results: results, + client: client, + dataLength: dataLength }); + console.info('Query data chunk saved', results.data); + if (client.requestQueryProgress === 100) { + client.isBusy = false; + client.notifyView('query-data-recieved'); + } + } else { + results.data.matches.push(...payload.matches); + results.data.addData(payload.cpos_lookup, 'cpos_lookup'); + results.data.addData(payload.text_lookup, 'text_lookup'); + results.data.cpos_ranges = payload.cpos_ranges; + let queryFormElement = document.querySelector('#query-form'); + results.data.getQueryStr(queryFormElement); + client.requestQueryProgress = 100; + client.notifyView('query-data-recieving', + { results: results, + client: client, + dataLength: dataLength }); + console.info('Query data chunk saved', results.data); + if (client.requestQueryProgress === 100) { + console.log(results.data); + client.notifyView('query-data-recieved'); + } } } 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 ea43c696..0d3b3c24 100644 --- a/web/app/static/js/modules/corpus_analysis/model/Results.js +++ b/web/app/static/js/modules/corpus_analysis/model/Results.js @@ -52,7 +52,7 @@ class Data { let queryStr; queryFormData = new FormData(queryFormElement); queryStr = queryFormData.get('query-form-query'); - this["query"] = queryStr; + this['query'] = queryStr; } // function creates a unique and safe filename for the download 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 bc9fd742..e2afbf74 100644 --- a/web/app/static/js/modules/corpus_analysis/view/ResultsView.js +++ b/web/app/static/js/modules/corpus_analysis/view/ResultsView.js @@ -336,21 +336,28 @@ class ResultsList extends List { return fake_response } - // gets result cpos infos for one dataIndex (list of length 1) to send back to + // gets result cpos infos for dataIndexes (use list of length 1 for one match) to send back to // the server - inspect(dataIndex, type) { + inspect(client, results, dataIndex, type) { // initialize context modal this.getHTMLElements([ '#context-modal', '#context-results', '#create-inspect-menu', + '#create-from-inspect', ]); this.contextModal = M.Modal.init(this.contextModal); // get result infos from server and show them in context modal this.contextId = dataIndex[0]; this.contextResults.innerHTML = ""; // clear it from old inspects - this.notifyClient('get-results', {resultsType: 'inspect-results', - dataIndexes: [dataIndex]}); + if (client.dynamicMode) { + this.notifyClient('get-results', {resultsType: 'inspect-results', + dataIndexes: [dataIndex]}); + } else { + 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 let contextMatchNrElement = document.getElementById("context-match-nr"); contextMatchNrElement.textContent = this.contextId + 1; @@ -361,7 +368,7 @@ class ResultsList extends List { let addToSubResultsIdsBtn = document.createElement("a"); addToSubResultsIdsBtn.setAttribute("class", classes + ` add`); addToSubResultsIdsBtn.innerHTML = 'add'; - addToSubResultsIdsBtn.onclick= () => {this.addToSubResults(dataIndex[0], false)}; + 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]]) { @@ -371,6 +378,10 @@ class ResultsList extends List { } this.createInspectMenu.innerHTML = ''; this.createInspectMenu.appendChild(addToSubResultsIdsBtn); + // Hide create menu if not in dynamic mode + if (!client.dynamicMode) { + this.createFromInspect.classList.add('hide'); + } } // create Element from HTML String helper function @@ -384,7 +395,7 @@ class ResultsList extends List { // Used as a callback to handle incoming match context results when inspect // has been used. - showMatchContext(results) { + showMatchContext(results, client) { this.getHTMLElements([ '#context-results', @@ -403,7 +414,12 @@ class ResultsList extends List { let tokenHTMlElement; let token; for (let cpos of lc) { - token = results.inspectResultsData.cpos_lookup[cpos]; + if (client.dynamicMode) { + token = results.inspectResultsData.cpos_lookup[cpos]; + // If client is not in dynamic mode use cpos_lookup from results.data + } else { + token = results.data.cpos_lookup[cpos]; + } uniqueS.add(token.s) htmlTokenStr = ` { for (let item of items) { - item.elm = resultsList.createResultRowElement(item, results.data); + item.elm = resultsList.createResultRowElement(item, + results.data, + client); } }); // update user feedback about query status @@ -110,12 +112,27 @@ function queryDataRecievingCallback(resultsList, detail) { resultsList.expertModeOn('query-display', results); } } else if (!client.dynamicMode) { - results.jsList.add(resultItems, (items) => { + resultsList.add(resultItems, (items) => { for (let item of items) { - item.elm = results.jsList.createResultRowElement(item, payload.chunk, - true); + item.elm = resultsList.createResultRowElement(item, + results.data, + client, + true); } }); + // 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(client, results); + resultsList.changeContext(); } } @@ -158,7 +175,7 @@ function resultsDataRecievedCallback(resultsList, detail) { */ resultsList.subResultsCreate.classList.toggle('disabled', true); } - resultsList.showMatchContext(detail.results); + resultsList.showMatchContext(detail.results, detail.client); } } diff --git a/web/app/templates/corpora/analyse_corpus.html.j2 b/web/app/templates/corpora/analyse_corpus.html.j2 index 831cca10..f3ad7959 100644 --- a/web/app/templates/corpora/analyse_corpus.html.j2 +++ b/web/app/templates/corpora/analyse_corpus.html.j2 @@ -187,6 +187,7 @@ document.addEventListener("DOMContentLoaded", () => { listenForMetaData, listenForViewNotification, listenForResults]); + console.log(client.eventListeners); // Load the listeners so that they will be executed if triggered client.loadSocketEventListeners(); /** @@ -225,21 +226,21 @@ document.addEventListener("DOMContentLoaded", () => { }); // Get all needed HTMLElements for the following event listeners. resultsList.getHTMLElements([ - '#display-options-form-results_per_page', + '.pagination', + '#display-options-form-expert_mode', '#display-options-form-result_context', - '#show-meta-data', - '#meta-data-modal', - '#meta-data-modal-content', - '#full-results-create', - '#sub-results-create', - '#full-results-export', - '#sub-results-export', + '#display-options-form-results_per_page', '#download-results-json', + '#full-results-create', + '#full-results-export', + '#inspect-results-export', + '#meta-data-modal-content', + '#meta-data-modal', '#query-results-download-modal', '#query-results-table', - '#display-options-form-expert_mode', - '.pagination', - '#inspect-results-export', + '#show-meta-data', + '#sub-results-create', + '#sub-results-export', ]); /** @@ -280,7 +281,7 @@ document.addEventListener("DOMContentLoaded", () => { let dataIndex; if (event.target.classList.contains('inspect-btn')) { dataIndex = parseInt(event.target.closest('tr').dataset.index); - resultsList.inspect([dataIndex], 'inspect'); + resultsList.inspect(client, results, [dataIndex], 'inspect'); } else if (event.target.classList.contains('add-btn')) { dataIndex = parseInt(event.target.closest('tr').dataset.index); resultsList.addToSubResults(dataIndex, client); diff --git a/web/app/templates/modals/context_modal.html.j2 b/web/app/templates/modals/context_modal.html.j2 index cb0baa92..636c907e 100644 --- a/web/app/templates/modals/context_modal.html.j2 +++ b/web/app/templates/modals/context_modal.html.j2 @@ -42,7 +42,7 @@ -
+
Create
diff --git a/web/app/templates/query_results/inspect.html.j2 b/web/app/templates/query_results/inspect.html.j2 index 92a75393..9bc800c6 100644 --- a/web/app/templates/query_results/inspect.html.j2 +++ b/web/app/templates/query_results/inspect.html.j2 @@ -34,11 +34,15 @@
-
+
{% include 'interactions/infos.html.j2' %} {% include 'interactions/display.html.j2' %} {% include 'interactions/analysis.html.j2' %} {% include 'interactions/cite.html.j2' %} +
+ {% include 'interactions/export.html.j2' %} + {% include 'interactions/create.html.j2' %} +
{% include 'tables/query_results.html.j2' %}
@@ -93,6 +97,10 @@ import { ResultsList, ViewEventListener, } from '../../static/js/modules/corpus_analysis/view/ResultsView.js'; +// Import listener which will be registered to the ViewEventListener class. +import { + recieveClientNotification, +} from '../../static/js/modules/corpus_analysis/view/listeners.js'; import { scrollToTop, } from '../../static/js/modules/corpus_analysis/view/scrollToTop.js' @@ -130,7 +138,116 @@ document.addEventListener("DOMContentLoaded", () => { saveQueryData, [client, results]); listenForQueryData.setCallbacks([queryDataCallback]); - // TODO: execute callbacks manually with right input + // Set the event listeners + client.setSocketEventListeners([ + listenForQueryStatus, + listenForQueryData, + ]); + /** + * Register resultsList listeners listening to notification events emitted by + * the Client class. + */ + const listenForClientNotification = new ViewEventListener('notify-view', + recieveClientNotification); + resultsList.setNotificationListeners([listenForClientNotification]); + resultsList.loadNotificationListeners(); + // Get all needed HTMLElements for the following event listeners. + resultsList.getHTMLElements([ + '.add-btn', + '.pagination', + '#display-options-form-expert_mode', + '#display-options-form-result_context', + '#display-options-form-results_per_page', + '#full-results-create', + '#meta-data-modal-content', + '#meta-data-modal', + '#query-results-table', + '#show-meta-data', + '#sub-results-create', + ]); + // Execute client event listener callbacks manually because dynamicMode is false + client.eventListeners['corpus_analysis_query'].executeCallbacks([resultsJson]) + client.eventListeners['corpus_analysis_query_results'].executeCallbacks([resultsJson]) + /** + * The following listener handles what functions are called when the user + * does use the page navigation to navigate to a new page. + */ + for (let element of resultsList.pagination) { + element.addEventListener("click", (event) => { + // Shows match context according to the user picked value on a new page. + resultsList.changeContext(); + // De- or activates expertMode on new page depending on switch value. + if (resultsList.displayOptionsFormExpertMode.checked) { + resultsList.expertModeOn('query-display', results); + } else { + resultsList.expertModeOff('query-display'); + } + // Activates inspect buttons on new page if client is not busy. + resultsList.toggleInspectButtons(client); + }); + } + + /** + * The following event Listener handles the expert mode switch for the list. + */ + resultsList.displayOptionsFormExpertMode.onchange = (event) => { + if (event.target.checked) { + resultsList.expertModeOn('query-display', results); + } else { + resultsList.expertModeOff('query-display'); + } + }; + + /** + * The following event Listener handles the add-btn and the inspect-btn + * onclick events via bubbleing. + */ + resultsList.queryResultsTable.addEventListener('click', (event) => { + let dataIndex; + if (event.target.classList.contains('inspect-btn')) { + dataIndex = parseInt(event.target.closest('tr').dataset.index); + resultsList.inspect(client, results, [dataIndex], 'inspect'); + } else if (event.target.classList.contains('add-btn')) { + dataIndex = parseInt(event.target.closest('tr').dataset.index); + resultsList.addToSubResults(dataIndex, client); + } + }) + + /** + * Following event listeners handle the change of Context size per match and + * the number of matches shown per page. + */ + resultsList.displayOptionsFormResultsPerPage.onchange = (event) => { + resultsList.changeHitsPerPage(client, results); + }; + resultsList.displayOptionsFormResultContext.onchange = (event) => { + resultsList.changeContext(); + }; + + /** + * The following event listener handles the show metadata button and its + * functionality. Before the needed modal is initialized. + */ + resultsList.metaDataModal= M.Modal.init(resultsList.metaDataModal, { + 'preventScrolling': false, + 'opacity': 0.0, + 'dismissible': false, + 'onOpenEnd': (() => {document.querySelector(".modal-overlay").remove()}) + }); + resultsList.showMetaData.onclick = () => { + resultsList.metaDataModalContent.textContent = ''; + let table = resultsList.createMetaDataForModal(results.metaData); + resultsList.metaDataModalContent.insertAdjacentHTML('afterbegin', table); + resultsList.metaDataModal.open(); + let collapsibles = resultsList.metaDataModalContent.querySelectorAll(".text-metadata"); + for (let collapsible of collapsibles) { + collapsible.onclick = () => { + let elems = resultsList.metaDataModalContent.querySelectorAll('.collapsible'); + let instances = M.Collapsible.init(elems, {accordion: false}); + resultsList.createTextDetails(results.metaData); + } + } + }; // Enable scroll to Top functionality. scrollToTop('#headline', '#menu-scroll-to-top-div'); });